/* * messagewindow.c * * IM window * * (c) 2002-2005 Thomas White * 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 #endif #include #include #include #include #include #include #include #include #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; inum_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), "Font and Colour for Your Messages"); 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), "Fonts and Colours for Contacts' Messages"); 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; } }