#include #include #include "limox.h" struct chat { GtkWidget* roster_item; // the button in the roster list GtkWidget* chat_layout_box; // the layout box of the chat page GtkWidget* chat_content_box; // the content box of the chat page GtkWidget* text_entry; // where new messages are typed struct chat* next; // entry to support the linked list }; // linked list of chats static struct chat* chats = NULL; // the GTK application is available as global variable static GtkApplication* app; // all GTK widgets are accessible in this file via global variables static GtkWidget* window; static GtkWidget* stack; // connector page static GtkStackPage* connector_page; static GtkWidget* connector_box; static GtkWidget* connector_jid_label; static GtkWidget* connector_jid_entry; static GtkWidget* connector_pwd_label; static GtkWidget* connector_pwd_entry; static GtkWidget* connector_button; // roster page static GtkStackPage* roster_page; static GtkWidget* roster_layout_box; static GtkWidget* roster_scrolled; static GtkWidget* roster_content_box; static GtkWidget* roster_button; static void quit_cb(void) { limox_quit(); g_application_quit(G_APPLICATION(app)); } static void connect_cb(void) { const char* jid_text = gtk_editable_get_text(GTK_EDITABLE(connector_jid_entry)); const char* pwd_text = gtk_editable_get_text(GTK_EDITABLE(connector_pwd_entry)); gtk_stack_set_visible_child(GTK_STACK(stack), roster_layout_box); // just dummy output printf("Connecting with:\nJID: %s\nPWD: %s\n", jid_text, pwd_text); limox_connect(jid_text, pwd_text); } static void disconnect_cb(void) { gtk_stack_set_visible_child(GTK_STACK(stack), connector_box); // just dummy output printf("Disconnected!\n"); limox_disconnect(); } static void to_chat(GtkWidget* layout_box) { gtk_stack_set_visible_child(GTK_STACK(stack), layout_box); } static void to_roster(void) { gtk_stack_set_visible_child(GTK_STACK(stack), roster_layout_box); } void send_message(struct chat* chat) { // get recipient and message text const char* recipient = gtk_button_get_label(GTK_BUTTON(chat->roster_item)); const char* text = gtk_editable_get_text(GTK_EDITABLE(chat->text_entry)); // execute dummy XMPP send TODO printf("Sending to %s:\n> %s\n", recipient, text); // add message content to the chat GtkWidget* message = gtk_label_new(text); gtk_box_append(GTK_BOX(chat->chat_content_box), message); // clear text input GtkEntryBuffer* empty_buffer = gtk_entry_buffer_new("", 0); gtk_entry_set_buffer(GTK_ENTRY(chat->text_entry), empty_buffer); } void add_chat(char* jid) { // create chat struct and initialize struct chat* chat = malloc(sizeof(struct chat)); chat->roster_item = gtk_button_new_with_label(jid); chat->chat_layout_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20); chat->chat_content_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); chat->text_entry = gtk_entry_new(); chat->next = NULL; // add chat page to stack gtk_stack_add_child(GTK_STACK(stack), chat->chat_layout_box); // add a button to go back to roster GtkWidget* back = gtk_button_new_with_label("back"); gtk_box_append(GTK_BOX(chat->chat_layout_box), back); g_signal_connect_swapped(back, "clicked", G_CALLBACK(to_roster), NULL); // add a scrolled window to chat page GtkWidget* scrolled = gtk_scrolled_window_new(); gtk_widget_set_vexpand(scrolled, TRUE); gtk_box_append(GTK_BOX(chat->chat_layout_box), scrolled); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled), chat->chat_content_box); // add text entry and send button to chat page gtk_box_append(GTK_BOX(chat->chat_layout_box), chat->text_entry); GtkWidget* send_button = gtk_button_new_with_label("send"); gtk_box_append(GTK_BOX(chat->chat_layout_box), send_button); g_signal_connect_swapped(send_button, "clicked", G_CALLBACK(send_message), chat); // add chat to roster list gtk_box_append(GTK_BOX(roster_content_box), chat->roster_item); g_signal_connect_swapped(chat->roster_item, "clicked", G_CALLBACK(to_chat), chat->chat_layout_box); // append chat to linked list of chats struct chat* current = chats; while (true) { if (current == NULL) { chats = chat; break; } else if (current->next == NULL) { current->next = chat; break; } else { current = current->next; } } } void add_incoming_text_message(char* sender_jid, char* content) { // do not add message if list of chats is empty if (chats == NULL) { return; } // find chat with corresponding JID struct chat* chat = chats; while (true) { const char* jid = gtk_button_get_label(GTK_BUTTON(chat->roster_item)); if (strcmp(jid, sender_jid) == 0) { break; // this seems to be the correct chat } else if (chat->next == NULL) { return; // no matching chat found - cannot add message } else { chat = chat->next; // go to next chat in linked list } } // add given message content to the chat GtkWidget* message = gtk_label_new(content); gtk_box_append(GTK_BOX(chat->chat_content_box), message); } static void build_static_widgets(void) { // main window with stack window = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(window), "LimoX"); gtk_window_set_default_size(GTK_WINDOW(window), 800, 600); stack = gtk_stack_new(); gtk_window_set_child(GTK_WINDOW(window), stack); // connector page connector_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20); connector_page = gtk_stack_add_child(GTK_STACK(stack), connector_box); connector_jid_label = gtk_label_new("XMPP address (JID)"); gtk_box_append(GTK_BOX(connector_box), connector_jid_label); connector_jid_entry = gtk_entry_new(); gtk_box_append(GTK_BOX(connector_box), connector_jid_entry); connector_pwd_label = gtk_label_new("Password"); gtk_box_append(GTK_BOX(connector_box), connector_pwd_label); connector_pwd_entry = gtk_password_entry_new(); gtk_box_append(GTK_BOX(connector_box), connector_pwd_entry); connector_button = gtk_button_new_with_label("Connect"); gtk_box_append(GTK_BOX(connector_box), connector_button); // roster page roster_layout_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20); roster_page = gtk_stack_add_child(GTK_STACK(stack), roster_layout_box); roster_button = gtk_button_new_with_label("Disconnect"); gtk_box_append(GTK_BOX(roster_layout_box), roster_button); roster_scrolled = gtk_scrolled_window_new(); gtk_widget_set_vexpand(roster_scrolled, TRUE); roster_content_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(roster_scrolled), roster_content_box); gtk_box_append(GTK_BOX(roster_layout_box), roster_scrolled); // TODO remove this // add dummy roster items for (int i=0; i<10; i++) { // char buffer[50]; char* buffer = malloc(50*sizeof(char)); sprintf(buffer, "contact-%d@example.com", i+1); add_chat(buffer); } // TODO just for debugging for (int i=0; i<20; i++) { add_incoming_text_message("contact-2@example.com", "Just a test."); // message = gtk_label_new("Test message"); // gtk_box_append(GTK_BOX(chat->chat_content_box), message); } } static void activate(void) { build_static_widgets(); // configure widgets gtk_window_set_application(GTK_WINDOW(window), app); g_signal_connect_swapped(window, "close-request", G_CALLBACK(quit_cb), app); g_signal_connect_swapped( connector_button, "clicked", G_CALLBACK(connect_cb), NULL); g_signal_connect_swapped( roster_button, "clicked", G_CALLBACK(disconnect_cb), NULL); gtk_widget_show(window); } static void idle_cb(void) { limox_run_once(); } void gui_run(void) { #ifdef GTK_SRCDIR g_chdir(GTK_SRCDIR); #endif app = gtk_application_new("eu.xengineering.limox", G_APPLICATION_FLAGS_NONE); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); g_idle_add(G_SOURCE_FUNC(idle_cb), NULL); g_application_run(G_APPLICATION(app), 0, NULL); g_object_unref(app); } void gui_add_message(const char* sender_jid, const char* content) { printf("Received from %s:\n%s\n", sender_jid, content); } void* gui_add_roster_item(const char* jid, const char* sub, const char* name) { if (name) { printf("roster item: %s, %s, sub:%s\n", name, jid, sub); } else { printf("roster item: (no name), %s, sub:%s\n", jid, sub); } return NULL; // TODO return pointer to GTK widget }