summaryrefslogblamecommitdiffstats
path: root/src/ui.c
blob: f2dbe7a802f67bd640bb99d85b5e4fbf5bf3a431 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                   


                         
                   




                                                                                                            


                                                                   



                       





                                                                                                
                                






                                                                                                    
                                                                                        



                                                                                 
                                                                                                                                                                                                            








                                                                                                                                        
                              
                 

                                                                                            


                                                                                                                               
                 
                    
         
                                  
                                                                                    
                                                                          

                                                                                     
                                                                                                                                                                 
                                                                                                                  
                                                              
                                                     
                                                                                                                          
                                                                                                                               
                                                                              








                                                                                                                                                                                       





                                                                                      
                         
















                                                                                                                                                   
 










                                                                                                                                                         
                                                                                                                                                  




























                                                                                                                                                               































                                                                                                                                                                           
                 

                                            
         







































                                                                                                                                                                                                                                                       
                                                                   

                      

                                                                       


                                                                                                                                    






















                                                                                                                                                              
                                                                                                                                                                                             
                                                                                                     






                                                                                                          



                                                                                                                                                                    



                                                    
                           

                            
 
#include <stdlib.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <gtk/gtk.h>
#include <string.h>
unsigned char dc_ui_def_u[] = {
#include <ui.xxd>
};
char * dc_ui_def = (char *) dc_ui_def_u;
#define DC_UI_SET_STATUS(b, s) gtk_label_set_text(GTK_LABEL(gtk_builder_get_object(b, "dc_main_status")), s)
#define DC_UI_GTK /* indicating that the default ui will be used */
#include <h.c>
G_DEFINE_AUTOPTR_CLEANUP_FUNC(gchar, g_free)
struct dc_ui_data {
	GtkBuilder * b;
	GKeyFile * k;
};
/*
	# configuration file - loaded at startup, saved at exit, comments persist - description:
	[discord.c]
	multiline = true|false
	login = string
	password = string
	strftime = format string
*/
void dc_ui_spawn_message (struct dc_message * m, struct dc_ui_data * d) { /* !m to clear messages */
	size_t i = 0;
	GtkWidget * b;
	GtkWidget * w, * w2;
#define DC_USMTL 32
	char t[DC_USMTL];
	g_autoptr(gchar) c = g_key_file_get_string(d->k, "discord.c", "strftime", NULL);
	GtkGrid * g = GTK_GRID(gtk_builder_get_object(d->b, "dc_main_messages"));
	if (!m) {
		while (gtk_grid_get_child_at(g, 0, 0))
			gtk_grid_remove_row(g, 0);
		/* struct dc_channel * c = m->channel->guild->channel; */ /* XXX: I wrote this line of code but then I wen't to sleep and I can't remember what should I do here with the channel :shrug: */
		return;
	}
	while ((w = gtk_grid_get_child_at(g, 0, i))) { /* now we get the index BEFORE which message will be placed */
		struct dc_message * before, * after;
		before = (struct dc_message *) g_object_get_data(G_OBJECT(w), "message"); /* this literally mustn't and can't be NULL */
		if ((w2 = gtk_grid_get_child_at(g, 0, i+1)))
			after = (struct dc_message *) g_object_get_data(G_OBJECT(w2), "message"); /* same here */
		else { /* there is nothing after, message is new */
			i++; /* BEFORE WHICH */
			break;
		}
		if (m->time <= before->time) /* we've found a spot at the top of the grid */
			break;
		if (m->time >= before->time && m->time <= after->time) { /* we've found a spot between two messages */
			i++; /* SAME HERE. if there are no messages already, while will fail immediatley and i will remain 0 */
			break;
		}
		i++;
	}
	gtk_grid_insert_row(g, i);
	/* gtk_grid_insert_column(g, i); */ /* useless, we already *HAVE* columns */
	b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0 /* spacing pixels */);
	snprintf(t, DC_USMTL, "%s#%04d", m->user->username,  m->user->discriminator);
	gtk_container_add(GTK_CONTAINER(b), gtk_label_new(t));
	/* TODO: implement parsing markup here: bold, italic, underline; REMOVE < character; implement tags, timestamps, channels and spoilers with GTK ahrefs */
	strftime(t, DC_USMTL, c ? strcmp(c, "") ? c : "%c" : "%c", localtime(&m->time)); /* singlethreaded only */
	gtk_container_add(GTK_CONTAINER(b), gtk_label_new(t));
	g_object_set_data(G_OBJECT(b), "message", m);
	gtk_grid_attach(g /* grid */, b /* widget to insert */, 0 /* left */, i /* top */, 1 /* width */, 1 /* height */);
	/* if (m->user == m->channel->guild->client->user) */ /* TODO: if I posted the message, make it an editable textview */
	gtk_grid_attach(g, GTK_WIDGET(gtk_label_new(m->message)), 1, i, 1, 1);
	gtk_widget_show_all(GTK_WIDGET(g));
	gtk_widget_show_all(GTK_WIDGET(g));
}
void dc_ui_spawn_channel (struct dc_channel * c /* needs a functional guild or segfaults */, struct dc_ui_data * d) { /* adds a channel to the channel list, !c to clear all entries */
	GtkTreeStore * l = GTK_TREE_STORE(gtk_builder_get_object(d->b, "dc_main_tree")); /* this func is transfer none */
	GtkTreeView * t = GTK_TREE_VIEW(gtk_builder_get_object(d->b, "dc_main_channels"));
	GtkTreeIter i;
	if (!c) {
		gtk_tree_store_clear(l);
		for (size_t i = 0; i < c->guild->client->guilds_length; i++) {
			c->guild->client->guilds[i]->is_iter = FALSE;
			struct dc_channel * ch = c->guild->client->guilds[i]->channel;
			while (ch) {
				ch->is_iter = FALSE;
				ch = ch->next;
			}
		}
	}
	if (!c->guild->is_iter) { /* we don't have this guild already rendered */
		gtk_tree_store_insert(l, &i, NULL, -1 /* row position */);
		gtk_tree_store_set(l, &i, 0, c->guild->name, -1);
		gtk_tree_store_set(l, &i, 1, c->guild, -1);
		memcpy(&c->guild->iter, &i, sizeof(GtkTreeIter));
		c->guild->is_iter = TRUE;
	}
	gtk_tree_store_insert(l, &i, &c->guild->iter, -1); /* for this to work, iter must not be same as parent! */
	gtk_tree_store_set(l, &i, 0, c->name, -1); /* TODO: set hover tooltip for c->topic */
	gtk_tree_store_set(l, &i, 1, c, -1);
	memcpy(&c->iter, &i,  sizeof(GtkTreeIter));
	c->is_iter = TRUE;
	if (!gtk_tree_view_get_column(t, 0))
		gtk_tree_view_append_column(t, gtk_tree_view_column_new_with_attributes("Channel", gtk_cell_renderer_text_new(), "text", 0, NULL));
	gtk_tree_view_expand_all(t);
}
gchar * gtk_text_buffer_get_all_text(GtkTextBuffer * b) {
	GtkTextIter s, e;
	gtk_text_buffer_get_start_iter(b, &s);
	gtk_text_buffer_get_end_iter(b, &e);
	gchar * c = gtk_text_iter_get_text(&s, &e);
	return c; /* must-g_free, transfer-full */
}
G_MODULE_EXPORT void dc_ui_settings_ok (GtkButton * b, struct dc_ui_data * d) {
	g_key_file_set_boolean(d->k, "discord.c", "multiline", gtk_switch_get_active(GTK_SWITCH(gtk_builder_get_object(d->b, "dc_settings_multiline"))));
	g_key_file_set_string(d->k, "discord.c", "login", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_login"))));
	g_key_file_set_string(d->k, "discord.c", "password", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_password"))));
	g_key_file_set_string(d->k, "discord.c", "strftime", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_strftime"))));
	gtk_widget_hide(gtk_widget_get_toplevel(GTK_WIDGET(b)));
}
G_MODULE_EXPORT void dc_ui_inputbox_changed (GtkWidget * i, struct dc_ui_data * d) {
	GtkTextView * t = GTK_TEXT_VIEW(gtk_builder_get_object(d->b, "dc_main_multiline"));
	GtkEntry * e = GTK_ENTRY(gtk_builder_get_object(d->b, "dc_main_singleline"));
	GtkWidget * b = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_main_send"));
	gchar * c = gtk_text_buffer_get_all_text(gtk_text_view_get_buffer(t));
	gtk_widget_set_sensitive(b, c[0] || gtk_entry_get_text(e)[0] ? TRUE : FALSE);
	g_free(c);
}
void dc_ui_inputbox_activate (GtkWidget * a, struct dc_ui_data * d) {
	GtkTextView * t = GTK_TEXT_VIEW(gtk_builder_get_object(d->b, "dc_main_multiline"));
	GtkEntry * e = GTK_ENTRY(gtk_builder_get_object(d->b, "dc_main_singleline"));
	GtkTextBuffer * b = gtk_text_view_get_buffer(t);
	gchar * c = (gchar *) /* DROPPING const HERE!!! TODO: do this more politely with suppressions etc. */ gtk_entry_get_text(e); /* do not free this one */
	if (c[0])
		g_print("entry says: %s\n", c);
	else { /* we need text from the textview, entry is empty */
		c = gtk_text_buffer_get_all_text(b);
		g_print("textview says: %s\n", c);
		a = NULL; /* so we mark the state, if we should free c or not */
	}
	/* do stuff with c */
	if (c[0] == '/') /* handle command */
		switch (c[1]) { /* unlike before the rewrite / can't be escaped, because escaping / is useless. */
			case 'c': /* clear messages, developing debugging TODO: delete before production useless */
			case 'C':
				dc_ui_spawn_message(NULL, d);
				break;
			case 'd': /* debug create message TODO: delete before production useless */
			case 'D': /* /debug unixtimestamp message */
				;
				struct dc_user u;
				u.username = "yeet";
				struct dc_message * m = malloc(sizeof(struct dc_message)); /* memory leak! */
				m->time = atoi((strchr(c, ' ') ? strchr(c, ' ') : c) + 1);
				m->user = &u;
				m->message = "test message";
				dc_ui_spawn_message(m, d);
				break;
			case 't': /* debug add string to the channel tree. TODO: delete before production useless */
			case 'T': /* /tree position string */
				;
				struct dc_channel * c = calloc(1, sizeof(struct dc_channel)); /* memory leak! */
				struct dc_guild * g = calloc(1, sizeof(struct dc_guild));
				c->guild = g;
				c->name = "this is a parent.";
				c->guild->name = "this is a child.";
				dc_ui_spawn_channel(c, d);
				break;
				GtkTreeStore * l = GTK_TREE_STORE(gtk_builder_get_object(d->b, "dc_main_tree"));
				GtkTreeView * t = GTK_TREE_VIEW(gtk_builder_get_object(d->b, "dc_main_channels"));
				GtkTreeIter i;
				GtkTreeIter j;
				gtk_tree_store_insert(l, &i, NULL, 0);
				gtk_tree_store_set(l, &i, 0, "eww", -1);
				gtk_tree_store_insert(l, &j, &i, 0); /* for this to work, iter must not be the same as parent! */
				gtk_tree_store_set(l, &j, 0, "eww", -1);
				if (!gtk_tree_view_get_column(t, 0))
					gtk_tree_view_append_column(t, gtk_tree_view_column_new_with_attributes("Channel", gtk_cell_renderer_text_new(), "text", 0, NULL));
				break;
		}
	else { /* send message to channel */

	}
	/* stop doing stuff with c */
	if (!a)
		g_free(c);
	gtk_text_buffer_set_text(b, "", -1);
	gtk_entry_set_text(e, ""); /* singleline */
}
G_MODULE_EXPORT gboolean dc_ui_multiline_focus (GtkTextView * t, GtkDirectionType d /* pojma nimam, kako ta enum pove a mam fokus al ne... čudno */, gpointer u) { /* not working, there's not placeholder then */
	char * p = "Enter message in this multiline text field or switch to a single line in preferences. Send message with Ctrl+Enter.";
	GtkTextBuffer * b = gtk_text_view_get_buffer(t);
	gchar * c = gtk_text_buffer_get_all_text(b);
	if (gtk_widget_has_focus(GTK_WIDGET(t))) {
		if (!strcmp(p, c))
			gtk_text_buffer_set_text(b, "", -1);
	} else
		if (!c[0])
			gtk_text_buffer_set_text(b, p, -1);
	g_free(c);
	return FALSE; /* to keep executing other handles for signals instead of finishing here. AFAIK, RTFM */
}
G_MODULE_EXPORT void dc_ui_set_multiline (GtkSwitch * a, gboolean s, struct dc_ui_data * d) {
	GtkWidget * t = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_main_multiline"));
	GtkWidget * e = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_main_singleline"));
	gtk_widget_hide(e);
	gtk_widget_hide(t);
	if (s)
		gtk_widget_show(t);
	else
		gtk_widget_show(e);
	/* dc_ui_multiline_focus(GTK_TEXT_VIEW(t), 0, NULL); */ /* NOT WORKING, meh, there will be no placeholder */ /* just so we set the placeholder, the most important part, otherwise the user will not even see the textview on some themes <3 */
}
G_MODULE_EXPORT void dc_ui_spawn_window (GtkToolButton * t, GtkWindow * w) {
	gtk_widget_show_all(GTK_WIDGET(w));
}
G_MODULE_EXPORT gboolean dc_ui_handle_close (GtkButton * b, gpointer u) {
	gtk_widget_hide(gtk_widget_get_toplevel(GTK_WIDGET(b)));
	return TRUE; /* so that it stays non-deleted, main window sould call/be handled with gtk_main_quit */
}
G_MODULE_EXPORT void dc_ui_reveal_password (GtkSwitch * t, gboolean s, GtkEntry * e) {
	gtk_entry_set_visibility(e, s);
}
void dc_ui_activate (GtkApplication * app, struct dc_ui_data * d) {
	GtkWidget * w;
	gchar * s;
	w = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_window_main"));
	gtk_builder_connect_signals(d->b, d);
	/* začetek definicije dodatnih signalov */
	/* g_signal_connect(gtk_builder_get_object(b, "dc_settings_multiline"), "state-set", G_CALLBACK(dc_ui_set_multiline), b); */
	/* konec definicije dodatnih signalov */
	gtk_widget_show_all(w);
	dc_ui_set_multiline(NULL, g_key_file_get_boolean(d->k, "discord.c", "multiline", NULL), d);
	dc_ui_inputbox_changed(NULL, d);
	/* začetek aplikacije konfiguracijskih vrednosti v UI */
	gtk_switch_set_state(GTK_SWITCH(gtk_builder_get_object(d->b, "dc_settings_multiline")), g_key_file_get_boolean(d->k, "discord.c", "multiline", NULL));
	s = g_key_file_get_string(d->k, "discord.c", "login", NULL);
	gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_login")), s ? s : "");
	g_free(s);
	s = g_key_file_get_string(d->k, "discord.c", "password", NULL);
	gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_password")), s ? s : "");
	g_free(s);
	/* gtk zdaj požene main loop */
}
int dc_ui (int argc, char ** argv) {
	GtkApplication *app;
	int status;
	struct dc_ui_data d;
	gchar * s;
	gtk_init(&argc, &argv);
	app = gtk_application_new("eu.sijanec.discord.c", G_APPLICATION_FLAGS_NONE);
	g_signal_connect(app, "activate", G_CALLBACK(dc_ui_activate), &d);
	/* pre app run setup */
	d.b = gtk_builder_new_from_string(dc_ui_def, -1);
#define dc_uacf "%s/%sdiscord.c", getenv("XDG_CONFIG_HOME") ? getenv("XDG_CONFIG_HOME") : getenv("HOME") ? getenv("HOME") : ".", getenv("XDG_CONFIG_HOME") ? "" : ".config/" /* as per XDG */
	gchar fn[snprintf(NULL, 0, dc_uacf)]; /* glib has g_get_user_config_dir but naaaaaaaaaaah  */
	sprintf(fn, dc_uacf);
	s = strrchr(fn, '/');
	s[0] = '\0';
	g_mkdir_with_parents(fn, 0700 /* as per XDG */);
	s[0] = '/';
	d.k = g_key_file_new();
	g_key_file_load_from_file(d.k, fn, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
	/* app run */
	status = g_application_run(G_APPLICATION(app), argc, argv);
	gtk_main(); /* XXX: NO IDEA why this has to be run.... gtk_main loop should be started by g_application_run, but it doesn't do that FOR SOME REASON. HELP */
	/* post app cleanup */
	g_object_unref(d.b);
	if (!g_key_file_save_to_file(d.k, fn, NULL))
		g_warning("couldn't save config");
	g_key_file_free(d.k);
	/* dc_ui cleanup */
	g_object_unref(app);
	return status;
}