joeydev/src/messages.c
2023-05-17 16:55:03 -05:00

221 lines
5.9 KiB
C

/*
* JoeyDev
* Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include <pthread.h>
#include "common.h"
#include "messages.h"
#include "utils.h"
static GtkWidget *_lstMessages = NULL;
static char **_pendingMessages = NULL;
static pthread_mutex_t _mtxMessage;
static gboolean messageScroll(gpointer userData);
static gboolean messagesUpdate(gpointer userData);
EVENT gboolean winMessagesClose(GtkWidget *object, gpointer userData);
static void winMessagesDelete(gpointer userData);
void message(MessageTypesT level, char *format, ...) {
va_list args;
char *string;
char *msg;
char *tok;
char *labels[MSG_COUNT] = {
"<span foreground=\"gray\"> Info:</span>",
"<span foreground=\"yellow\">Warning:</span>",
"<span foreground=\"red\"> Error:</span>",
"<span foreground=\"red\"><b> Severe:</b></span>"
};
// Construct message.
va_start(args, format);
msg = utilCreateStringVArgs(format, args);
va_end(args);
debug("%s\n", msg);
// Break multiline messages down into individual lines.
tok = strtok(msg, "\n");
while (tok != NULL) {
//***TODO*** Filter out things that could be mistaken as markup tags.
string = utilCreateString("<tt>%s %s</tt>", labels[level], tok);
// Save for UI thread. message() is not always called from the UI thread!
pthread_mutex_lock(&_mtxMessage);
arrput(_pendingMessages, string); // Do not free string.
pthread_mutex_unlock(&_mtxMessage);
tok = strtok(NULL, "\n");
}
DEL(msg);
}
static gboolean messageScroll(gpointer userData) {
GtkAdjustment *adjustment;
static int count = 100; // Try this many times to scroll.
(void)userData;
//***TODO*** This doesn't always scroll to the very end.
adjustment = gtk_list_box_get_adjustment(GTK_LIST_BOX(_lstMessages));
gtk_adjustment_set_value(adjustment, gtk_adjustment_get_upper(adjustment));
gtk_widget_show_all(_lstMessages);
utilForceUpdate();
count--;
if (count > 0) return G_SOURCE_CONTINUE;
count = 100;
return G_SOURCE_REMOVE;
}
void messageShutdown(void) {
g_idle_remove_by_data(messagesUpdate);
pthread_mutex_destroy(&_mtxMessage);
}
void messageStartup(void) {
if (pthread_mutex_init(&_mtxMessage, NULL) != 0) {
debug("Message mutex init failed!\n");
exit(1);
}
// Set up updates on the UI thread.
g_idle_add(messagesUpdate, messagesUpdate);
}
static gboolean messagesUpdate(gpointer userData) {
WindowDataT *self = NULL;
char *widgetNames[] = {
"winMessages",
"lstMessages",
NULL
};
GtkWidget **widgets[] = {
NULL,
&_lstMessages
};
GtkWidget *row;
GtkWidget *box;
GtkWidget *label;
char *string = NULL;
(void)userData;
// Lock and access the pending message list.
pthread_mutex_lock(&_mtxMessage);
if (arrlen(_pendingMessages) > 0) {
string = _pendingMessages[0]; // We just need the pointer.
arrdel(_pendingMessages, 0);
}
pthread_mutex_unlock(&_mtxMessage);
// Are there pending messages?
if (string != NULL) {
// Do we need to open the message window?
if (!_lstMessages) {
// Set up instance data. We only need WindowDataT since this is a "singleton" window.
self = NEW(WindowDataT);
self->closeWindow = winMessagesClose;
widgets[0] = &self->window;
utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/Messages.glade", widgetNames, widgets, self);
// Register window.
utilWindowRegister(self);
// Show window.
gtk_widget_show_all(self->window);
}
// Create new row with the message string.
row = gtk_list_box_row_new();
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
gtk_widget_set_hexpand(box, TRUE);
label = gtk_label_new(string);
gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
// Add new row to the message box.
gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
gtk_container_add(GTK_CONTAINER(row), box);
gtk_list_box_insert(GTK_LIST_BOX(_lstMessages), row, -1);
gtk_widget_show_all(_lstMessages);
utilForceUpdate();
// Scroll to show new row.
g_idle_add(messageScroll, NULL);
DEL(string); // Finally free the string allocated in message().
}
return G_SOURCE_CONTINUE;
}
EVENT void toolMessagesClearClicked(GtkWidget *widget, gpointer userData) {
GList *children;
GList *iter;
(void)widget;
(void)userData;
children = gtk_container_get_children(GTK_CONTAINER(_lstMessages));
for (iter = children; iter != NULL; iter = g_list_next(iter)) {
gtk_widget_destroy(GTK_WIDGET(iter->data));
}
g_list_free(children);
}
EVENT gboolean winMessagesClose(GtkWidget *object, gpointer userData) {
// userData is not reliable due to util indirectly calling us.
WindowDataT *self = (WindowDataT *)utilGetWindowData(object);
(void)userData;
winMessagesDelete(self);
return FALSE;
}
static void winMessagesDelete(gpointer userData) {
// Delete any pending messages.
while (arrlen(_pendingMessages) > 0) {
DEL(_pendingMessages[0]);
arrdel(_pendingMessages, 0);
}
//ARRFREE(_pendingMessages); // This is making Memwatch mad.
utilWindowUnRegister(userData);
_lstMessages = NULL;
DEL(userData);
}