From af5efc909e9807220021bfebb9029f6cb4956856 Mon Sep 17 00:00:00 2001 From: Florian Zeitz Date: Mon, 24 Dec 2012 01:43:07 +0100 Subject: [PATCH] Handle closing chat windows, fix a race and a retain cycle in the process --- src/gui/gtk/JubGtkChatUI.h | 9 ++- src/gui/gtk/JubGtkChatUI.m | 131 +++++++++++++++++++++-------------- src/gui/gtk/JubGtkRosterUI.m | 31 +++++---- 3 files changed, 106 insertions(+), 65 deletions(-) diff --git a/src/gui/gtk/JubGtkChatUI.h b/src/gui/gtk/JubGtkChatUI.h index 5c0addb..1c35681 100644 --- a/src/gui/gtk/JubGtkChatUI.h +++ b/src/gui/gtk/JubGtkChatUI.h @@ -3,17 +3,24 @@ #include typedef void (^jub_send_block_t)(OFString *); +typedef void (^jub_close_block_t)(void); @interface JubGtkChatUI: OFObject { GtkWidget *chat_window; GtkTextBuffer *chat_buffer; jub_send_block_t sendBlock; + jub_close_block_t closeBlock; BOOL bufferEmpty; } +@property (assign) GtkWidget *chat_window; +@property (assign) GtkTextBuffer * chat_buffer; +@property (readonly) jub_send_block_t sendBlock; +@property (readonly) jub_close_block_t closeBlock; - initWithTitle: (OFString*)title - sendBlock: (jub_send_block_t)sendBlock_; + closeBlock: (jub_close_block_t)closeBlock + sendBlock: (jub_send_block_t)sendBlock; - (void)addMessage: (OFString*)text sender: (OFString*)sender; diff --git a/src/gui/gtk/JubGtkChatUI.m b/src/gui/gtk/JubGtkChatUI.m index 1dd1155..b1fc930 100644 --- a/src/gui/gtk/JubGtkChatUI.m +++ b/src/gui/gtk/JubGtkChatUI.m @@ -1,25 +1,5 @@ #import "JubGtkChatUI.h" -struct show_chat_params { - GtkWidget *window; - OFString *title; -}; - -static gboolean show_chat(gpointer data) -{ - struct show_chat_params *params = data; - - gtk_window_set_title(GTK_WINDOW(params->window), - [params->title UTF8String]); - gtk_widget_show(params->window); - - g_object_unref(params->window); - [params->title release]; - free(params); - - return FALSE; -} - struct add_text_params { OFString *name; OFString *text; @@ -54,70 +34,118 @@ static gboolean add_text(gpointer data) return FALSE; } +struct call_send_block_params { + jub_send_block_t block; + JubGtkChatUI *chat; +}; + static gboolean call_send_block(GtkEntry *entry, GdkEventKey *event, gpointer data) { - if (event->keyval != GDK_KEY_Return) return TRUE; + struct call_send_block_params *params = data; + if (event->keyval != GDK_KEY_Return && + event->keyval != GDK_KEY_KP_Enter) return TRUE; - jub_send_block_t block = data; OFString *text = [[OFString alloc] initWithUTF8String: gtk_entry_get_text(entry)]; gtk_entry_set_text(entry, ""); - block(text); + params->block(text); + [params->chat addMessage: text + sender: @"You"]; [text release]; return TRUE; } -static gboolean add_tags(gpointer data) { - gtk_text_buffer_create_tag(data, "bold", "weight", - PANGO_WEIGHT_BOLD, NULL); +static gboolean call_close_block(GtkWidget *object, gpointer data) +{ + jub_close_block_t block = data; + block(); + + return FALSE; +} + +struct init_params { + OFString *title; + JubGtkChatUI *object; + volatile BOOL *initialized; +}; + +static gboolean init(gpointer data) +{ + struct init_params *params = data; + + GtkTextView *chat_view; + GtkEntry *chat_entry; + + GtkBuilder *builder = gtk_builder_new(); + gtk_builder_add_from_file(builder, "data/gtk/chat.ui", NULL); + + params->object.chat_window = GTK_WIDGET( + gtk_builder_get_object(builder, "ChatWindow")); + g_signal_connect(params->object.chat_window, "destroy", + G_CALLBACK(call_close_block), params->object.closeBlock); + + + chat_view = GTK_TEXT_VIEW( + gtk_builder_get_object(builder, "ChatTextView")); + params->object.chat_buffer = gtk_text_view_get_buffer(chat_view); + gtk_text_buffer_create_tag(params->object.chat_buffer, "bold", + "weight", PANGO_WEIGHT_BOLD, NULL); + + chat_entry = GTK_ENTRY( + gtk_builder_get_object(builder, "ChatEntry")); + struct call_send_block_params *send_params = + [params->object allocMemoryWithSize: sizeof(*send_params)]; + send_params->block = params->object.sendBlock; + send_params->chat = params->object; + g_signal_connect(chat_entry, "key_release_event", + G_CALLBACK(call_send_block), send_params); + + gtk_window_set_title(GTK_WINDOW(params->object.chat_window), + [params->title UTF8String]); + gtk_widget_show(params->object.chat_window); + + *params->initialized = YES; + + g_object_unref(builder); + [params->title release]; + free(params); return FALSE; } @implementation JubGtkChatUI +@synthesize chat_window; +@synthesize chat_buffer; +@synthesize sendBlock; +@synthesize closeBlock; + - initWithTitle: (OFString*)title + closeBlock: (jub_close_block_t)closeBlock_ sendBlock: (jub_send_block_t)sendBlock_ { + volatile BOOL initialized = NO; self = [super init]; @try { - GtkTextView *chat_view; - GtkEntry *chat_entry; - GtkBuilder *builder = gtk_builder_new(); + closeBlock = [closeBlock_ copy]; sendBlock = [sendBlock_ copy]; bufferEmpty = YES; - gtk_builder_add_from_file(builder, "data/gtk/chat.ui", NULL); - - chat_window = GTK_WIDGET( - gtk_builder_get_object(builder, "ChatWindow")); - - chat_view = GTK_TEXT_VIEW( - gtk_builder_get_object(builder, "ChatTextView")); - - chat_entry = GTK_ENTRY( - gtk_builder_get_object(builder, "ChatEntry")); - - g_signal_connect(chat_entry, "key_release_event", - G_CALLBACK(call_send_block), sendBlock); - - chat_buffer = gtk_text_view_get_buffer(chat_view); - g_idle_add(add_tags, chat_buffer); - - struct show_chat_params *params = malloc(sizeof(*params)); - params->window = g_object_ref(chat_window); + struct init_params *params = malloc(sizeof(*params)); + params->initialized = &initialized; params->title = [title retain]; - g_idle_add(show_chat, params); - - g_object_unref(builder); + params->object = self; + g_idle_add(init, params); } @catch (id e) { [self release]; @throw e; } + while (!initialized); + return self; } @@ -125,6 +153,7 @@ static gboolean add_tags(gpointer data) { { gtk_widget_destroy(chat_window); [sendBlock release]; + [closeBlock release]; [super dealloc]; } diff --git a/src/gui/gtk/JubGtkRosterUI.m b/src/gui/gtk/JubGtkRosterUI.m index 9a2da29..c5163e1 100644 --- a/src/gui/gtk/JubGtkRosterUI.m +++ b/src/gui/gtk/JubGtkRosterUI.m @@ -100,26 +100,31 @@ static gboolean refilter_roster(gpointer data) - (void)connection: (XMPPConnection*)connection didReceiveMessage: (XMPPMessage*)message { - JubGtkChatUI *chat = [chatMap objectForKey: [message.from bareJID]]; + JubGtkChatUI *chat = + [chatMap objectForKey: [message.from bareJID]]; if (chat == nil) { OFString * title = [@"Chat with " stringByAppendingString: [message.from bareJID]]; - chat = [JubGtkChatUI alloc]; - [[chat initWithTitle: title - sendBlock: ^(OFString *text) { - XMPPMessage *msg = - [XMPPMessage messageWithType: @"chat"]; - msg.to = message.from; - msg.body = text; - [connection sendStanza: msg]; - - [chat addMessage: msg.body - sender: [message.to bareJID]]; - }] autorelease]; + + chat = [[[JubGtkChatUI alloc] + initWithTitle: title + closeBlock: ^{ + [chatMap removeObjectForKey: + [message.from bareJID]]; + } + sendBlock: ^(OFString *text) { + XMPPMessage *msg = + [XMPPMessage messageWithType: @"chat"]; + msg.to = message.from; + msg.body = text; + [connection sendStanza: msg]; + } + ] autorelease]; [chatMap setObject: chat forKey: [message.from bareJID]]; } + [chat addMessage: message.body sender: [message.from bareJID]]; } -- 2.39.2