diff options
Diffstat (limited to 'src/messagewindow.c')
-rw-r--r-- | src/messagewindow.c | 1854 |
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; + } + +} |