diff options
author | tlatorre <tlatorre@uchicago.edu> | 2022-11-12 13:47:35 -0800 |
---|---|---|
committer | tlatorre <tlatorre@uchicago.edu> | 2022-11-12 13:47:35 -0800 |
commit | e5dfc1285f4be8559a6f1f42e52facee72808a99 (patch) | |
tree | 372d43fea9457d4e28463e78b16e74d0d941fe19 /moji.c | |
parent | e7766d22748115410567d666906162c9bdb91548 (diff) | |
download | moji-e5dfc1285f4be8559a6f1f42e52facee72808a99.tar.gz moji-e5dfc1285f4be8559a6f1f42e52facee72808a99.tar.bz2 moji-e5dfc1285f4be8559a6f1f42e52facee72808a99.zip |
rename project to moji
Diffstat (limited to 'moji.c')
-rw-r--r-- | moji.c | 337 |
1 files changed, 337 insertions, 0 deletions
@@ -0,0 +1,337 @@ +#include <gtk/gtk.h> +#include <sys/time.h> +#include <stdlib.h> +#include <gdk/gdkkeysyms.h> +#include <gdk/gdkenums.h> + +/* Time in milliseconds it takes to write a character, i.e. if you hold down + * the mouse button for this many ms it will write the character and move on to + * the next character (or emoji). + * + * Initially had this set to 500 ms (half a second), but that was a bit short, + * so I've updated it to one second. */ +#define CLICK_TIME 1000 + +#define LEN(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) + +/* Returns the current time in seconds and milliseconds since the epoch. + * Mostly useful for calculating time intervals. */ +static void aeGetTime(long *seconds, long *milliseconds) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + *seconds = tv.tv_sec; + *milliseconds = tv.tv_usec/1000; +} + +GtkWidget *text_widget; +GtkTextTag *tag; + +/* List of emojis. */ +#define STOP "\xf0\x9f\x9b\x91" +#define THANK_YOU "\xf0\x9f\x99\x8f" +#define UNAMUSED "\xf0\x9f\x98\x92" +#define SLEEPING "\xf0\x9f\x98\xb4" +#define BED "\xf0\x9f\x9b\x8f" +#define CHAIR "\xf0\x9f\xaa\x91" +#define NURSE "\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9a\x95\xef\xb8\x8f" +#define ROLLING_EYES "\xf0\x9f\x99\x84" +#define OVERHEATED "\xf0\x9f\xa5\xb5" +#define FREEZING "\xf0\x9f\xa5\xb6" +#define NAUSEOUS "\xf0\x9f\xa4\xa2" +#define THINKING "\xf0\x9f\xa4\x94" +#define THUMBS_UP "\xf0\x9f\x91\x8d" +#define THUMBS_DOWN "\xf0\x9f\x91\x8e" +#define LEFT_ARROW "\xe2\x86\x90" +#define CLEAR_SCREEN "\xe2\x8e\x9a" +#define SOUND "\xf0\x9f\x94\x8a" +#define DROPLET "\xf0\x9f\x92\xa7" +#define FOOD "\xf0\x9f\x8d\xb2" +#define TIRED "\xf0\x9f\x98\xab" +#define HAPPY "\xf0\x9f\x98\x80" +#define LAUGHING "\xf0\x9f\x98\x84" +#define NEWLINE "\xe2\x86\xb5" + +typedef struct chars +{ + const char **characters; + int len; +} chars; + +const char *emoji_array[] = { + THINKING, + THUMBS_UP, + THUMBS_DOWN, + STOP, + HAPPY, + LAUGHING, + TIRED, + FREEZING, + OVERHEATED, + NAUSEOUS, + ROLLING_EYES, + SLEEPING, + UNAMUSED, + THANK_YOU, + NURSE, + DROPLET, + CHAIR, + BED, + //FOOD, + LEFT_ARROW, + CLEAR_SCREEN, +}; + +const char *alphabet_array[] = { + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + LEFT_ARROW, + CLEAR_SCREEN, + SOUND, + "_", + NEWLINE, + "!", + "?", + "." +}; + +chars emoji = { emoji_array, LEN(emoji_array)}; +chars alphabet = {alphabet_array, LEN(alphabet_array)}; +chars *current_chars = &emoji; + +char buf[100000]; + +/* The current alphabetical letter. */ +int current_letter = 0; +/* The current position in the buffer. + * + * FIXME: What to do if we run out of buffer space?. */ +int current_position = 0; + +/* Time the mouse was clicked (seconds) */ +long pressed_time_sec = 0; +/* Time the mouse was clicked (milliseconds) */ +long pressed_time_msec = 0; +/* Time the mouse was released (seconds) */ +long released_time_sec = 0; +/* Time the mouse was released (milliseconds) */ +long released_time_msec = 0; + +void backspace(void) +{ + GtkTextBuffer *text_buffer; + GtkTextIter start, end; + + if (current_position > 0) { + text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget)); + gtk_text_buffer_set_text(text_buffer, buf, -1); + + gtk_text_buffer_get_iter_at_offset(text_buffer, &start, 0); + gtk_text_buffer_get_iter_at_offset(text_buffer, &end, g_utf8_strlen(buf,-1)-2); + current_position = strlen(gtk_text_buffer_get_slice(text_buffer,&start,&end,TRUE)); + } +} + +static void write_buf(void) +{ + GtkTextBuffer *text_buffer; + GtkTextIter start, end; + + text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget)); + sprintf(buf+current_position,"%s",current_chars->characters[current_letter % current_chars->len]); + + gtk_text_buffer_set_text(text_buffer, buf, -1); + + gtk_text_buffer_get_iter_at_offset(text_buffer, &start, g_utf8_strlen(buf,-1)-1); + gtk_text_buffer_get_iter_at_offset(text_buffer, &end, -1); + gtk_text_buffer_apply_tag(text_buffer, tag, &start, &end); +} + +static int right_click_pressed(GtkWidget *widget, gpointer data) +{ + backspace(); + write_buf(); + return TRUE; +} + +static int right_click_released(GtkWidget *widget, gpointer data) +{ + return TRUE; +} + +static int pressed(GtkWidget *widget, gpointer data) +{ + aeGetTime(&pressed_time_sec,&pressed_time_msec); + return TRUE; +} + +static int released(GtkWidget *widget, gpointer data) +{ + const char *current_char; + + aeGetTime(&released_time_sec,&released_time_msec); + long long diff = (released_time_sec - pressed_time_sec)*1000 + (released_time_msec - pressed_time_msec); + + if (diff > 10000) { + current_position = 0; + current_letter = 0; + } else if (diff > CLICK_TIME) { + current_char = current_chars->characters[current_letter % current_chars->len]; + if (!strcmp(current_char,LEFT_ARROW)) { + backspace(); + } else if (!strcmp(current_char,CLEAR_SCREEN)) { + current_position = 0; + current_letter = 0; + } else if (!strcmp(current_char,SOUND)) { + char cmd[1000000]; + buf[current_position] = '\0'; + sprintf(cmd, "gtts-cli \"%s\" --output moji.mp3", buf); + if (!system(cmd)) { + system("mpg123 moji.mp3"); + system("rm moji.mp3"); + } + } else if (!strcmp(current_char,"_")) { + sprintf(buf+current_position," "); + current_position += strlen(current_char); + } else if (!strcmp(current_char,NEWLINE)) { + sprintf(buf+current_position,"\n"); + current_position += 1; + } else { + sprintf(buf+current_position,"%s",current_char); + current_position += strlen(current_char); + } + if (current_position > LEN(buf) - 1) { + /* FIXME: better way to do this? */ + current_position = 0; + } + /* Uncomment the next line to start over at the beginning of the + * alphabet after a new letter is entered. But, I think it's better + * to keep the current letter. The reason is that you might want to + * delete a whole word and so when you navigate to the backspace you + * just have to keep entering the same letter. */ + current_letter = 0; + } else { + current_letter += 1; + } + + write_buf(); + + return TRUE; +} + +gboolean on_key_press(GtkEventControllerKey *, guint keyval, guint keycode, GdkModifierType mod, gpointer user_data) +{ + switch (keyval) + { + case GDK_KEY_space: + if (current_chars == &emoji) + current_chars = &alphabet; + else if (current_chars == &alphabet) + current_chars = &emoji; + current_letter = 0; + write_buf(); + break; + case GDK_KEY_BackSpace: + backspace(); + write_buf(); + break; + default: + return FALSE; + } + return FALSE; +} + +static void activate(GtkApplication *app, gpointer user_data) +{ + GtkWidget *window; + GtkTextBuffer *text_buffer; + GtkCssProvider *provider; + GtkStyleContext *context; + GtkGesture *gesture, *right_click_gesture; + GtkEventController *event_controller; + + window = gtk_application_window_new(app); + gtk_window_set_title(GTK_WINDOW(window), "Moji"); + gtk_window_set_default_size(GTK_WINDOW(window), 200, 200); + + event_controller = gtk_event_controller_key_new(); + g_signal_connect(event_controller, "key-pressed", G_CALLBACK(on_key_press), NULL); + gtk_widget_add_controller(window, event_controller); + + right_click_gesture = gtk_gesture_click_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE (right_click_gesture), 3); + g_signal_connect(right_click_gesture, "pressed", G_CALLBACK(right_click_pressed), NULL); + g_signal_connect(right_click_gesture, "released", G_CALLBACK(right_click_released), NULL); + gtk_widget_add_controller(window, GTK_EVENT_CONTROLLER(right_click_gesture)); + + gesture = gtk_gesture_click_new(); + g_signal_connect(gesture, "pressed", G_CALLBACK(pressed), NULL); + g_signal_connect(gesture, "released", G_CALLBACK(released), NULL); + gtk_widget_add_controller(window, GTK_EVENT_CONTROLLER(gesture)); + + text_widget = gtk_text_view_new(); + gtk_text_view_set_editable((GtkTextView *) text_widget, FALSE); + gtk_text_view_set_cursor_visible((GtkTextView *) text_widget, TRUE); + gtk_text_view_set_wrap_mode((GtkTextView *) text_widget, GTK_WRAP_CHAR); + gtk_widget_set_can_focus(text_widget, FALSE); + gtk_widget_set_can_target(text_widget, FALSE); + text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget)); + + /* Change default font and color throughout the widget */ + provider = gtk_css_provider_new(); + gtk_css_provider_load_from_data(provider, + "textview {" + " font: 144pt monospace;" + " color: black;" + "}", + -1); + context = gtk_widget_get_style_context(text_widget); + gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + /* Use a tag to change the color for just one part of the widget */ + tag = gtk_text_buffer_create_tag(text_buffer, "red_foreground", "foreground", "red", NULL); + write_buf(); + gtk_window_set_child(GTK_WINDOW(window), text_widget); + + gtk_window_fullscreen(GTK_WINDOW(window)); + + gtk_window_present(GTK_WINDOW(window)); +} + +int main(int argc, char **argv) +{ + GtkApplication *app; + int status; + + app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE); + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + + return status; +} |