1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
4 * vim: tabstop=4 shiftwidth=4 noexpandtab :
8 #include <libxml/parser.h>
19 htmlescape htmlescapes
[] = {
23 /* FIXME: add more entities here?
24 * the above are all that are really necessary, though. */
29 tools_html_escape(GtkWindow
*win
, JamDoc
*doc
) {
30 GtkTextBuffer
*buffer
;
31 GtkTextIter start
, end
, pos
, nextpos
;
36 buffer
= jam_doc_get_text_buffer(doc
);
38 if (!gtk_text_buffer_get_selection_bounds(buffer
, &start
, &end
)) {
39 /* no selection; give them some help. */
40 jam_messagebox(win
, _("HTML Escape Help"),
41 _("Some characters (such as '<' and '&') are reserved in HTML, and may not display correctly in your post.\n"
42 "To properly escape these characters, first select a block of text and try this command again.\n"));
46 gtk_text_buffer_begin_user_action(buffer
); /* atomic, in terms of undo */
49 gtk_text_iter_in_range(&pos
, &start
, &end
);
50 gtk_text_iter_forward_char(&pos
)) {
51 c
= gtk_text_iter_get_char(&pos
);
52 for (esc
= htmlescapes
; esc
->c
!= 0; esc
++) {
53 if (esc
->c
== c
) break;
55 /* if this character isn't in the to-escape list, continue. */
56 if (esc
->c
== 0) continue;
59 gtk_text_iter_forward_char(&nextpos
);
60 gtk_text_buffer_delete(buffer
, &pos
, &nextpos
);
61 len
= (int)strlen(esc
->escaped
);
62 gtk_text_buffer_insert(buffer
, &pos
, esc
->escaped
, len
);
64 /* to counteract the pos++ in the for loop */
65 gtk_text_iter_backward_char(&pos
);
67 /* changing the buffer invalidates this iterator */
68 gtk_text_buffer_get_selection_bounds(buffer
, &start
, &end
);
71 gtk_text_buffer_move_mark_by_name(buffer
, "insert", &end
);
72 gtk_text_buffer_move_mark_by_name(buffer
, "selection_bound", &end
);
74 gtk_text_buffer_end_user_action(buffer
);
78 tools_remove_linebreaks(GtkWindow
*win
, JamDoc
*doc
) {
79 GtkTextBuffer
*buffer
;
80 GtkTextIter start
, end
, pos
, nextpos
;
82 gboolean leading
= TRUE
;
84 buffer
= jam_doc_get_text_buffer(doc
);
86 if (!gtk_text_buffer_get_selection_bounds(buffer
, &start
, &end
)) {
87 /* no selection; give them some help. */
88 jam_messagebox(win
, _("Linebreak Removal Help"),
89 _("When you paste text into LogJam, linebreaks from the original are preserved, and will normally show up in your post on the server. You can have LogJam remove extraneous linebreaks from your entry, but you must make a specific selection of the text you wish this to happen on first.\n\nNote that you can also use the \"don't autoformat\" option on your post."));
93 gtk_text_buffer_begin_user_action(buffer
); /* atomic, in terms of undo */
96 gtk_text_iter_in_range(&pos
, &start
, &end
);
97 gtk_text_iter_forward_char(&pos
)) {
99 /* we remove linebreaks as we see them; but later we will
100 * make up for our deletions by inserting spaces or \n\ns
102 if (gtk_text_iter_ends_line(&pos
)) {
103 if (!leading
) /* leading linebreaks should just be removed */
106 gtk_text_iter_forward_line(&nextpos
);
107 gtk_text_buffer_delete(buffer
, &pos
, &nextpos
);
109 gtk_text_iter_backward_char(&pos
);
111 gtk_text_buffer_get_selection_bounds(buffer
, &start
, &end
);
118 /* make up for what we removed, according to how long a run
120 if (nlrun
== 1) { /* line separators turn into a space */
122 gtk_text_buffer_insert(buffer
, &pos
, " ", 1);
123 gtk_text_buffer_get_selection_bounds(buffer
, &start
, &end
);
124 } else if (nlrun
> 1) { /* paragraph breaks are normalized */
126 gtk_text_buffer_insert(buffer
, &pos
, "\n\n", 2);
128 /* this is safe, since we've just added two characters. */
129 gtk_text_iter_forward_chars(&pos
, 2);
131 gtk_text_buffer_get_selection_bounds(buffer
, &start
, &end
);
135 gtk_text_buffer_move_mark_by_name(buffer
, "insert", &end
);
136 gtk_text_buffer_move_mark_by_name(buffer
, "selection_bound", &end
);
138 gtk_text_buffer_end_user_action(buffer
);
142 tools_insert_file(GtkWindow
*win
, JamDoc
*doc
) {
144 GtkWidget
*hbox
, *label
, *combo
;
145 GList
*strings
= NULL
;
146 const char *localeenc
;
148 filesel
= gtk_file_chooser_dialog_new(_("Select File"), win
,
149 GTK_FILE_CHOOSER_ACTION_OPEN
,
150 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
151 GTK_STOCK_OPEN
, GTK_RESPONSE_ACCEPT
,
153 label
= gtk_label_new(NULL
);
154 gtk_label_set_text_with_mnemonic(GTK_LABEL(label
),
155 _("File _encoding:"));
156 combo
= gtk_combo_new();
158 /* if they're in a non-UTF-8 locale, default to the locale encoding. */
159 if (!g_get_charset(&localeenc
))
160 strings
= g_list_append(strings
, (char*)localeenc
);
161 /* what else should go in this list other than UTF-8? */
162 strings
= g_list_append(strings
, "UTF-8");
163 gtk_combo_set_popdown_strings(GTK_COMBO(combo
), strings
);
165 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), GTK_COMBO(combo
)->entry
);
167 hbox
= gtk_hbox_new(FALSE
, 12);
168 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
169 gtk_box_pack_start(GTK_BOX(hbox
), combo
, TRUE
, TRUE
, 0);
171 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(filesel
)->vbox
),
172 hbox
, FALSE
, FALSE
, 0);
173 gtk_widget_show_all(hbox
);
175 while (gtk_dialog_run(GTK_DIALOG(filesel
)) == GTK_RESPONSE_ACCEPT
) {
176 const gchar
*filename
;
177 const gchar
*encoding
;
180 filename
= gtk_file_chooser_get_filename(
181 GTK_FILE_CHOOSER(filesel
));
182 encoding
= gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(combo
)->entry
));
184 if (!jam_doc_insert_file(doc
, filename
, encoding
, &err
)) {
185 jam_warning(GTK_WINDOW(filesel
),
186 _("Error loading file: %s"), err
->message
);
192 gtk_widget_destroy(filesel
);
196 tools_insert_command_output(GtkWindow
*win
, JamDoc
*doc
) {
197 /* FIXME: use combo with a history drop-down, save the history */
198 GtkWidget
*cmd_dlg
, *entry
, *box
;
200 cmd_dlg
= gtk_dialog_new_with_buttons(_("Insert Command Output"),
201 win
, GTK_DIALOG_MODAL
,
202 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
203 GTK_STOCK_OK
, GTK_RESPONSE_OK
,
205 gtk_window_set_transient_for(GTK_WINDOW(cmd_dlg
), win
);
207 entry
= gtk_entry_new();
209 box
= labelled_box_new(_("_Command:"), entry
);
210 jam_dialog_set_contents(GTK_DIALOG(cmd_dlg
), box
);
211 gtk_widget_grab_focus(entry
);
212 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
213 gtk_dialog_set_default_response(GTK_DIALOG(cmd_dlg
), GTK_RESPONSE_OK
);
215 while (gtk_dialog_run(GTK_DIALOG(cmd_dlg
)) == GTK_RESPONSE_OK
) {
216 const gchar
*command
;
218 command
= gtk_entry_get_text(GTK_ENTRY(entry
));
219 if (!jam_doc_insert_command_output(doc
, command
, NULL
, &err
, win
) && (err
)) {
220 jam_warning(GTK_WINDOW(cmd_dlg
),
221 _("Error getting command output: %s"),
223 if (err
->code
!= 127)
230 gtk_widget_destroy(cmd_dlg
);
233 /* it appears there's no way to attach a pointer to a libXML
234 * parser context, which means we need to use a global variable
235 * so we can save the error message it tries to print to stdout
236 * and display it in a dialog instead. as another hack, we need
237 * to print that text in a monospaced font because it highlights
238 * the invalid character position...
240 * the ideal solution would be for libxml to use GError to report
241 * errors and then I wouldn't need any of this.
243 static GString
*xml_error_context_hack
;
245 xmlErrFunc(void *ctx
, const char *msg
, ...) {
249 s
= g_strdup_vprintf(msg
, ap
);
250 g_string_append(xml_error_context_hack
, s
);
255 xml_error_dialog(GtkWindow
*parent
, GString
*errstr
) {
256 GtkWidget
*dlg
, *box
, *label
;
258 dlg
= gtk_dialog_new_with_buttons(_("XML Parse Error"),
259 parent
, GTK_DIALOG_MODAL
,
260 GTK_STOCK_OK
, GTK_STOCK_CANCEL
, NULL
);
262 box
= gtk_vbox_new(FALSE
, 5);
264 gtk_box_pack_start(GTK_BOX(box
),
265 gtk_label_new(_("Error parsing XML:")),
268 label
= gtk_label_new(errstr
->str
);
269 gtk_label_set_selectable(GTK_LABEL(label
), TRUE
);
270 jam_widget_set_font(label
, "monospace");
271 gtk_box_pack_start(GTK_BOX(box
), label
, TRUE
, TRUE
, 0);
273 jam_dialog_set_contents(GTK_DIALOG(dlg
), box
);
275 gtk_dialog_run(GTK_DIALOG(dlg
));
276 gtk_widget_destroy(dlg
);
280 validate_xml(GtkWindow
*parent
, char *text
) {
281 xmlParserCtxtPtr ctxt
= xmlCreateDocParserCtxt(BAD_CAST text
);
283 xml_error_context_hack
= g_string_new(NULL
);
284 xmlSetGenericErrorFunc(ctxt
, xmlErrFunc
);
285 if (xmlParseDocument(ctxt
) < 0) {
286 xml_error_dialog(parent
, xml_error_context_hack
);
287 xmlFreeParserCtxt(ctxt
);
288 g_string_free(xml_error_context_hack
, TRUE
);
291 initGenericErrorDefaultFunc(NULL
);
292 xmlFreeParserCtxt(ctxt
);
293 g_string_free(xml_error_context_hack
, TRUE
);
298 tools_validate_xml(GtkWindow
*win
, JamDoc
*doc
) {
299 GtkTextBuffer
*buffer
;
300 GtkTextIter start
, end
;
303 buffer
= jam_doc_get_text_buffer(doc
);
305 /* default to the selection, but fall back to the entire entry. */
306 if (!gtk_text_buffer_get_selection_bounds(buffer
, &start
, &end
))
307 gtk_text_buffer_get_bounds(buffer
, &start
, &end
);
309 entry
= gtk_text_buffer_get_text(buffer
, &start
, &end
, TRUE
);
310 str
= g_strdup_printf("<entry>%s</entry>", entry
);
312 if (validate_xml(win
, str
)) {
313 jam_messagebox(win
, _("XML Validation"),
314 _("Document is well-formed XML."));
322 tools_ljcut(GtkWindow
*win
, JamDoc
*doc
) {
323 GtkTextBuffer
*buffer
;
324 GtkTextIter start
, end
;
325 GtkWidget
*dlg
, *vbox
, *hbox
, *label
, *entry
;
328 dlg
= gtk_dialog_new_with_buttons(_("LJ-Cut"), win
,
330 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
331 GTK_STOCK_OK
, GTK_RESPONSE_OK
,
333 gtk_dialog_set_default_response(GTK_DIALOG(dlg
), GTK_RESPONSE_OK
);
335 vbox
= gtk_vbox_new(FALSE
, 5);
336 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 10);
338 entry
= gtk_entry_new();
339 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
340 hbox
= labelled_box_new(_("Cut c_aption:"), entry
);
341 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
343 label
= gtk_label_new(NULL
);
344 gtk_label_set_markup(GTK_LABEL(label
),
345 _("<small>If left empty, the LiveJournal default "
346 "will be used.</small>"));
347 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
349 jam_dialog_set_contents(GTK_DIALOG(dlg
), vbox
);
351 if (gtk_dialog_run(GTK_DIALOG(dlg
)) != GTK_RESPONSE_OK
) {
352 gtk_widget_destroy(dlg
);
355 text
= gtk_editable_get_chars(GTK_EDITABLE(entry
), 0, -1);
356 gtk_widget_destroy(dlg
);
363 buffer
= jam_doc_get_text_buffer(doc
);
365 gtk_text_buffer_begin_user_action(buffer
); /* start undo action */
366 if (!gtk_text_buffer_get_selection_bounds(buffer
, &start
, &end
)) {
368 gtk_text_buffer_insert_at_cursor(buffer
, "<lj-cut text=\"", -1);
369 gtk_text_buffer_insert_at_cursor(buffer
, text
, -1);
370 gtk_text_buffer_insert_at_cursor(buffer
, "\">", -1);
372 gtk_text_buffer_insert_at_cursor(buffer
, "<lj-cut>", -1);
376 gtk_text_buffer_insert(buffer
, &start
, "<lj-cut text=\"", -1);
377 gtk_text_buffer_insert(buffer
, &start
, text
, -1);
378 gtk_text_buffer_insert(buffer
, &start
, "\">", -1);
380 gtk_text_buffer_insert(buffer
, &start
, "<lj-cut>", -1);
382 gtk_text_buffer_get_selection_bounds(buffer
, &start
, &end
);
383 gtk_text_buffer_insert(buffer
, &end
, "</lj-cut>", -1);
384 gtk_text_buffer_move_mark_by_name(buffer
, "insert", &end
);
385 gtk_text_buffer_move_mark_by_name(buffer
, "selection_bound", &end
);
389 gtk_text_buffer_end_user_action(buffer
);