]> cgit.babelmonkeys.de Git - jubjub.git/blob - src/gui/gtk/JubGtkChatUI.m
Handle closing chat windows, fix a race and a retain cycle in the process
[jubjub.git] / src / gui / gtk / JubGtkChatUI.m
1 #import "JubGtkChatUI.h"
2
3 struct add_text_params {
4         OFString *name;
5         OFString *text;
6         GtkTextBuffer *buffer;
7         BOOL first;
8 };
9
10 static gboolean add_text(gpointer data)
11 {
12         GtkTextIter endIter;
13         struct add_text_params *params = data;
14
15         if (OF_LIKELY(!params->first))
16                 gtk_text_buffer_insert_at_cursor(params->buffer, "\n", 1);
17
18         gtk_text_buffer_get_end_iter(params->buffer, &endIter);
19         gtk_text_buffer_insert_with_tags_by_name(params->buffer, &endIter,
20             [params->name UTF8String], [params->name UTF8StringLength], "bold",
21             NULL);
22         gtk_text_buffer_get_end_iter(params->buffer, &endIter);
23         gtk_text_buffer_insert_with_tags_by_name(params->buffer, &endIter,
24             ": ", 2, "bold", NULL);
25
26         gtk_text_buffer_insert_at_cursor(params->buffer,
27             [params->text UTF8String], [params->text UTF8StringLength]);
28
29         [params->name release];
30         [params->text release];
31         g_object_unref(params->buffer);
32         free(params);
33
34         return FALSE;
35 }
36
37 struct call_send_block_params {
38         jub_send_block_t block;
39         JubGtkChatUI *chat;
40 };
41
42 static gboolean call_send_block(GtkEntry *entry, GdkEventKey *event,
43     gpointer data)
44 {
45         struct call_send_block_params *params = data;
46         if (event->keyval != GDK_KEY_Return &&
47             event->keyval != GDK_KEY_KP_Enter) return TRUE;
48
49         OFString *text =
50             [[OFString alloc] initWithUTF8String: gtk_entry_get_text(entry)];
51         gtk_entry_set_text(entry, "");
52         params->block(text);
53         [params->chat addMessage: text
54                           sender: @"You"];
55         [text release];
56
57         return TRUE;
58 }
59
60 static gboolean call_close_block(GtkWidget *object, gpointer data)
61 {
62         jub_close_block_t block = data;
63         block();
64
65         return FALSE;
66 }
67
68 struct init_params {
69         OFString *title;
70         JubGtkChatUI *object;
71         volatile BOOL *initialized;
72 };
73
74 static gboolean init(gpointer data)
75 {
76         struct init_params *params = data;
77
78         GtkTextView *chat_view;
79         GtkEntry *chat_entry;
80
81         GtkBuilder *builder = gtk_builder_new();
82         gtk_builder_add_from_file(builder, "data/gtk/chat.ui", NULL);
83
84         params->object.chat_window = GTK_WIDGET(
85             gtk_builder_get_object(builder, "ChatWindow"));
86         g_signal_connect(params->object.chat_window, "destroy",
87             G_CALLBACK(call_close_block), params->object.closeBlock);
88
89
90         chat_view = GTK_TEXT_VIEW(
91             gtk_builder_get_object(builder, "ChatTextView"));
92         params->object.chat_buffer = gtk_text_view_get_buffer(chat_view);
93         gtk_text_buffer_create_tag(params->object.chat_buffer, "bold",
94             "weight", PANGO_WEIGHT_BOLD, NULL);
95
96         chat_entry = GTK_ENTRY(
97             gtk_builder_get_object(builder, "ChatEntry"));
98         struct call_send_block_params *send_params =
99             [params->object allocMemoryWithSize: sizeof(*send_params)];
100         send_params->block = params->object.sendBlock;
101         send_params->chat = params->object;
102         g_signal_connect(chat_entry, "key_release_event",
103             G_CALLBACK(call_send_block), send_params);
104
105         gtk_window_set_title(GTK_WINDOW(params->object.chat_window),
106             [params->title UTF8String]);
107         gtk_widget_show(params->object.chat_window);
108
109         *params->initialized = YES;
110
111         g_object_unref(builder);
112         [params->title release];
113         free(params);
114
115         return FALSE;
116 }
117
118 @implementation JubGtkChatUI
119 @synthesize chat_window;
120 @synthesize chat_buffer;
121 @synthesize sendBlock;
122 @synthesize closeBlock;
123
124 - initWithTitle: (OFString*)title
125      closeBlock: (jub_close_block_t)closeBlock_
126       sendBlock: (jub_send_block_t)sendBlock_
127 {
128         volatile BOOL initialized = NO;
129         self = [super init];
130
131         @try {
132
133                 closeBlock = [closeBlock_ copy];
134                 sendBlock = [sendBlock_ copy];
135                 bufferEmpty = YES;
136
137                 struct init_params *params = malloc(sizeof(*params));
138                 params->initialized = &initialized;
139                 params->title = [title retain];
140                 params->object = self;
141                 g_idle_add(init, params);
142         } @catch (id e) {
143                 [self release];
144                 @throw e;
145         }
146
147         while (!initialized);
148
149         return self;
150 }
151
152 - (void)dealloc
153 {
154         gtk_widget_destroy(chat_window);
155         [sendBlock release];
156         [closeBlock release];
157
158         [super dealloc];
159 }
160
161 - (void)addMessage: (OFString*)text
162             sender: (OFString*)sender;
163 {
164         struct add_text_params *params = malloc(sizeof(*params));
165         params->name = [sender retain];
166         params->text = [text retain];
167         params->buffer = g_object_ref(chat_buffer);
168         params->first = bufferEmpty;
169         g_idle_add(add_text, params);
170         if (OF_UNLIKELY(bufferEmpty)) bufferEmpty = NO;
171 }
172 @end