aboutsummaryrefslogtreecommitdiff
path: root/moji.c
diff options
context:
space:
mode:
Diffstat (limited to 'moji.c')
-rw-r--r--moji.c337
1 files changed, 337 insertions, 0 deletions
diff --git a/moji.c b/moji.c
new file mode 100644
index 0000000..7a397d4
--- /dev/null
+++ b/moji.c
@@ -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;
+}