aboutsummaryrefslogtreecommitdiff
path: root/src/messagewindow.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/messagewindow.c')
-rw-r--r--src/messagewindow.c1854
1 files changed, 1854 insertions, 0 deletions
diff --git a/src/messagewindow.c b/src/messagewindow.c
new file mode 100644
index 0000000..ae0326d
--- /dev/null
+++ b/src/messagewindow.c
@@ -0,0 +1,1854 @@
+/*
+ * messagewindow.c
+ *
+ * IM window
+ *
+ * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org>
+ * Part of TuxMessenger - GTK+-based MSN Messenger client
+ *
+ * This package 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 dated June, 1991.
+ *
+ * This package 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 package; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <glob.h>
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "debug.h"
+#include "sbsessions.h"
+#include "contactlist.h"
+#include "routines.h"
+#include "avatars.h"
+#include "sbprotocol.h"
+#include "msnp2p.h"
+#include "about.h"
+#include "fonttrans.h"
+#include "messagewindow.h"
+#include "options.h"
+#include "mime.h"
+#include "filetrans.h"
+
+typedef enum {
+ MESSAGEWINDOW_MODE_TEXT,
+ MESSAGEWINDOW_MODE_INK
+} MessageWindowMode;
+
+static MessageWindow *messagewindows;
+
+static MessageWindow *messagewindow_last() {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ while ( messagewindow ) {
+ assert(messagewindow != NULL);
+ if ( messagewindow->next == NULL ) {
+ return messagewindow;
+ } else {
+ messagewindow = messagewindow->next;
+ }
+ }
+
+ return NULL; /* If there were no messagewindows at all. */
+
+}
+
+void messagewindow_plug(MessageWindow *messagewindow, SbSession *session) {
+ messagewindow->session = session;
+}
+
+/* Unplug any message windows which are plugged into a given SB session */
+void messagewindow_unplug(SbSession *session) {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ while ( messagewindow != NULL ) {
+ if ( messagewindow->session == session ) {
+ messagewindow->session = NULL;
+ }
+ messagewindow = messagewindow->next;
+ }
+
+}
+
+static void messagewindow_destroy(MessageWindow *messagewindow) {
+
+ MessageWindow *prev;
+ MwUser *mwuser;
+
+ prev = messagewindows;
+ if ( prev != messagewindow ) {
+ while ( prev != NULL ) {
+ assert(prev != NULL);
+ if ( prev->next == messagewindow ) {
+ break;
+ } else {
+ prev = prev->next;
+ }
+ }
+ assert(prev->next == messagewindow);
+ /* Link it out of the list. */
+ prev->next = messagewindow->next;
+ } else {
+ /* This session was the first on the list. */
+ assert(messagewindows == messagewindow); /* Can't fail... */
+ messagewindows = messagewindow->next; /* Which may be NULL if the list is now empty. */
+ }
+
+ mwuser = messagewindow->users;
+ while ( mwuser != NULL ) {
+
+ MwUser *next_user = mwuser->next;
+ free(mwuser->username);
+ free(mwuser);
+ mwuser = next_user;
+
+ }
+
+ if ( messagewindow->localcolour_gdk != NULL ) {
+ gdk_color_free(messagewindow->localcolour_gdk);
+ }
+ if ( messagewindow->localcolour_string != NULL ) {
+ free(messagewindow->localcolour_string);
+ }
+ if ( messagewindow->ocolour_gdk != NULL ) {
+ gdk_color_free(messagewindow->ocolour_gdk);
+ }
+ if ( messagewindow->ocolour_string != NULL ) {
+ free(messagewindow->ocolour_string);
+ }
+ if ( messagewindow->localfont != NULL ) {
+ free(messagewindow->localfont);
+ }
+ if ( messagewindow->dislocalfont != NULL ) {
+ free(messagewindow->dislocalfont);
+ }
+ if ( messagewindow->ofont != NULL ) {
+ free(messagewindow->ofont);
+ }
+
+ free(messagewindow);
+
+}
+
+/* Find an IM window with exactly one given user. */
+static MessageWindow *messagewindow_find_single(const char *username) {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ while ( messagewindow != NULL ) {
+ if ( (messagewindow->num_users == 1) && (strcmp(messagewindow->users->username, username) == 0) ) {
+ return messagewindow;
+ }
+ messagewindow = messagewindow->next;
+ }
+
+ return NULL;
+
+}
+
+static MwUser *messagewindow_find_username(MessageWindow *messagewindow, const char *username) {
+
+ MwUser *mwuser;
+
+ assert(messagewindow != NULL);
+
+ mwuser = messagewindow->users;
+ while ( mwuser != NULL ) {
+
+ assert(mwuser != NULL);
+
+ /* Case-insensitive here. Username may have different case depending
+ on its source, since the servers seem to change usernames to
+ lower case but clients might not in (e.g.) TypingUser controls. */
+ if ( strcasecmp(mwuser->username, username) == 0 ) {
+ return mwuser;
+ } else {
+ mwuser = mwuser->next;
+ }
+
+ }
+
+ /* No users. */
+ return NULL;
+
+}
+
+static MessageWindow *messagewindow_find_user(MwUser *mwuser) {
+
+ MwUser *find;
+ MessageWindow *messagewindow;
+
+ assert(mwuser != NULL);
+ messagewindow = messagewindows;
+
+ while ( messagewindow != NULL ) {
+
+ find = messagewindow->users;
+ while ( find != NULL ) {
+
+ assert(find != NULL);
+
+ if ( find == mwuser ) {
+ return messagewindow;
+ } else {
+ find = find->next;
+ }
+
+ }
+
+ messagewindow = messagewindow->next;
+
+ }
+
+ /* Not found. */
+ return NULL;
+
+}
+
+static MwUser *messagewindow_lastuser(MessageWindow *messagewindow) {
+
+ MwUser *mwuser;
+
+ mwuser = messagewindow->users;
+ while ( mwuser != NULL ) {
+
+ assert(mwuser != NULL);
+
+ if ( mwuser->next == NULL ) {
+ return mwuser;
+ } else {
+ mwuser = mwuser->next;
+ }
+
+ }
+
+ /* No users. */
+ return NULL;
+
+}
+
+/* User clicked "Nudge" button. */
+static void messagewindow_sendnudge(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ if ( messagewindow->session == NULL ) {
+
+ debug_print("MW %8p: Not plugged in - sorting out.\n", messagewindow);
+ assert(messagewindow->num_users == 1);
+ assert(messagewindow->users != NULL);
+ messagewindow_plug(messagewindow, sbsessions_create_local(messagewindow->users->username));
+ sbsessions_plug(messagewindow->session, messagewindow);
+
+ }
+
+ messagewindow_addtext_system(messagewindow, "You nudge.");
+ sbprotocol_sendnudge(messagewindow->session);
+
+}
+
+static void messagewindow_scrolltoend(MessageWindow *messagewindow) {
+
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages));
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ gtk_text_iter_set_line_offset(&iter, 0);
+ gtk_text_buffer_move_mark(buffer, messagewindow->mark, &iter);
+ gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(messagewindow->messages), messagewindow->mark, 0, TRUE, 1.0, 0.0);
+
+}
+
+static void messagewindow_size_allocate(GtkWidget *widget, GtkAllocation *allocation, MessageWindow *messagewindow) {
+
+ if ( messagewindow->stuck ) {
+ messagewindow_scrolltoend(messagewindow);
+ }
+
+}
+
+static void messagewindow_scrolled(GtkAdjustment *adjustment, MessageWindow *messagewindow) {
+
+ gdouble pos;
+ gdouble limit;
+ gdouble pagesize;
+
+ /* See if the window is currently scrolled to the bottom. Make a note for later. */
+ pos = gtk_adjustment_get_value(adjustment);
+ g_object_get(G_OBJECT(adjustment), "upper", &limit, "page-size", &pagesize, NULL);
+
+ if ( pos == limit-pagesize ) {
+ messagewindow->stuck = TRUE;
+ } else {
+ messagewindow->stuck = FALSE;
+ }
+
+}
+
+/* Add "System" text to a window (like join/part messages) */
+static void messagewindow_addtext_system_nonewline(MessageWindow *messagewindow, const char *text) {
+
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages));
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, text, -1, "system", NULL);
+
+ if ( messagewindow->stuck ) {
+ messagewindow_scrolltoend(messagewindow);
+ }
+
+}
+
+/* Add "System" text to a window (like join/part messages), adding a newline if this isn't the first text. */
+void messagewindow_addtext_system(MessageWindow *messagewindow, const char *text) {
+
+ /* Add PRECEDING newline if this isn't the first event. */
+ if ( messagewindow->first_event == 0 ) {
+ messagewindow_addtext_system_nonewline(messagewindow, "\n\n");
+ } else {
+ messagewindow->first_event = 0;
+ }
+
+ messagewindow_addtext_system_nonewline(messagewindow, text);
+
+ /* Assume this is the case. If this WAS a NAK, the caller should set it again straight AFTERWARDS. */
+ messagewindow_set_last_was_nak(messagewindow, FALSE);
+
+}
+
+/* Add "User" text to a window (user messages) */
+void messagewindow_addtext_user_nonewline(MessageWindow *messagewindow, const char *text, int length, const char *colour, const char *font) {
+
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ GtkTextTag *tag;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages));
+
+ if ( colour != NULL ) {
+ if ( font != NULL ) {
+ tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, "foreground", colour, "font", font, NULL);
+ } else {
+ tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, "foreground", colour, NULL);
+ }
+ } else {
+ if ( font != NULL ) {
+ tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, "font", font, NULL);
+ } else {
+ tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(buffer), NULL, "left_margin", 10, "right_margin", 10, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, NULL);
+ }
+ }
+
+ /* Window should have been opened by now. */
+ assert(messagewindow != NULL);
+
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ gtk_text_buffer_insert_with_tags(buffer, &iter, text, length, tag, NULL);
+
+ if ( messagewindow->stuck ) {
+ messagewindow_scrolltoend(messagewindow);
+ }
+
+/* if ( GTK_WINDOW(record->message_window)->window_has_focus != 1 )
+ messagewindow_flash(record); */
+
+}
+
+/* This isn't needed, since user text is never preceded by a blank line
+static void messagewindow_addtext_user(MessageWindow *messagewindow, char *text) {
+
+ if ( messagewindow->first_event == 0 ) {
+ messagewindow_addtext_user_nonewline(messagewindow, "\n\n");
+ } else {
+ messagewindow->first_event = 0;
+ }
+
+ messagewindow_addtext_user_nonewline(messagewindow, text);
+
+} */
+
+void messagewindow_reportdropped(MessageWindow *messagewindow, char *message, size_t length) {
+
+ const char *mime;
+
+ debug_print("Dropping message: '%s'\n", message);
+
+ messagewindow_addtext_system(messagewindow, "The following message was not sent:\n");
+
+ mime = strstr(message, "MIME-Version:");
+ assert(mime != NULL);
+
+ messagewindow_addtext_user_nonewline(messagewindow, mime_getbody(mime), strlen(mime_getbody(mime)), NULL, NULL);
+
+}
+
+void messagewindow_leavemessage(MessageWindow *messagewindow, const char *username) {
+
+ char *text;
+ char *friendlyname_decoded;
+ const char *friendlyname;
+
+ friendlyname = contactlist_friendlyname(username);
+ friendlyname_decoded = routines_urldecode(friendlyname);
+
+ text = malloc(strlen(username) + strlen(friendlyname_decoded) + 4);
+ assert(text != NULL);
+
+ strcpy(text, friendlyname_decoded);
+ strcat(text, " (");
+ strcat(text, username);
+ strcat(text, ")");
+
+ messagewindow_addtext_system(messagewindow, "<-- Leave: ");
+ messagewindow_addtext_system_nonewline(messagewindow, text);
+
+ free(text);
+ free(friendlyname_decoded);
+
+}
+
+void messagewindow_removeuser(MessageWindow *messagewindow, const char *username) {
+
+ MwUser *find_user;
+ MwUser *mwuser = messagewindow_find_username(messagewindow, username);
+
+ if ( messagewindow->num_users > 1 ) {
+ messagewindow_leavemessage(messagewindow, username);
+ }
+
+ find_user = messagewindow->users;
+ if ( find_user == mwuser ) {
+ /* User was the first in the list. */
+ messagewindow->users = mwuser->next;
+ } else {
+
+ while ( find_user != NULL ) {
+
+ assert(find_user != NULL);
+
+ if ( find_user->next == mwuser ) {
+ find_user->next = mwuser->next;
+ break;
+ } else {
+ find_user = find_user->next;
+ }
+
+ }
+ }
+
+ messagewindow->num_users--;
+ free(mwuser->username);
+ gtk_widget_destroy(mwuser->bar_hbox);
+ gtk_widget_destroy(mwuser->avatar_eventbox);
+
+ free(mwuser);
+
+}
+
+static char *messagewindow_statusbarstring(const char *username) {
+
+ char *text;
+ char *friendlyname_decoded;
+ const char *friendlyname;
+
+ friendlyname = contactlist_friendlyname(username);
+ friendlyname_decoded = routines_urldecode(friendlyname);
+
+ text = malloc(strlen(username) + strlen(friendlyname_decoded) + 4);
+ assert(text != NULL);
+
+ strcpy(text, friendlyname_decoded);
+ strcat(text, " (");
+ strcat(text, username);
+ strcat(text, ")");
+ free(friendlyname_decoded);
+
+ return text;
+
+}
+
+/* Set "User is typing" status. */
+void messagewindow_typing(MessageWindow *messagewindow, const char *username) {
+
+ char *string;
+ char *bigstring;
+ MwUser *mwuser = messagewindow_find_username(messagewindow, username);
+
+ assert(mwuser != NULL);
+
+ string = messagewindow_statusbarstring(username);
+ bigstring = malloc(strlen(string) + 10);
+ strcpy(bigstring, "(Typing) ");
+ strcat(bigstring, string);
+
+ gtk_label_set_text(GTK_LABEL(mwuser->bar), bigstring);
+ free(bigstring);
+ free(string);
+
+}
+
+/* Put friendlyname and username in status bar. */
+void messagewindow_resetstatusbar(MessageWindow *messagewindow, const char *username) {
+
+ char *text;
+ MwUser *mwuser = messagewindow_find_username(messagewindow, username);
+
+ text = messagewindow_statusbarstring(username);
+ gtk_label_set_text(GTK_LABEL(mwuser->bar), text);
+ free(text);
+
+}
+
+static void messagewindow_settextmode(MessageWindow *messagewindow) {
+ gtk_widget_show(messagewindow->textbox_hbox);
+ gtk_widget_hide(messagewindow->gtk_ink);
+}
+
+static void messagewindow_setinkmode(MessageWindow *messagewindow) {
+ gtk_widget_hide(messagewindow->textbox_hbox);
+ gtk_widget_show(messagewindow->gtk_ink);
+}
+
+/* User clicked "Ink" button. */
+static void messagewindow_ink(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ GtkAction *action;
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) {
+ /* Ink mode on */
+ messagewindow_setinkmode(messagewindow);
+ action = gtk_action_group_get_action(messagewindow->action_group, "ModeInkAction");
+ } else {
+ /* Ink mode off */
+ messagewindow_settextmode(messagewindow);
+ action = gtk_action_group_get_action(messagewindow->action_group, "ModeTextAction");
+ }
+
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+
+}
+
+static void messagewindow_setmode(GtkRadioAction *action, GtkRadioAction *current, MessageWindow *messagewindow) {
+
+ MessageWindowMode mode = gtk_radio_action_get_current_value(action);
+
+ if ( mode == MESSAGEWINDOW_MODE_TEXT ) {
+ messagewindow_settextmode(messagewindow);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(messagewindow->ink_button), FALSE);
+ } else {
+ messagewindow_setinkmode(messagewindow);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(messagewindow->ink_button), TRUE);
+ }
+
+}
+
+static void messagewindow_sendfilesel(GtkDialog *dialog, gint response, MessageWindow *messagewindow) {
+
+ if ( response == GTK_RESPONSE_ACCEPT ) {
+
+ char *filename;
+ unsigned int i;
+ MwUser *user;
+ char *usernames[messagewindow->num_users];
+
+ filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+ debug_print("Sending file '%s'\n", filename);
+
+ user = messagewindow->users;
+ for ( i=0; i<messagewindow->num_users; i++ ) {
+ usernames[i] = user->username;
+ user = user->next;
+ }
+ filetrans_offer_multiple(filename, usernames, messagewindow->num_users);
+
+ g_free(filename);
+
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+
+}
+
+/* User clicked "Send File" button. */
+static void messagewindow_sendfile(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ GtkWidget *dialog;
+
+ dialog = gtk_file_chooser_dialog_new("Send File", GTK_WINDOW(messagewindow->window), GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ g_signal_connect(G_OBJECT(dialog), "response", GTK_SIGNAL_FUNC(messagewindow_sendfilesel), messagewindow);
+
+
+ gtk_widget_show(dialog);
+
+}
+
+/* Called when it's time to send whatever's in the text entry widget of a message window. */
+static void messagewindow_send(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gchar *textblock;
+ GtkTextIter start;
+ GtkTextIter end;
+ GtkTextBuffer *buffer;
+ int length;
+ char *colour;
+ char *hash_colour;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->textbox));
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+
+ textblock = gtk_text_iter_get_text(&start, &end);
+ length = strlen(textblock);
+
+ if ( messagewindow->session == NULL ) {
+ messagewindow_plug(messagewindow, sbsessions_create_local(messagewindow->users->username));
+ sbsessions_plug(messagewindow->session, messagewindow);
+ }
+ /* Message might get cached for sending later. */
+ sbprotocol_send(messagewindow->session, textblock, length);
+
+ if ( strlen(textblock) > 0 ) {
+ messagewindow_addtext_system(messagewindow, "You say:\n");
+ if ( messagewindow->localcolour_string != NULL ) {
+ colour = routines_flipcolour(messagewindow->localcolour_string);
+ hash_colour = malloc(8);
+ strcpy(hash_colour, "#");
+ strncat(hash_colour, colour, 6);
+ hash_colour[7] = '\0';
+ messagewindow_addtext_user_nonewline(messagewindow, textblock, length, hash_colour, messagewindow->dislocalfont);
+ free(hash_colour);
+ free(colour);
+ } else {
+ messagewindow_addtext_user_nonewline(messagewindow, textblock, length, NULL, NULL);
+ }
+ }
+
+ /* Empty the text entry widget */
+ gtk_text_buffer_set_text(buffer, "", -1);
+ g_free(textblock);
+
+}
+
+void messagewindow_am_typing(MessageWindow *messagewindow) {
+
+ if ( messagewindow->session == NULL ) {
+
+ debug_print("MW %8p: Not plugged in - not sending TypingUser.\n", messagewindow);
+ if ( messagewindow->num_users == 1 ) {
+ assert(messagewindow->users != NULL);
+ messagewindow_plug(messagewindow, sbsessions_create_local(messagewindow->users->username));
+ sbsessions_plug(messagewindow->session, messagewindow);
+ }
+ return;
+
+ }
+
+ if ( sbsessions_sessionready(messagewindow->session) ) {
+ sbsessions_am_typing(messagewindow->session);
+ } else {
+ debug_print("MW %8p: Session %p isn't ready - not sending TypingUser.\n", messagewindow, messagewindow->session);
+ }
+
+}
+
+/* Called when a key is pressed inside the text entry widget of a message window. */
+static gint messagewindow_key(GtkWidget *ignore1, GdkEventKey *event, MessageWindow *messagewindow) {
+
+ if ( (event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter) ) {
+
+ /* Send the message */
+ messagewindow_send(NULL, messagewindow);
+ return TRUE; /* Don't process further. */
+
+ } else {
+
+ /* YUK */
+ if ( (event->keyval != GDK_Shift_L) && (event->keyval != GDK_Shift_R)
+ && (event->keyval != GDK_Control_L) && (event->keyval != GDK_Control_R)
+ && (event->keyval != GDK_Alt_L) && (event->keyval != GDK_Alt_R)
+ && (event->keyval != GDK_Meta_L) && (event->keyval != GDK_Meta_R)
+ && (event->keyval != GDK_Shift_L) && (event->keyval != GDK_Shift_R) ) {
+
+ messagewindow_am_typing(messagewindow);
+
+ }
+
+ }
+
+ return FALSE;
+
+}
+
+/* Create or update the picture associated with a user. username=NULL means the local user's picture. */
+static void messagewindow_newpicture(MessageWindow *messagewindow, const char *username, const char *filename) {
+
+ GtkWidget *pixmap_widget;
+ GdkPixbuf *pixbuf;
+ MwUser *user;
+ GtkTooltips *tooltip;
+ GtkWidget *event_box;
+
+ int local_image;
+ int changing_image;
+
+ user = NULL;
+ if ( username != NULL ) {
+
+ user = messagewindow_find_username(messagewindow, username);
+ if ( user == NULL ) {
+ debug_print("MW %8p: Couldn't find user data!\n", messagewindow);
+ return;
+ }
+
+ }
+
+ if ( username == NULL ) {
+ local_image = TRUE;
+ } else {
+ local_image = FALSE;
+ }
+
+ /* Get rid of any previous display picture we may have */
+ if ( local_image == FALSE ) {
+
+ if ( user->avatar != NULL ) {
+
+ gtk_widget_destroy(user->avatar);
+ gdk_pixbuf_unref(user->avatar_pixbuf);
+ changing_image = TRUE;
+ user->avatar = NULL;
+ user->avatar_pixbuf = NULL;
+
+ } else {
+ changing_image = FALSE;
+ }
+
+ } else {
+
+ if ( messagewindow->avatar != NULL ) {
+
+ gtk_widget_destroy(messagewindow->avatar);
+ gdk_pixbuf_unref(messagewindow->avatar_pixbuf);
+ messagewindow->avatar = NULL;
+ messagewindow->avatar_pixbuf = NULL;
+ changing_image = TRUE;
+
+ } else {
+ changing_image = FALSE;
+ }
+
+ }
+
+ pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+
+ if ( pixbuf == NULL ) {
+
+ debug_print("MW %8p: Couldn't load user display image - ", messagewindow);
+
+ /* Check if the picture's in the process of being downloaded... */
+ if ( !msnp2p_retrieving(filename) ) {
+
+ /* Delete the file, and re-download it... */
+ if ( remove(filename) == 0 ) {
+ debug_print("deleted it.\n");
+ messagewindow_picturekick(username);
+ return;
+ } else {
+ debug_print("failed to delete it, too. Noooo...\n");
+ return;
+ }
+
+ } else {
+ debug_print("download in progress. Not worrying...\n");
+ }
+
+ return;
+
+ }
+
+ pixmap_widget = gtk_image_new_from_pixbuf(pixbuf);
+
+ if ( changing_image == FALSE ) {
+ event_box = gtk_event_box_new();
+ } else {
+
+ if ( local_image == FALSE ) {
+ event_box = user->avatar_eventbox;
+ } else {
+ event_box = messagewindow->avatar_eventbox;
+ }
+
+ }
+
+ gtk_container_add(GTK_CONTAINER(event_box), pixmap_widget);
+
+ if ( changing_image == FALSE ) {
+
+ if ( username != NULL ) {
+ gtk_box_pack_start(GTK_BOX(messagewindow->picture_list), event_box, FALSE, FALSE, 0);
+ } else {
+ gtk_box_pack_end(GTK_BOX(messagewindow->picture_list), event_box, FALSE, FALSE, 0);
+ }
+
+ tooltip = gtk_tooltips_new();
+ gtk_tooltips_set_tip(tooltip, event_box, username, NULL);
+
+ }
+
+ if ( !local_image ) {
+
+ user->avatar_eventbox = event_box;
+ user->avatar = pixmap_widget;
+ user->avatar_pixbuf = pixbuf;
+
+ } else {
+
+ messagewindow->avatar_eventbox = event_box;
+ messagewindow->avatar = pixmap_widget;
+ messagewindow->avatar_pixbuf = pixbuf;
+
+ }
+
+ gtk_widget_show_all(event_box);
+
+}
+
+/* Decide what to do in order to eventually end up with an avatar for a remote user. */
+void messagewindow_trypicture(MessageWindow *messagewindow, const char *username) {
+
+ const char *dpobject;
+
+ dpobject = contactlist_haspicture(username);
+ if ( (dpobject != NULL) && (strlen(dpobject) > 0) ) {
+
+ char *avatar_filename;
+ avatar_filename = avatars_havepicture(dpobject);
+ if ( avatar_filename != NULL ) {
+
+ debug_print("MW %8p: Already have picture.\n", messagewindow);
+ messagewindow_newpicture(messagewindow, username, avatar_filename);
+
+ } else {
+
+ char *filename;
+
+ debug_print("MW %8p: Setting \"Wait\" picture.\n", messagewindow);
+ filename = avatars_default_fetching();
+ messagewindow_newpicture(messagewindow, username, filename);
+ free(filename);
+
+ if ( (messagewindow->session == NULL) && (!messagewindow->in_creation) ) {
+ messagewindow_plug(messagewindow, sbsessions_create_local(username));
+ sbsessions_plug(messagewindow->session, messagewindow);
+ } else {
+ if ( sbsessions_sessionready(messagewindow->session) ) {
+ msnp2p_getpicture(messagewindow->session, username, dpobject);
+ } else {
+ debug_print("MW %8p: Session isn't ready, but will be...\n", messagewindow);
+ }
+ }
+
+ }
+ free(avatar_filename);
+
+ } else {
+ char *avatars_blank = avatars_default_none();
+ debug_print("MW %8p: Setting \"Blank\" picture.\n", messagewindow);
+ messagewindow_newpicture(messagewindow, username, avatars_blank);
+ free(avatars_blank);
+ }
+
+}
+
+void messagewindow_localpicture(MessageWindow *messagewindow) {
+
+ char *filename;
+
+ filename = avatars_local();
+ if ( filename == NULL ) {
+ filename = DATADIR"/tuxmessenger/no_avatar.png";
+ messagewindow_newpicture(messagewindow, NULL, filename);
+ } else {
+ messagewindow_newpicture(messagewindow, NULL, filename);
+ free(filename);
+ }
+
+}
+
+/* Find all instances of the given user in all SB sessions, and reload the picture for them.
+ NULL means kick the local user's picture. */
+void messagewindow_picturekick(const char *username) {
+
+ MessageWindow *messagewindow;
+
+ messagewindow = messagewindows;
+ while ( messagewindow != NULL ) {
+
+ if ( username != NULL ) {
+
+ /* See if the user in question is in this session. */
+ MwUser *user = messagewindow_find_username(messagewindow, username);
+ if ( user != NULL ) {
+ messagewindow_trypicture(messagewindow, username);
+ }
+
+ } else {
+
+ messagewindow_localpicture(messagewindow);
+
+ }
+
+ messagewindow = messagewindow->next;
+
+ }
+
+}
+
+static int messagewindow_close(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gtk_widget_destroy(messagewindow->window);
+ return FALSE;
+
+}
+
+static int messagewindow_toggleavatars(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gboolean active;
+ GtkAction *action;
+
+ action = gtk_action_group_get_action(messagewindow->action_group, "AvatarsAction");
+ assert(action != NULL);
+ active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+
+ if ( active ) {
+ /* Show avatars */
+ gtk_widget_show(messagewindow->picture_list);
+ } else {
+ /* Hide avatars */
+ gtk_widget_hide(messagewindow->picture_list);
+ }
+
+ return FALSE;
+
+}
+
+static int messagewindow_toggleircstyle(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gboolean active;
+ GtkAction *action;
+
+ action = gtk_action_group_get_action(messagewindow->action_group, "IRCStyleAction");
+ assert(action != NULL);
+ active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+
+ if ( active ) {
+ /* Use IRC style */
+ messagewindow->ircstyle = TRUE;
+ } else {
+ /* Use normal style */
+ messagewindow->ircstyle = FALSE;
+ }
+
+ return FALSE;
+
+}
+
+static int messagewindow_toggletimestamps(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gboolean active;
+ GtkAction *action;
+
+ action = gtk_action_group_get_action(messagewindow->action_group, "TimeStampAction");
+ assert(action != NULL);
+ active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+
+ if ( active ) {
+ /* Show timestamps */
+ messagewindow->timestamps = TRUE;
+ } else {
+ /* Don't show timestamps */
+ messagewindow->timestamps = FALSE;
+ }
+
+ return FALSE;
+
+}
+
+static int messagewindow_toggleemoticons(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ gboolean active;
+ GtkAction *action;
+
+ action = gtk_action_group_get_action(messagewindow->action_group, "EmoticonsAction");
+ assert(action != NULL);
+ active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+
+ if ( active ) {
+ /* Show emoticons */
+ messagewindow->showemoticons = TRUE;
+ } else {
+ /* Don't show emoticons */
+ messagewindow->showemoticons = FALSE;
+ }
+
+ return FALSE;
+
+}
+
+static void messagewindow_closed(MessageWindow *messagewindow) {
+
+ /* If this is a multi-user conversation, it'd be polite to at least try to leave it... */
+ if ( messagewindow->session != NULL ) {
+ if ( messagewindow->session->num_users > 1 ) {
+ debug_print("MW %8p: Closing a multi-way conversation. Sending OUT...\n", messagewindow);
+ sbprotocol_leavesession(messagewindow->session);
+ }
+ }
+
+ sbsessions_unplug(messagewindow);
+ messagewindow_destroy(messagewindow);
+
+}
+
+static void messagewindow_lcolsel(GtkWidget *colourbutton, MessageWindow *messagewindow) {
+
+ GdkColor colour;
+ char *string;
+
+ gtk_color_button_get_color(GTK_COLOR_BUTTON(colourbutton), &colour);
+
+ if ( messagewindow->localcolour_gdk != NULL ) {
+ gdk_color_free(messagewindow->localcolour_gdk);
+ }
+ if ( messagewindow->localcolour_string != NULL ) {
+ free(messagewindow->localcolour_string);
+ }
+
+ messagewindow->localcolour_gdk = gdk_color_copy(&colour);
+
+ /* Now work out the string version */
+ string = malloc(7);
+ /* Yukky BGR order instead of RGB */
+ snprintf(string, 7, "%02hhx%02hhx%02hhx", colour.blue >> 8, colour.green >> 8, colour.red >> 8);
+ messagewindow->localcolour_string = string;
+ debug_print("MW %8p: String value '%s'\n", messagewindow, string);
+
+}
+
+static void messagewindow_lfontsel(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ const char *font;
+
+ if ( messagewindow->dislocalfont != NULL ) {
+ free(messagewindow->dislocalfont);
+ }
+ if ( messagewindow->localfont != NULL ) {
+ free(messagewindow->localfont);
+ }
+ font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget));
+ messagewindow->dislocalfont = strdup(font);
+ messagewindow->localfont = fonttrans_font_to_format(font);
+
+}
+
+static void messagewindow_ofontsel(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ const char *font;
+
+ if ( messagewindow->ofont != NULL ) {
+ free(messagewindow->ofont);
+ }
+ font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget));
+ messagewindow->ofont = strdup(font);
+
+}
+
+static void messagewindow_ocolsel(GtkWidget *colourbutton, MessageWindow *messagewindow) {
+
+ GdkColor colour;
+
+ gtk_color_button_get_color(GTK_COLOR_BUTTON(colourbutton), &colour);
+
+ if ( messagewindow->ocolour_gdk != NULL ) {
+ gdk_color_free(messagewindow->ocolour_gdk);
+ }
+
+ if ( messagewindow->ocolour_string != NULL ) {
+ free(messagewindow->ocolour_string);
+ }
+ messagewindow->ocolour_gdk = gdk_color_copy(&colour);
+ messagewindow->ocolour_string = routines_gdk_to_hashrgb(&colour);
+
+}
+
+void messagewindow_fontsdialog_closed(GtkWidget *widget, MessageWindow *messagewindow) {
+ messagewindow->fontsdialog = NULL;
+}
+
+static void messagewindow_ofontoverride_toggle(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ) {
+ messagewindow->ofontoverride = TRUE;
+ gtk_widget_set_sensitive(messagewindow->ofont_button, TRUE);
+ gtk_widget_set_sensitive(messagewindow->ocolour_button, TRUE);
+ } else {
+ messagewindow->ofontoverride = FALSE;
+ gtk_widget_set_sensitive(messagewindow->ofont_button, FALSE);
+ gtk_widget_set_sensitive(messagewindow->ocolour_button, FALSE);
+ }
+
+}
+
+void messagewindow_openfontsdialog(GtkWidget *widget, MessageWindow *messagewindow) {
+
+ GtkWidget *dialog_box;
+
+ GtkWidget *fbox;
+ GtkWidget *font_label;
+ GtkWidget *font_label_justify;
+ GtkWidget *font_vbox;
+ GtkWidget *font_hbox;
+ GtkWidget *font_box;
+ GtkWidget *font_button;
+ GtkWidget *colour_button;
+ GtkWidget *nbox;
+ GtkWidget *ofont_label;
+ GtkWidget *ofont_label_justify;
+ GtkWidget *ofont_vbox;
+ GtkWidget *ofont_hbox;
+ GtkWidget *ofont_override;
+ GtkWidget *ofont_box;
+
+ if ( messagewindow->fontsdialog != NULL ) {
+ return;
+ }
+
+ messagewindow->fontsdialog = gtk_dialog_new_with_buttons("Message Fonts", GTK_WINDOW(messagewindow->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL);
+ dialog_box = GTK_DIALOG(messagewindow->fontsdialog)->vbox;
+
+ fbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(dialog_box), fbox, FALSE, FALSE, 12);
+ font_label = gtk_label_new("");
+ font_label_justify = gtk_hbox_new(FALSE, 0);
+ gtk_label_set_markup(GTK_LABEL(font_label), "<span weight=\"bold\">Font and Colour for Your Messages</span>");
+ gtk_box_pack_start(GTK_BOX(font_label_justify), font_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(fbox), font_label_justify, FALSE, FALSE, 6);
+
+ font_vbox = gtk_vbox_new(FALSE, 0);
+ font_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(fbox), font_hbox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(font_hbox), font_vbox, FALSE, FALSE, 12);
+
+ font_box = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(font_vbox), font_box, FALSE, FALSE, 0);
+ if ( messagewindow->dislocalfont == NULL ) {
+ font_button = gtk_font_button_new();
+ } else {
+ font_button = gtk_font_button_new_with_font(messagewindow->dislocalfont);
+ }
+ g_signal_connect(G_OBJECT(font_button), "font-set", GTK_SIGNAL_FUNC(messagewindow_lfontsel), messagewindow);
+ gtk_box_pack_start(GTK_BOX(font_box), font_button, FALSE, FALSE, 6);
+ if ( messagewindow->localcolour_gdk == NULL ) {
+ colour_button = gtk_color_button_new();
+ } else {
+ colour_button = gtk_color_button_new_with_color(messagewindow->localcolour_gdk);
+ }
+ g_signal_connect(G_OBJECT(colour_button), "color-set", GTK_SIGNAL_FUNC(messagewindow_lcolsel), messagewindow);
+ gtk_box_pack_start(GTK_BOX(font_box), colour_button, FALSE, FALSE, 6);
+
+ nbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(dialog_box), nbox, FALSE, FALSE, 12);
+ ofont_label = gtk_label_new("");
+ ofont_label_justify = gtk_hbox_new(FALSE, 0);
+ gtk_label_set_markup(GTK_LABEL(ofont_label), "<span weight=\"bold\">Fonts and Colours for Contacts' Messages</span>");
+ gtk_box_pack_start(GTK_BOX(ofont_label_justify), ofont_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(nbox), ofont_label_justify, FALSE, FALSE, 0);
+
+ ofont_vbox = gtk_vbox_new(FALSE, 0);
+ ofont_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(nbox), ofont_hbox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(ofont_hbox), ofont_vbox, FALSE, FALSE, 12);
+
+ ofont_override = gtk_check_button_new_with_label("Override contacts' chosen fonts");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ofont_override), messagewindow->ofontoverride);
+ g_signal_connect(G_OBJECT(ofont_override), "toggled", GTK_SIGNAL_FUNC(messagewindow_ofontoverride_toggle), messagewindow);
+ gtk_box_pack_start(GTK_BOX(ofont_vbox), ofont_override, FALSE, FALSE, 6);
+ ofont_box = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(ofont_vbox), ofont_box, FALSE, FALSE, 0);
+ if ( messagewindow->ofont ) {
+ messagewindow->ofont_button = gtk_font_button_new_with_font(messagewindow->ofont);
+ } else {
+ messagewindow->ofont_button = gtk_font_button_new();
+ }
+ g_signal_connect(G_OBJECT(messagewindow->ofont_button), "font-set", GTK_SIGNAL_FUNC(messagewindow_ofontsel), messagewindow);
+ gtk_box_pack_start(GTK_BOX(ofont_box), messagewindow->ofont_button, FALSE, FALSE, 6);
+ if ( messagewindow->ocolour_gdk == NULL ) {
+ messagewindow->ocolour_button = gtk_color_button_new();
+ } else {
+ messagewindow->ocolour_button = gtk_color_button_new_with_color(messagewindow->ocolour_gdk);
+ }
+ g_signal_connect(G_OBJECT(messagewindow->ocolour_button), "color-set", GTK_SIGNAL_FUNC(messagewindow_ocolsel), messagewindow);
+ gtk_box_pack_start(GTK_BOX(ofont_box), messagewindow->ocolour_button, FALSE, FALSE, 6);
+ messagewindow_ofontoverride_toggle(ofont_override, messagewindow);
+
+ g_signal_connect(G_OBJECT(messagewindow->fontsdialog), "destroy", GTK_SIGNAL_FUNC(messagewindow_fontsdialog_closed), messagewindow);
+ g_signal_connect(G_OBJECT(messagewindow->fontsdialog), "response", GTK_SIGNAL_FUNC(gtk_widget_destroy), NULL);
+ gtk_widget_show_all(messagewindow->fontsdialog);
+
+}
+
+static void messagewindow_userdnd_receive(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *seldata, guint info, guint time, MessageWindow *messagewindow) {
+
+ char *username;
+
+ username = seldata->data;
+
+ if ( messagewindow_find_username(messagewindow, username) != NULL ) {
+ debug_print("MW %8p: '%s' is already in this message window.\n", messagewindow, username);
+ return;
+ }
+
+ if ( messagewindow->session != NULL ) {
+ debug_print("MW %8p: Inviting '%s'...\n", messagewindow, username);
+ sbprotocol_invite(messagewindow->session, username);
+ } else {
+ assert(messagewindow->num_users == 1);
+ debug_print("MW %8p: Creating session and inviting '%s' and '%s'...\n", messagewindow, messagewindow->users->username, username);
+ messagewindow->session = sbsessions_create_threeway(messagewindow->users->username, username);
+ }
+
+}
+
+static void messagewindow_addui_callback(GtkUIManager *ui, GtkWidget *widget, GtkContainer *container) {
+
+ gtk_box_pack_start(GTK_BOX(container), widget, FALSE, FALSE, 0);
+
+ /* Enable overflow menu if this is a toolbar */
+ if ( GTK_IS_TOOLBAR(widget) ) {
+ gtk_toolbar_set_show_arrow(GTK_TOOLBAR(widget), TRUE);
+ }
+
+}
+
+static void messagewindow_addmenubar(MessageWindow *messagewindow) {
+
+ GtkActionEntry entries[] = {
+
+ { "ConversationAction", NULL, "_Conversation", NULL, NULL, NULL },
+ { "InviteAction", GTK_STOCK_ADD, "_Invite New User...", NULL, NULL, NULL },
+ { "CloseAction", GTK_STOCK_CLOSE, "_Close", NULL, NULL, G_CALLBACK(messagewindow_close) },
+
+ { "ViewAction", NULL, "_View", NULL, NULL, NULL },
+ { "FontAction", GTK_STOCK_SELECT_FONT, "_Set Fonts and Colours...", NULL, NULL, G_CALLBACK(messagewindow_openfontsdialog) },
+ /* More in toggles[] */
+
+ { "ToolsAction", NULL, "_Tools", NULL, NULL, NULL },
+ { "BlockAction", GTK_STOCK_NO, "_Block User", NULL, NULL, NULL },
+ { "NudgeAction", NULL, "_Nudge", NULL, NULL, G_CALLBACK(messagewindow_sendnudge) },
+ #ifdef HAVE_GTK_2_6_0
+ { "SendFileAction", GTK_STOCK_FILE, "Send a _File...", NULL, NULL, NULL },
+ { "AboutAction", GTK_STOCK_ABOUT, "_About TuxMessenger", NULL, NULL, G_CALLBACK(about_open) },
+ #else /* HAVE_GTK_2_6_0 */
+ { "SendFileAction", NULL, "Send a _File...", NULL, NULL, NULL },
+ { "AboutAction", NULL, "_About TuxMessenger", NULL, NULL, G_CALLBACK(about_open) },
+ #endif /* HAVE_GTK_2_6_0 */
+
+ { "HelpAction", NULL, "_Help", NULL, NULL, NULL },
+
+ };
+ GtkToggleActionEntry toggles[] = {
+
+ { "AvatarsAction", NULL, "Display _Avatars", NULL, NULL, G_CALLBACK(messagewindow_toggleavatars), TRUE },
+ { "IRCStyleAction", NULL, "_IRC Style", NULL, NULL, G_CALLBACK(messagewindow_toggleircstyle), FALSE },
+ { "TimeStampAction", NULL, "_Timestamp Messages", NULL, NULL, G_CALLBACK(messagewindow_toggletimestamps), FALSE },
+ { "EmoticonsAction", NULL, "Enable _Emoticons", NULL, NULL, G_CALLBACK(messagewindow_toggleemoticons), FALSE },
+
+ };
+ GtkRadioActionEntry radios_mode[] = {
+
+ { "ModeTextAction", NULL, "_Text Mode", NULL, NULL, MESSAGEWINDOW_MODE_TEXT },
+ { "ModeInkAction", NULL, "In_k Mode", NULL, NULL, MESSAGEWINDOW_MODE_INK },
+
+ };
+
+ guint n_entries = G_N_ELEMENTS(entries);
+ guint n_toggles = G_N_ELEMENTS(toggles);
+ guint n_radios_mode = G_N_ELEMENTS(radios_mode);
+ GError *error = NULL;
+
+ messagewindow->action_group = gtk_action_group_new("TuxMessengerIMWindow");
+ gtk_action_group_add_actions(messagewindow->action_group, entries, n_entries, messagewindow);
+ gtk_action_group_add_toggle_actions(messagewindow->action_group, toggles, n_toggles, messagewindow);
+ gtk_action_group_add_radio_actions(messagewindow->action_group, radios_mode, n_radios_mode, MESSAGEWINDOW_MODE_TEXT, G_CALLBACK(messagewindow_setmode), messagewindow);
+
+ messagewindow->ui = gtk_ui_manager_new();
+ gtk_ui_manager_insert_action_group(messagewindow->ui, messagewindow->action_group, 0);
+ g_signal_connect(messagewindow->ui, "add_widget", G_CALLBACK(messagewindow_addui_callback), messagewindow->bigvbox);
+ if ( gtk_ui_manager_add_ui_from_file(messagewindow->ui, DATADIR"/tuxmessenger/imwindow.ui", &error) == 0 ) {
+ debug_print("MW %8p: Error loading message window menu bar: %s\n", messagewindow, error->message);
+ return;
+ }
+
+ gtk_window_add_accel_group(GTK_WINDOW(messagewindow->window), gtk_ui_manager_get_accel_group(messagewindow->ui));
+ gtk_ui_manager_ensure_update(messagewindow->ui);
+
+}
+
+static MwUser *messagewindow_adduser(MessageWindow *messagewindow, const char *username) {
+
+ MwUser *mwuser;
+ MwUser *previous_user;
+
+ mwuser = malloc(sizeof(MwUser));
+
+ mwuser->username = strdup(username);
+
+ mwuser->avatar_eventbox = NULL;
+ mwuser->avatar = NULL;
+ mwuser->avatar_pixbuf = NULL;
+ mwuser->bar = gtk_label_new("");
+ mwuser->bar_hbox = gtk_hbox_new(FALSE, 0);
+ mwuser->typing_callback = 0;
+
+ gtk_widget_set_usize(mwuser->bar_hbox, 10, -1);
+ gtk_box_pack_start(GTK_BOX(mwuser->bar_hbox), mwuser->bar, FALSE, FALSE, 0);
+ gtk_box_pack_end(GTK_BOX(messagewindow->windowbox), mwuser->bar_hbox, FALSE, TRUE, 2);
+
+ gtk_widget_show(mwuser->bar);
+ gtk_widget_show(mwuser->bar_hbox);
+
+ previous_user = messagewindow_lastuser(messagewindow);
+ mwuser->next = NULL;
+ if ( previous_user == NULL ) {
+ messagewindow->users = mwuser;
+ } else {
+ previous_user->next = mwuser;
+ }
+
+ messagewindow->num_users++;
+
+ messagewindow_resetstatusbar(messagewindow, username);
+ messagewindow_trypicture(messagewindow, username);
+
+ return mwuser;
+
+}
+
+int messagewindow_get_last_was_nak(MessageWindow *messagewindow) {
+ return messagewindow->last_was_nak;
+}
+
+void messagewindow_set_last_was_nak(MessageWindow *messagewindow, int last_was_nak) {
+ messagewindow->last_was_nak = last_was_nak;
+}
+
+MessageWindow *messagewindow_create(const char *username, SbSession *session) {
+
+ MessageWindow *messagewindow;
+
+ GtkWidget *messagelist_hbox;
+ GtkWidget *messagelist_scroll;
+ GtkWidget *textbox_scroll;
+ GtkWidget *textbox_hbox;
+ GtkWidget *button_box;
+ GtkWidget *picture_hbox;
+ GtkAdjustment *adjustment;
+
+ GtkWidget *send_button;
+ GtkWidget *sendfile_button;
+ GtkWidget *nudge_button;
+ GdkColor bgcolour;
+ GdkColor fgcolour;
+
+ GdkColor colour;
+ GtkTextBuffer *buffer;
+ GtkTextIter start;
+ GtkTextIter end;
+ GtkTextIter iter;
+ char *friendlyname_decoded;
+ GtkTargetEntry targets[1];
+
+ MessageWindow *last_messagewindow;
+
+ messagewindow = malloc(sizeof(MessageWindow));
+ messagewindow->users = NULL;
+ messagewindow->num_users = 0;
+ messagewindow->avatar = NULL;
+ messagewindow->avatar_eventbox = NULL;
+ messagewindow->avatar_pixbuf = NULL;
+ messagewindow->session = NULL;
+ messagewindow->in_creation = TRUE;
+ messagewindow->first_event = TRUE;
+ messagewindow->fontsdialog = NULL;
+ messagewindow->stuck = TRUE;
+
+ if ( options_localcolour_gdk() != NULL ) {
+ messagewindow->localcolour_gdk = gdk_color_copy(options_localcolour_gdk());
+ } else {
+ messagewindow->localcolour_gdk = NULL;
+ }
+ if ( options_localcolour_string() != NULL ) {
+ messagewindow->localcolour_string = strdup(options_localcolour_string());
+ } else {
+ messagewindow->localcolour_string = NULL;
+ }
+ if ( options_ocolour_gdk() != NULL ) {
+ messagewindow->ocolour_gdk = gdk_color_copy(options_ocolour_gdk());
+ messagewindow->ocolour_string = routines_gdk_to_hashrgb(messagewindow->ocolour_gdk);
+ } else {
+ messagewindow->ocolour_gdk = NULL;
+ messagewindow->ocolour_string = NULL;
+ }
+ if ( options_localfont() != NULL ) {
+ messagewindow->localfont = fonttrans_font_to_format(options_localfont());
+ messagewindow->dislocalfont = strdup(options_localfont());
+ } else {
+ messagewindow->localfont = NULL;
+ messagewindow->dislocalfont = NULL;
+ }
+ if ( options_ofont() != NULL ) {
+ messagewindow->ofont = strdup(options_ofont());
+ } else {
+ messagewindow->ofont = NULL;
+ }
+ messagewindow->ofontoverride = options_ofontoverride();
+ messagewindow->ircstyle = options_ircstyle();
+ messagewindow->timestamps = options_timestamps();
+ messagewindow->showemoticons = options_showemoticons();
+ messagewindow->last_was_nak = FALSE;
+
+ debug_print("MW %8p: Created for '%s'\n", messagewindow, username);
+
+ /* Prepare the window. */
+ messagewindow->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_signal_connect_object(GTK_OBJECT(messagewindow->window), "destroy", GTK_SIGNAL_FUNC(messagewindow_closed), (gpointer)messagewindow);
+ gtk_window_set_default_size(GTK_WINDOW(messagewindow->window), 360, 340);
+
+ /* Set window title. */
+ friendlyname_decoded = routines_urldecode(contactlist_friendlyname(username));
+ gtk_window_set_title(GTK_WINDOW(messagewindow->window), friendlyname_decoded);
+ free(friendlyname_decoded);
+
+ /* Top-level structure. */
+ messagewindow->bigvbox = gtk_vbox_new(FALSE, 0);
+ messagewindow->windowbox = gtk_vbox_new(FALSE, 0);
+ gtk_widget_show(messagewindow->windowbox);
+ picture_hbox = gtk_hbox_new(FALSE, 2);
+ gtk_container_set_border_width(GTK_CONTAINER(picture_hbox), 2);
+ gtk_container_add(GTK_CONTAINER(messagewindow->window), messagewindow->bigvbox);
+ gtk_box_pack_end(GTK_BOX(messagewindow->bigvbox), picture_hbox, TRUE, TRUE, 0);
+ gtk_widget_show(picture_hbox);
+ gtk_widget_show(messagewindow->bigvbox);
+
+ messagewindow_addmenubar(messagewindow);
+
+ /* Create the box for past messages. */
+ messagewindow->messages = gtk_text_view_new();
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(messagewindow->messages), GTK_WRAP_WORD);
+ messagelist_hbox = gtk_hbox_new(FALSE, 0);
+ messagelist_scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(messagelist_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_container_add(GTK_CONTAINER(messagelist_scroll), GTK_WIDGET(messagewindow->messages));
+ gtk_box_pack_start(GTK_BOX(messagelist_hbox), messagelist_scroll, TRUE, TRUE, 0);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(messagelist_scroll), GTK_SHADOW_IN);
+ gdk_color_parse("white", &colour);
+ gtk_widget_modify_bg(messagewindow->messages, GTK_STATE_NORMAL, &colour);
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->messages));
+ gtk_text_buffer_create_tag(buffer, "system", "left_margin", 3, "right_margin", 3, "weight", PANGO_WEIGHT_BOLD, "size", 9*PANGO_SCALE, NULL);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(messagewindow->messages), FALSE);
+ gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), messagelist_hbox, TRUE, TRUE, 0);
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ messagewindow->mark = gtk_text_buffer_create_mark(buffer, NULL, &iter, FALSE);
+ gtk_widget_show(messagewindow->messages);
+ gtk_widget_show(messagelist_hbox);
+ gtk_widget_show(messagelist_scroll);
+ GTK_WIDGET_UNSET_FLAGS(messagewindow->messages, GTK_CAN_FOCUS);
+
+ /* Now add a few buttons for the user to control the session with */
+ button_box = gtk_hbox_new(FALSE, 0);
+ sendfile_button = gtk_button_new_with_label("Send File");
+ messagewindow->ink_button = gtk_toggle_button_new_with_label("Ink");
+ nudge_button = gtk_button_new_with_label("Nudge");
+ send_button = gtk_button_new_with_label("Send");
+ gtk_box_pack_start(GTK_BOX(button_box), messagewindow->ink_button, FALSE, FALSE, 5);
+ gtk_box_pack_start(GTK_BOX(button_box), sendfile_button, FALSE, FALSE, 5);
+ gtk_box_pack_end(GTK_BOX(button_box), send_button, FALSE, FALSE, 5);
+ gtk_box_pack_end(GTK_BOX(button_box), nudge_button, FALSE, FALSE, 5);
+ gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), button_box, FALSE, FALSE, 5);
+ gtk_widget_show(button_box);
+ gtk_widget_show(messagewindow->ink_button);
+ gtk_widget_show(sendfile_button);
+ gtk_widget_show(send_button);
+ gtk_widget_show(nudge_button);
+
+ /* Create the box into which the user will type */
+ messagewindow->textbox = gtk_text_view_new();
+ textbox_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(messagewindow->textbox), GTK_WRAP_WORD);
+ textbox_scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(textbox_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_container_add(GTK_CONTAINER(textbox_scroll), GTK_WIDGET(messagewindow->textbox));
+ gtk_box_pack_start(GTK_BOX(textbox_hbox), textbox_scroll, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), textbox_hbox, FALSE, FALSE, 0);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(textbox_scroll), GTK_SHADOW_IN);
+ gdk_color_parse("white", &colour);
+ gtk_widget_modify_bg(messagewindow->textbox, GTK_STATE_NORMAL, &colour);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(messagewindow->textbox), TRUE);
+ g_signal_connect(messagewindow->textbox, "key-press-event", GTK_SIGNAL_FUNC(messagewindow_key), messagewindow);
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messagewindow->textbox));
+ gtk_text_buffer_create_tag(buffer, "normal", "left_margin", 3, "right_margin", 3, "weight", PANGO_WEIGHT_NORMAL, "size", 9*PANGO_SCALE, NULL);
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ gtk_text_buffer_apply_tag_by_name(buffer, "normal", &start, &end);
+ gtk_widget_show(messagewindow->textbox);
+ gtk_widget_show(textbox_hbox);
+ gtk_widget_show(textbox_scroll);
+
+ messagewindow->textbox_hbox = textbox_hbox;
+ gdk_color_parse("#FFFFFF", &bgcolour);
+ gdk_color_parse("#100080", &fgcolour);
+ messagewindow->gtk_ink = gtk_ink_new(GTK_INK_FLAG_EDITABLE | GTK_INK_FLAG_PAPER, ink_new(), &fgcolour, &bgcolour);
+ gtk_box_pack_start(GTK_BOX(messagewindow->windowbox), messagewindow->gtk_ink, FALSE, FALSE, 0);
+ /* But don't gtk_widget_show() it yet! */
+
+ gtk_widget_set_size_request(messagewindow->gtk_ink, -1, 80);
+ gtk_widget_set_size_request(messagewindow->textbox_hbox, -1, 80);
+
+ gtk_signal_connect(GTK_OBJECT(send_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_send), messagewindow);
+ gtk_signal_connect(GTK_OBJECT(messagewindow->ink_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_ink), messagewindow);
+ gtk_signal_connect(GTK_OBJECT(sendfile_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_sendfile), messagewindow);
+ gtk_signal_connect(GTK_OBJECT(nudge_button), "clicked", GTK_SIGNAL_FUNC(messagewindow_sendnudge), messagewindow);
+
+ messagewindow->picture_list = gtk_vbox_new(FALSE, 5);
+ gtk_widget_set_usize(GTK_WIDGET(messagewindow->picture_list), 96, 0);
+ gtk_box_pack_start(GTK_BOX(picture_hbox), messagewindow->windowbox, TRUE, TRUE, 0);
+ gtk_box_pack_end(GTK_BOX(picture_hbox), messagewindow->picture_list, FALSE, FALSE, 0);
+ gtk_widget_show(messagewindow->picture_list);
+
+ /* Drag and Drop */
+ targets[0].target = "tm_username";
+ targets[0].flags = GTK_TARGET_SAME_APP;
+ targets[0].info = 1;
+ gtk_drag_dest_set(messagewindow->window, GTK_DEST_DEFAULT_ALL, targets, 1, GDK_ACTION_COPY);
+ g_signal_connect(messagewindow->window, "drag-data-received", GTK_SIGNAL_FUNC(messagewindow_userdnd_receive), (gpointer)messagewindow);
+
+ g_signal_connect(messagewindow->window, "size-allocate", GTK_SIGNAL_FUNC(messagewindow_size_allocate), messagewindow);
+ adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(messagelist_scroll));
+ g_signal_connect(adjustment, "value-changed", GTK_SIGNAL_FUNC(messagewindow_scrolled), messagewindow);
+
+ if ( !options_showavatars() ) {
+ GtkAction *action;
+ action = gtk_action_group_get_action(messagewindow->action_group, "AvatarsAction");
+ assert(action != NULL);
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), FALSE);
+ gtk_widget_hide(messagewindow->picture_list);
+ }
+
+ if ( messagewindow->ircstyle ) {
+ GtkAction *action;
+ action = gtk_action_group_get_action(messagewindow->action_group, "IRCStyleAction");
+ assert(action != NULL);
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+ }
+
+ if ( messagewindow->timestamps ) {
+ GtkAction *action;
+ action = gtk_action_group_get_action(messagewindow->action_group, "TimeStampAction");
+ assert(action != NULL);
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+ }
+
+ if ( messagewindow->showemoticons ) {
+ GtkAction *action;
+ action = gtk_action_group_get_action(messagewindow->action_group, "EmoticonsAction");
+ assert(action != NULL);
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+ }
+
+ gtk_widget_show(messagewindow->window); /* Slow */
+ gtk_widget_grab_focus(messagewindow->textbox);
+
+ /* Link into list */
+ last_messagewindow = messagewindow_last();
+ if ( last_messagewindow != NULL ) {
+ assert(last_messagewindow->next == NULL);
+ last_messagewindow->next = messagewindow;
+ } else {
+ messagewindows = messagewindow;
+ }
+ messagewindow->next = NULL;
+
+ if ( session != NULL ) {
+
+ debug_print("MW %8p: Plugging into SB session %p.\n", messagewindow, session);
+ sbsessions_plug(session, messagewindow);
+ messagewindow_plug(messagewindow, session);
+ messagewindow->in_creation = FALSE; /* Fudge! */
+
+ } else {
+
+ if ( (session = sbsessions_find_headless(username)) == NULL ) {
+ debug_print("MW %8p: Creating new SB session.\n", messagewindow);
+ messagewindow_plug(messagewindow, sbsessions_create_local(username));
+ } else {
+ debug_print("MW %8p: Found headless session %p.\n", messagewindow, session);
+ messagewindow_plug(messagewindow, session);
+ messagewindow->in_creation = FALSE; /* Fudge! */
+ }
+ sbsessions_plug(messagewindow->session, messagewindow);
+
+ }
+
+ /* Introduce new and local users. */
+ messagewindow_adduser(messagewindow, username);
+ messagewindow_localpicture(messagewindow);
+
+ messagewindow->in_creation = FALSE;
+ return messagewindow;
+
+}
+
+void messagewindow_create_if_none(const char *username, SbSession *session) {
+
+ MessageWindow *messagewindow = messagewindow_find_single(username);
+
+ if ( messagewindow ) {
+ debug_print("MW %8p: Already have IM window for %s.\n", messagewindow, username);
+ } else {
+ messagewindow_create(username, session);
+ }
+
+}
+
+/* Deal with a user joining a switchboard */
+void messagewindow_joined(MessageWindow *messagewindow, const char *username) {
+
+ MwUser *mwuser = messagewindow_find_username(messagewindow, username);
+
+ if ( mwuser == NULL ) {
+ mwuser = messagewindow_adduser(messagewindow, username);
+ } else {
+ messagewindow_resetstatusbar(messagewindow, username); /* Put Friendlyname (Username) in status bar. */
+ }
+
+ /* Only display message if this isn't the first user. */
+ if ( messagewindow->session->num_users > 1 ) {
+
+ char *text;
+
+ /* Remember, newlines go BEFORE the text in the window. */
+ messagewindow_addtext_system(messagewindow, "--> Join: ");
+ text = messagewindow_statusbarstring(username);
+ messagewindow_addtext_system_nonewline(messagewindow, text);
+
+ free(text);
+
+ }
+
+ messagewindow_trypicture(messagewindow, mwuser->username);
+
+}
+
+/* Called when an attempt is made to write to an IM window which doesn't exist. */
+void messagewindow_mitigate(SbSession *session) {
+
+ MessageWindow *messagewindow = NULL;
+ SbUser *user;
+
+ if ( session->num_users == 1 ) {
+ messagewindow = messagewindow_find_single(session->users->username);
+ if ( messagewindow == NULL ) {
+ messagewindow = messagewindow_create(session->users->username, session);
+ }
+ } else {
+ /* If there's more than one user, there's no chance of window reuse. */
+ messagewindow = messagewindow_create(session->users->username, session);
+ }
+
+ /* IM window favours most recently 'mitigated' session. */
+ messagewindow_plug(messagewindow, session);
+ /* More than one session can report to one IM window. */
+ sbsessions_plug(session, messagewindow);
+
+ user = session->users;
+ while ( user != NULL ) {
+ debug_print("MW %8p: Adding '%s'\n", messagewindow, user->username);
+ messagewindow_joined(messagewindow, user->username);
+ user = user->next;
+ }
+
+}
+
+static void messagewindow_disable(MessageWindow *messagewindow) {
+
+ GtkWidget *menuitem;
+
+ gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->textbox), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->gtk_ink), FALSE);
+
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/conversation/invite_user");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/block_user");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/send_file");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/nudge");
+ gtk_widget_set_sensitive(menuitem, FALSE);
+
+}
+
+static void messagewindow_enable(MessageWindow *messagewindow) {
+
+ GtkWidget *menuitem;
+
+ gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->textbox), TRUE);
+ gtk_widget_set_sensitive(GTK_WIDGET(messagewindow->gtk_ink), TRUE);
+
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/conversation/invite_user");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/block_user");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/send_file");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+ menuitem = gtk_ui_manager_get_widget(messagewindow->ui, "/imwindow/tools/nudge");
+ gtk_widget_set_sensitive(menuitem, TRUE);
+
+}
+
+void messagewindow_disable_all() {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ debug_print("MW %8p: disabling all windows..\n", NULL);
+
+ while ( messagewindow != NULL ) {
+ messagewindow_disable(messagewindow);
+ messagewindow = messagewindow->next;
+ }
+
+}
+
+void messagewindow_enable_all() {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ debug_print("MW %8p: enabling all windows..\n", NULL);
+
+ while ( messagewindow != NULL ) {
+ messagewindow_enable(messagewindow);
+ messagewindow = messagewindow->next;
+ }
+
+
+}
+
+int messagewindow_stoptyping(MwUser *user) {
+
+ MessageWindow *messagewindow;
+
+ /* Careful. By the time this callback gets called, the user or window might not exist. */
+ if ( ( messagewindow = messagewindow_find_user(user)) != NULL ) {
+
+ if ( user->typing_callback != 0 ) {
+ gtk_timeout_remove(user->typing_callback);
+ user->typing_callback = 0;
+ }
+
+ messagewindow_resetstatusbar(messagewindow, user->username);
+ user->typing_callback = 0;
+
+ }
+
+ return FALSE;
+
+}
+
+void messagewindow_stoptypingbyusername(MessageWindow *messagewindow, const char *username) {
+ MwUser *user = messagewindow_find_username(messagewindow, username);
+ messagewindow_stoptyping(user);
+}
+
+void messagewindow_starttyping(MessageWindow *messagewindow, const char *username) {
+
+ MwUser *user;
+
+ user = messagewindow_find_username(messagewindow, username);
+ if ( user != NULL ) {
+
+ messagewindow_typing(messagewindow, username);
+ if ( user->typing_callback != 0 ) {
+ gtk_timeout_remove(user->typing_callback);
+ user->typing_callback = 0;
+ }
+ user->typing_callback = gtk_timeout_add(6000, (GtkFunction)messagewindow_stoptyping, user);
+
+ }
+
+}
+
+void messagewindow_notifyoffline(const char *username) {
+
+ MessageWindow *messagewindow = messagewindows;
+
+ while ( messagewindow != NULL ) {
+ MwUser *user = messagewindow->users;
+ while ( user != NULL ) {
+ if ( strcmp(user->username, username) == 0) {
+
+ const char *fname;
+ char *friendlyname;
+ char *message;
+
+ fname = contactlist_friendlyname(username);
+ if ( fname == NULL ) {
+ friendlyname = malloc(strlen(username)+1);
+ strcpy(friendlyname, username);
+ } else {
+ friendlyname = routines_urldecode(fname);
+ }
+
+ message = malloc(strlen(friendlyname)+19);
+
+ debug_print("MW %8p: '%s' went offline.\n", messagewindow, username);
+
+ strcpy(message, "*** ");
+ strcat(message, friendlyname);
+ strcat(message, " went offline.");
+ messagewindow_addtext_system(messagewindow, message);
+ free(message);
+ free(friendlyname);
+
+ }
+ user = user->next;
+
+ }
+ messagewindow = messagewindow->next;
+ }
+
+}