--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkWindow" id="ChatWindow">
+ <property name="can_focus">False</property>
+ <property name="default_width">400</property>
+ <property name="default_height">200</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Datei</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem1">
+ <property name="label">gtk-new</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem2">
+ <property name="label">gtk-open</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem3">
+ <property name="label">gtk-save</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem4">
+ <property name="label">gtk-save-as</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem5">
+ <property name="label">gtk-quit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Bearbeiten</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem6">
+ <property name="label">gtk-cut</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem7">
+ <property name="label">gtk-copy</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem8">
+ <property name="label">gtk-paste</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem9">
+ <property name="label">gtk-delete</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Ansicht</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Hilfe</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem10">
+ <property name="label">gtk-about</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTextView" id="ChatTextView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="wrap_mode">word</property>
+ <property name="cursor_visible">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="ChatEntry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
--- /dev/null
+#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;
+ GtkTextBuffer *buffer;
+ BOOL first;
+};
+
+static gboolean add_text(gpointer data)
+{
+ GtkTextIter endIter;
+ struct add_text_params *params = data;
+
+ if (OF_LIKELY(!params->first))
+ gtk_text_buffer_insert_at_cursor(params->buffer, "\n", 1);
+
+ gtk_text_buffer_get_end_iter(params->buffer, &endIter);
+ gtk_text_buffer_insert_with_tags_by_name(params->buffer, &endIter,
+ [params->name UTF8String], [params->name UTF8StringLength], "bold",
+ NULL);
+ gtk_text_buffer_get_end_iter(params->buffer, &endIter);
+ gtk_text_buffer_insert_with_tags_by_name(params->buffer, &endIter,
+ ": ", 2, "bold", NULL);
+
+ gtk_text_buffer_insert_at_cursor(params->buffer,
+ [params->text UTF8String], [params->text UTF8StringLength]);
+
+ [params->name release];
+ [params->text release];
+ g_object_unref(params->buffer);
+ free(params);
+
+ return FALSE;
+}
+
+static gboolean call_send_block(GtkEntry *entry, GdkEventKey *event,
+ gpointer data)
+{
+ if (event->keyval != GDK_KEY_Return) 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);
+ [text release];
+
+ return TRUE;
+}
+
+static gboolean add_tags(gpointer data) {
+ gtk_text_buffer_create_tag(data, "bold", "weight",
+ PANGO_WEIGHT_BOLD, NULL);
+
+ return FALSE;
+}
+
+@implementation JubGtkChatUI
+- initWithTitle: (OFString*)title
+ sendBlock: (jub_send_block_t)sendBlock_
+{
+ self = [super init];
+
+ @try {
+ GtkTextView *chat_view;
+ GtkEntry *chat_entry;
+ GtkBuilder *builder = gtk_builder_new();
+
+ 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);
+ params->title = [title retain];
+ g_idle_add(show_chat, params);
+
+ g_object_unref(builder);
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ gtk_widget_destroy(chat_window);
+ [sendBlock release];
+
+ [super dealloc];
+}
+
+- (void)addMessage: (OFString*)text
+ sender: (OFString*)sender;
+{
+ struct add_text_params *params = malloc(sizeof(*params));
+ params->name = [sender retain];
+ params->text = [text retain];
+ params->buffer = g_object_ref(chat_buffer);
+ params->first = bufferEmpty;
+ g_idle_add(add_text, params);
+ if (OF_UNLIKELY(bufferEmpty)) bufferEmpty = NO;
+}
+@end
#import "JubGtkRosterUI.h"
#import "JubGObjectMap.h"
+#import "JubGtkChatUI.h"
static gboolean filter_roster_by_presence(GtkTreeModel *model,
GtkTreeIter *iter, gpointer data)
initWithKeyFunctions: keyFunctions
valueFunctions: rowRefFunctions];
contactMap = [[OFMutableDictionary alloc] init];
+ chatMap = [[OFMutableDictionary alloc] init];
presences = [[OFCountedSet alloc] init];
builder = g_object_ref(builder_);
g_idle_add(refilter_roster, roster_filter);
}
+// FIXME: This needs to move somewhere else
+- (void)connection: (XMPPConnection*)connection
+ didReceiveMessage: (XMPPMessage*)message
+{
+ 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];
+
+ [chatMap setObject: chat
+ forKey: [message.from bareJID]];
+ }
+ [chat addMessage: message.body
+ sender: [message.from bareJID]];
+}
+
/* Roster Delegate methods */
struct add_roster_item_param {
OFString *group;