Mon, 22 Aug 2022 21:40:04 -0500
Inline pidgin_make_scrollable
We need to change it for GTK4, and there are few enough that it can be inlined. Eventually, that code might be a `.ui` anyway.
Testing Done:
Compile only.
Reviewed at https://reviews.imfreedom.org/r/1615/
/* * Purple - Internet Messaging Library * Copyright (C) Pidgin Developers <devel@pidgin.im> * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * 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, see <https://www.gnu.org/licenses/>. */ #include <ctype.h> #include <purple.h> #include "zephyr_html.h" typedef struct _zframe zframe; struct _zframe { /* common part */ /* true for everything but @color, since inside the parens of that one is * the color. */ gboolean has_closer; /* </i>, </font>, </b>, etc. */ const char *closing; /* text including the opening html thingie. */ GString *text; /* html_to_zephyr */ /* @i, @b, etc. */ const char *env; /* }=1, ]=2, )=4, >=8 */ int closer_mask; /* href for links */ gboolean is_href; GString *href; /* zephyr_to_html */ /* }, ], ), > */ char *closer; }; static zframe * zframe_new_with_text(const gchar *text, const gchar *closing, gboolean has_closer) { zframe *frame = g_new(zframe, 1); frame->text = g_string_new(text); frame->closing = closing; frame->has_closer = has_closer; return frame; } static inline zframe * zframe_new(const gchar *closing, gboolean has_closer) { return zframe_new_with_text("", closing, has_closer); } static gboolean zframe_href_has_prefix(const zframe *frame, const gchar *prefix) { gsize prefix_len = strlen(prefix); return (frame->href->len == (prefix_len + frame->text->len)) && !strncmp(frame->href->str, prefix, prefix_len) && purple_strequal(frame->href->str + prefix_len, frame->text->str); } static gsize html_to_zephyr_pop(GQueue *frames) { zframe *popped = (zframe *)g_queue_pop_head(frames); zframe *head = (zframe *)g_queue_peek_head(frames); gsize result = strlen(popped->closing); if (popped->is_href) { head->href = popped->text; } else { g_string_append(head->text, popped->env); if (popped->has_closer) { g_string_append_c(head->text, (popped->closer_mask & 1) ? '{' : (popped->closer_mask & 2) ? '[' : (popped->closer_mask & 4) ? '(' : '<'); } g_string_append(head->text, popped->text->str); if (popped->href) { if (!purple_strequal(popped->href->str, popped->text->str) && !zframe_href_has_prefix(popped, "http://") && !zframe_href_has_prefix(popped, "mailto:")) { g_string_append(head->text, " <"); g_string_append(head->text, popped->href->str); if (popped->closer_mask & ~8) { g_string_append_c(head->text, '>'); popped->closer_mask &= ~8; } else { g_string_append(head->text, "@{>}"); } } g_string_free(popped->href, TRUE); } if (popped->has_closer) { g_string_append_c(head->text, (popped->closer_mask & 1) ? '}' : (popped->closer_mask & 2) ? ']' : (popped->closer_mask & 4) ? ')' : '>'); } if (!popped->has_closer) { head->closer_mask = popped->closer_mask; } g_string_free(popped->text, TRUE); } g_free(popped); return result; } char * html_to_zephyr(const char *message) { GQueue frames = G_QUEUE_INIT; zframe *frame, *new_f; char *ret; if (*message == '\0') return g_strdup(""); frame = zframe_new(NULL, FALSE); frame->href = NULL; frame->is_href = FALSE; frame->env = ""; frame->closer_mask = 15; g_queue_push_head(&frames, frame); purple_debug_info("zephyr", "html received %s\n", message); while (*message) { frame = (zframe *)g_queue_peek_head(&frames); if (frame->closing && purple_str_has_caseprefix(message, frame->closing)) { message += html_to_zephyr_pop(&frames); } else if (*message == '<') { if (!g_ascii_strncasecmp(message + 1, "i>", 2)) { new_f = zframe_new("</i>", TRUE); new_f->href = NULL; new_f->is_href = FALSE; new_f->env = "@i"; new_f->closer_mask = 15; g_queue_push_head(&frames, new_f); message += 3; } else if (!g_ascii_strncasecmp(message + 1, "b>", 2)) { new_f = zframe_new("</b>", TRUE); new_f->href = NULL; new_f->is_href = FALSE; new_f->env = "@b"; new_f->closer_mask = 15; g_queue_push_head(&frames, new_f); message += 3; } else if (!g_ascii_strncasecmp(message + 1, "br>", 3)) { g_string_append_c(frame->text, '\n'); message += 4; } else if (!g_ascii_strncasecmp(message + 1, "a href=\"", 8)) { message += 9; new_f = zframe_new("</a>", FALSE); new_f->href = NULL; new_f->is_href = FALSE; new_f->env = ""; new_f->closer_mask = frame->closer_mask; g_queue_push_head(&frames, new_f); new_f = zframe_new("\">", FALSE); new_f->href = NULL; new_f->is_href = TRUE; new_f->closer_mask = frame->closer_mask; g_queue_push_head(&frames, new_f); } else if (!g_ascii_strncasecmp(message + 1, "font", 4)) { new_f = zframe_new("</font>", TRUE); new_f->href = NULL; new_f->is_href = FALSE; new_f->closer_mask = 15; g_queue_push_head(&frames, new_f); message += 5; while (*message == ' ') { message++; } if (!g_ascii_strncasecmp(message, "color=\"", 7)) { message += 7; new_f->env = "@"; new_f = zframe_new("\">", TRUE); new_f->env = "@color"; new_f->href = NULL; new_f->is_href = FALSE; new_f->closer_mask = 15; g_queue_push_head(&frames, new_f); } else if (!g_ascii_strncasecmp(message, "face=\"", 6)) { message += 6; new_f->env = "@"; new_f = zframe_new("\">", TRUE); new_f->env = "@font"; new_f->href = NULL; new_f->is_href = FALSE; new_f->closer_mask = 15; g_queue_push_head(&frames, new_f); } else if (!g_ascii_strncasecmp(message, "size=\"", 6)) { message += 6; if ((*message == '1') || (*message == '2')) { new_f->env = "@small"; } else if ((*message == '3') || (*message == '4')) { new_f->env = "@medium"; } else if ((*message == '5') || (*message == '6') || (*message == '7')) { new_f->env = "@large"; } else { new_f->env = ""; new_f->has_closer = FALSE; new_f->closer_mask = frame->closer_mask; } message += 3; } else { /* Drop all unrecognized/misparsed font tags */ new_f->env = ""; new_f->has_closer = FALSE; new_f->closer_mask = frame->closer_mask; while (g_ascii_strncasecmp(message, "\">", 2) != 0) { message++; } if (*message != '\0') { message += 2; } } } else { /* Catch all for all unrecognized/misparsed <foo> tags */ g_string_append_c(frame->text, *message++); } } else if (*message == '@') { g_string_append(frame->text, "@@"); message++; } else if (*message == '}') { if (frame->closer_mask & ~1) { frame->closer_mask &= ~1; g_string_append_c(frame->text, *message++); } else { g_string_append(frame->text, "@[}]"); message++; } } else if (*message == ']') { if (frame->closer_mask & ~2) { frame->closer_mask &= ~2; g_string_append_c(frame->text, *message++); } else { g_string_append(frame->text, "@{]}"); message++; } } else if (*message == ')') { if (frame->closer_mask & ~4) { frame->closer_mask &= ~4; g_string_append_c(frame->text, *message++); } else { g_string_append(frame->text, "@{)}"); message++; } } else if (!g_ascii_strncasecmp(message, ">", 4)) { if (frame->closer_mask & ~8) { frame->closer_mask &= ~8; g_string_append_c(frame->text, *message++); } else { g_string_append(frame->text, "@{>}"); message += 4; } } else { g_string_append_c(frame->text, *message++); } } frame = (zframe *)g_queue_pop_head(&frames); ret = g_string_free(frame->text, FALSE); g_free(frame); purple_debug_info("zephyr", "zephyr outputted %s\n", ret); return ret; } static void zephyr_to_html_pop(GQueue *frames, gboolean *last_had_closer) { zframe *popped = (zframe *)g_queue_pop_head(frames); zframe *head = (zframe *)g_queue_peek_head(frames); g_string_append(head->text, popped->text->str); g_string_append(head->text, popped->closing); if (last_had_closer != NULL) { *last_had_closer = popped->has_closer; } g_string_free(popped->text, TRUE); g_free(popped); } char * zephyr_to_html(const char *message) { GQueue frames = G_QUEUE_INIT; zframe *frame; char *ret; frame = zframe_new("", FALSE); frame->closer = NULL; g_queue_push_head(&frames, frame); while (*message) { frame = (zframe *)g_queue_peek_head(&frames); if (*message == '@' && message[1] == '@') { g_string_append(frame->text, "@"); message += 2; } else if (*message == '@') { int end = 1; while (message[end] && (isalnum(message[end]) || message[end] == '_')) { end++; } if (message[end] && (message[end] == '{' || message[end] == '[' || message[end] == '(' || !g_ascii_strncasecmp(message + end, "<", 4))) { zframe *new_f; char *buf; char *closer; buf = g_new0(char, end); g_snprintf(buf, end, "%s", message + 1); message += end; closer = (*message == '{' ? "}" : *message == '[' ? "]" : *message == '(' ? ")" : ">"); message += (*message == '&' ? 4 : 1); if (!g_ascii_strcasecmp(buf, "italic") || !g_ascii_strcasecmp(buf, "i")) { new_f = zframe_new_with_text("<i>", "</i>", TRUE); } else if (!g_ascii_strcasecmp(buf, "small")) { new_f = zframe_new_with_text("<font size=\"1\">", "</font>", TRUE); } else if (!g_ascii_strcasecmp(buf, "medium")) { new_f = zframe_new_with_text("<font size=\"3\">", "</font>", TRUE); } else if (!g_ascii_strcasecmp(buf, "large")) { new_f = zframe_new_with_text("<font size=\"7\">", "</font>", TRUE); } else if (!g_ascii_strcasecmp(buf, "bold") || !g_ascii_strcasecmp(buf, "b")) { new_f = zframe_new_with_text("<b>", "</b>", TRUE); } else if (!g_ascii_strcasecmp(buf, "font")) { zframe *extra_f; extra_f = zframe_new("</font>", FALSE); extra_f->closer = frame->closer; g_queue_push_head(&frames, extra_f); new_f = zframe_new_with_text("<font face=\"", "\">", TRUE); } else if (!g_ascii_strcasecmp(buf, "color")) { zframe *extra_f; extra_f = zframe_new("</font>", FALSE); extra_f->closer = frame->closer; g_queue_push_head(&frames, extra_f); new_f = zframe_new_with_text("<font color=\"", "\">", TRUE); } else { new_f = zframe_new("", TRUE); } new_f->closer = closer; g_queue_push_head(&frames, new_f); g_free(buf); } else { /* Not a formatting tag, add the character as normal. */ g_string_append_c(frame->text, *message++); } } else if (frame->closer && purple_str_has_caseprefix(message, frame->closer)) { message += strlen(frame->closer); if (g_queue_get_length(&frames) > 1) { gboolean last_had_closer; do { zephyr_to_html_pop(&frames, &last_had_closer); } while (g_queue_get_length(&frames) > 1 && !last_had_closer); } else { g_string_append_c(frame->text, *message); } } else if (*message == '\n') { g_string_append(frame->text, "<br>"); message++; } else { g_string_append_c(frame->text, *message++); } } /* go through all the stuff that they didn't close */ while (g_queue_get_length(&frames) > 1) { zephyr_to_html_pop(&frames, NULL); } frame = (zframe *)g_queue_pop_head(&frames); ret = g_string_free(frame->text, FALSE); g_free(frame); return ret; }