Last active
October 29, 2023 10:53
-
-
Save rlapz/1a2ae7398b5af949b6fc6f0ea1105ddb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
set -e | |
cc -g -Wall -Wextra moetr.c $(pkg-config --cflags gtk4 libsoup-3.0 json-glib-1.0)\ | |
$(pkg-config --libs gtk4 libsoup-3.0 json-glib-1.0) -o moetr |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <gtk/gtk.h> | |
#include <libsoup/soup.h> | |
#include <json-glib/json-glib.h> | |
/* | |
* default configs | |
*/ | |
#define DEFAULT_WINDOW_W 450 | |
#define DEFAULT_WINDOW_H 500 | |
#define DEFAULT_WINDOW_TITLE "MoeTr - A Simple Language Translator" | |
//#define HTTP_API_HOST "translate.googleapis.com:811" | |
#define HTTP_API_HOST "translate.googleapis.com" | |
#define HTTP_API_QUERY "/translate_a/single?client=gtx&ie=UTF-8&oe=UTF-8" | |
#define HTTP_API_URI_FMT_DETAIL "http://" HTTP_API_HOST HTTP_API_QUERY \ | |
"&dt=bd&dt=ex&dt=ld&dt=md&dt=rw&dt=rm&dt=ss&dt="\ | |
"t&dt=at&dt=gt&dt=qca&sl=%s&tl=%s&hl=%s&q=" | |
/* | |
* helpers | |
*/ | |
#define SIGNAL_CONNECT(INSTANCE, SIGNAL, FUNC, UDATA)\ | |
g_signal_connect(INSTANCE, SIGNAL, G_CALLBACK(FUNC), UDATA) | |
/* | |
* global vars | |
*/ | |
static const char *langs[] = { | |
"auto - Automatic", /* only for "src" lang, | |
skipped for "trg" lang (without 0th index) */ | |
"af - Afrikaans", "sq - Albanian", "am - Amharic", | |
"ar - Arabic", "hy - Armenian", "az - Azerbaijani", | |
"eu - Basque", "be - Belarusian", "bn - Bengali", | |
"bs - Bosnian", "bg - Bulgarian", "ca - Catalan", | |
"ceb - Cebuano", "zh-CN - Chinese Simplified", "zh-TW - Chinese Traditional", | |
"co - Corsican", "hr - Croatian", "cs - Czech", | |
"da - Danish", "nl - Dutch", "en - English", | |
"eo - Esperanto", "et - Estonian", "fi - Finnish", | |
"fr - French", "fy - Frisian", "gl - Galician", | |
"ka - Georgian", "de - German", "el - Greek", | |
"gu - Gujarati", "ht - Haitian Crole", "ha - Hausan", | |
"haw - Hawaiian", "iw - Hebrew", "hi - Hindi", | |
"hmn - Hmong", "hu - Hungarian", "is - Icelandic", | |
"ig - Igbo", "id - Indonesian", "ga - Irish", | |
"it - Italian", "ja - Japanese", "jw - Javanese", | |
"kn - Kannada", "kk - Kazakh", "km - Khmer", | |
"rw - Kinyarwanda", "ko - Korean", "ku - Kurdish", | |
"ky - Kyrgyz", "lo - Lao", "la - Latin", | |
"lv - Latvian", "lt - Lithunian", "lb - Luxembourgish", | |
"mk - Macedonian", "mg - Malagasy", "ms - Malay", | |
"ml - Malayam", "mt - Maltese", "mi - Maori", | |
"mr - Marathi", "mn - Mongolian", "my - Myanmar", | |
"ne - Nepali", "no - Norwegian", "ny - Nyanja", | |
"or - Odia", "ps - Pashto", "fa - Persian", | |
"pl - Polish", "pt - Portuguese", "pa - Punjabi", | |
"ro - Romanian", "ru - Russian", "sm - Samoan", | |
"gd - Scots Gaelic", "sr - Serbian", "st - Sesotho", | |
"sn - Shona", "sd - Sindhi", "si - Sinhala", | |
"sk - Slovak", "sl - Slovenian", "so - Somali", | |
"es - Spanish", "su - Sundanese", "sw - Swahili", | |
"sv - Swedish", "tl - Tagalog", "tg - Tajik", | |
"ta - Tamil", "tt - Tatar", "te - Telugu", | |
"th - Thai", "tr - Turkish", "tk - Turkmen", | |
"uk - Ukranian", "ur - Urdu", "ug - Uyghur", | |
"uz - Uzbek", "vi - Vietnamese", "cy - Welsh", | |
"xh - Xhosa", "yi - Yiddish", "yo - Yaruba", | |
"zu - Zulu", | |
NULL, | |
}; | |
/* | |
* lang | |
*/ | |
struct lang { | |
char key[6]; | |
const char *val; | |
}; | |
static struct lang *lang_get_by_id(struct lang *self, guint id); | |
static const char *lang_get_val(struct lang *self, const char key[]); | |
/* | |
* txtbuf | |
*/ | |
struct txtbuf { | |
char *name; | |
GtkTextBuffer *body; | |
}; | |
static void txtbuf_init(struct txtbuf *self); | |
static void txtbuf_set_body_text(struct txtbuf *self, const char body[]); | |
static void txtbuf_set_name(struct txtbuf *self, const char name[]); | |
static GtkTextBuffer *txtbuf_get_body(struct txtbuf *self); | |
static char *txtbuf_get_body_text(struct txtbuf *self); | |
static char *txtbuf_get_name(struct txtbuf *self); | |
static void txtbuf_copy_body_to_name(struct txtbuf *self); | |
static void txtbuf_deinit(struct txtbuf *self); | |
/* | |
* HTTP | |
*/ | |
typedef void (*HttpCallback)(void *udata, GBytes *resp, GError *err); | |
struct http { | |
SoupSession *session; | |
SoupMessage *message; | |
GCancellable *cancellable; | |
HttpCallback callback; | |
void *callback_udata; | |
}; | |
static void http_init(struct http *self, HttpCallback callback, void *udata); | |
static void http_request(struct http *self, const char uri[]); | |
static void http_request_cancel(struct http *self); | |
static void http_clear(struct http *self); | |
static void http_deinit(struct http *self); | |
/* | |
* moetr | |
*/ | |
struct moetr { | |
gboolean is_ready; | |
GtkApplication *app; | |
GtkWidget *lst_src; | |
GtkWidget *lst_trg; | |
GtkWidget *txt_trg; | |
GtkWidget *txt_src; | |
GtkWidget *btn_open; | |
GtkWidget *btn_switch; | |
GtkWidget *btn_clear; | |
GtkWidget *btn_translate; | |
GtkWidget *btn_speak_src; | |
GtkWidget *btn_speak_trg; | |
struct txtbuf txtbuf_src; | |
struct txtbuf txtbuf_trg; | |
struct lang lang_src; | |
struct lang lang_trg; | |
struct http http; | |
}; | |
static void moetr_init(struct moetr *self); | |
static void moetr_deinit(struct moetr *self); | |
static int moetr_run(struct moetr *self, int argc, char *argv[]); | |
static char *moetr_build_request(struct moetr *self, const char text[]); | |
static void moetr_translate(struct moetr *self); | |
static void moetr_write(struct moetr *self, const char resp_json[]); | |
/* | |
* callbacks | |
*/ | |
static void on_app_activate(GtkApplication *self, struct moetr *moe); | |
static void on_txt_buffer_trg_changed(GtkTextBuffer *self, struct moetr *moe); | |
static void on_txt_buffer_src_changed(GtkTextBuffer *self, struct moetr *moe); | |
static void on_btn_open_clicked(GtkButton *self, struct moetr *moe); | |
static void on_btn_switch_clicked(GtkButton *self, struct moetr *moe); | |
static void on_btn_clear_clicked(GtkButton *self, struct moetr *moe); | |
static void on_btn_translate_clicked(GtkButton *self, struct moetr *moe); | |
static void on_btn_speak_src_clicked(GtkButton *self, struct moetr *moe); | |
static void on_btn_speak_trg_clicked(GtkButton *self, struct moetr *moe); | |
static void on_http_response_ready(GObject *self, GAsyncResult *res, void *udata); | |
static void on_moetr_http_response(void *udata, GBytes *resp, GError *err); | |
/*=====================================================================* | |
* IMPLS * | |
*=====================================================================*/ | |
/* | |
* lang | |
*/ | |
static struct lang * | |
lang_get_by_id(struct lang *self, guint id) | |
{ | |
if (id >= G_N_ELEMENTS(langs) - 1) | |
return NULL; | |
const char *const lang = langs[id]; | |
if (lang == NULL) | |
return NULL; | |
const char *const dash = strstr(lang, " - "); | |
if (*(dash + 3) == '\0') | |
return NULL; | |
char tmp[64]; | |
size_t const l_size = (dash - lang) + 1; | |
if (l_size >= sizeof(tmp)) | |
return NULL; | |
g_strlcpy(tmp, lang, l_size); | |
g_strlcpy(self->key, g_strstrip(tmp), sizeof(self->key)); | |
self->val = (dash + 3); | |
return self; | |
} | |
static const char * | |
lang_get_val(struct lang *self, const char key[]) | |
{ | |
const size_t len = strlen(key); | |
if ((len < 2) || (len > 5)) | |
return NULL; | |
const size_t size = G_N_ELEMENTS(langs) - 1; | |
for (size_t i = 0; i < size; i++) { | |
if (g_ascii_strncasecmp(langs[i], key, len) == 0) { | |
if (lang_get_by_id(self, i) == NULL) | |
return NULL; | |
return self->val; | |
} | |
} | |
return NULL; | |
} | |
/* | |
* HTTP | |
*/ | |
static void | |
http_init(struct http *self, HttpCallback callback, void *udata) | |
{ | |
self->message = NULL; | |
self->callback = callback; | |
self->callback_udata = udata; | |
self->session = soup_session_new(); | |
self->cancellable = g_cancellable_new(); | |
} | |
static void | |
http_request(struct http *self, const char uri[]) | |
{ | |
self->message = soup_message_new(SOUP_METHOD_GET, uri); | |
soup_session_send_and_read_async(self->session, self->message, G_PRIORITY_DEFAULT, | |
self->cancellable, on_http_response_ready, self); | |
} | |
static void | |
http_request_cancel(struct http *self) | |
{ | |
g_cancellable_cancel(self->cancellable); | |
} | |
static void | |
http_clear(struct http *self) | |
{ | |
if (self->message != NULL) { | |
g_object_unref(self->message); | |
self->message = NULL; | |
} | |
g_cancellable_reset(self->cancellable); | |
} | |
static void | |
http_deinit(struct http *self) | |
{ | |
http_clear(self); | |
g_object_unref(self->session); | |
g_object_unref(self->cancellable); | |
} | |
/* | |
* txtbuf | |
*/ | |
static void | |
txtbuf_init(struct txtbuf *self) | |
{ | |
self->name = NULL; | |
self->body = gtk_text_buffer_new(NULL); | |
} | |
static void | |
txtbuf_set_body_text(struct txtbuf *self, const char body[]) | |
{ | |
if (body != NULL) | |
gtk_text_buffer_set_text(self->body, body, -1); | |
} | |
static void | |
txtbuf_set_name(struct txtbuf *self, const char name[]) | |
{ | |
g_free(self->name); | |
self->name = g_strdup(name); | |
} | |
static GtkTextBuffer * | |
txtbuf_get_body(struct txtbuf *self) | |
{ | |
return self->body; | |
} | |
static char * | |
txtbuf_get_body_text(struct txtbuf *self) | |
{ | |
GtkTextIter start, end; | |
gtk_text_buffer_get_bounds(self->body, &start, &end); | |
return gtk_text_buffer_get_text(self->body, &start, &end, FALSE); | |
} | |
static char * | |
txtbuf_get_name(struct txtbuf *self) | |
{ | |
return self->name; | |
} | |
static void | |
txtbuf_copy_body_to_name(struct txtbuf *self) | |
{ | |
g_free(self->name); | |
self->name = g_strstrip(txtbuf_get_body_text(self)); | |
} | |
static void | |
txtbuf_deinit(struct txtbuf *self) | |
{ | |
g_free(self->name); | |
g_object_unref(self->body); | |
} | |
/* | |
* moetr | |
*/ | |
static void | |
moetr_init(struct moetr *self) | |
{ | |
self->is_ready = FALSE; | |
self->app = gtk_application_new("org.rlapz.moetr", G_APPLICATION_DEFAULT_FLAGS); | |
txtbuf_init(&self->txtbuf_src); | |
txtbuf_init(&self->txtbuf_trg); | |
http_init(&self->http, on_moetr_http_response, self); | |
SIGNAL_CONNECT(self->app, "activate", on_app_activate, self); | |
} | |
static void | |
moetr_deinit(struct moetr *self) | |
{ | |
g_object_unref(self->app); | |
txtbuf_deinit(&self->txtbuf_src); | |
txtbuf_deinit(&self->txtbuf_trg); | |
http_deinit(&self->http); | |
} | |
static int | |
moetr_run(struct moetr *self, int argc, char *argv[]) | |
{ | |
return g_application_run(G_APPLICATION(self->app), argc, argv); | |
} | |
static char * | |
moetr_build_request(struct moetr *self, const char text[]) | |
{ | |
GString *const str = g_string_new(NULL); | |
if (str == NULL) | |
return NULL; | |
const char *const _langs[] = { self->lang_src.key, self->lang_trg.key }; | |
g_string_append_printf(str, HTTP_API_URI_FMT_DETAIL, _langs[0], _langs[1], _langs[1]); | |
g_string_append_uri_escaped(str, text, NULL, TRUE); | |
return g_string_free_and_steal(str); | |
} | |
static char * | |
__moetr_parse_response_detail(const char trg_txt[], JsonNode *node) | |
{ | |
gboolean is_err = TRUE; | |
GString *const str = g_string_new(NULL); | |
JsonArray *const arr0 = json_node_get_array(node); | |
if (arr0 == NULL) | |
goto out0; | |
g_print("%u\n", json_array_get_length(arr0)); | |
/* TODO */ | |
g_string_append(str, trg_txt); | |
is_err = FALSE; | |
out0: | |
if (is_err) { | |
g_print("error occured: %s\n", "invalid response"); | |
g_string_printf(str, "%s", trg_txt); | |
} | |
return g_string_free_and_steal(str); | |
} | |
static char * | |
__moetr_parse_response(struct moetr *self, const char resp_json[]) | |
{ | |
char *ret = NULL; | |
const char *trg_txt; | |
gboolean is_err = TRUE; | |
GError *err = NULL; | |
JsonParser *const parser = json_parser_new(); | |
if (!json_parser_load_from_data(parser, resp_json, -1, &err)) { | |
g_print("error occured: %s\n", err->message); | |
g_error_free(err); | |
is_err = FALSE; | |
goto out0; | |
} | |
JsonNode *const root = json_parser_get_root(parser); | |
if (root == NULL) | |
goto out1; | |
JsonArray *const arr_top = json_node_get_array(root); | |
if (root == NULL) | |
goto out1; | |
JsonNode *const node0 = json_array_get_element(arr_top, 0); | |
if (node0 == NULL) | |
goto out1; | |
JsonArray *const arr0 = json_node_get_array(node0); | |
if (arr0 == NULL) | |
goto out1; | |
JsonNode *const node1 = json_array_get_element(arr0, 0); | |
if (node1 == NULL) | |
goto out1; | |
JsonArray *const arr2 = json_node_get_array(node1); | |
if (arr2 == NULL) | |
goto out1; | |
JsonNode *const node_trg = json_array_get_element(arr2, 0); | |
if (node_trg == NULL) | |
goto out1; | |
trg_txt = json_node_get_string(node_trg); | |
if (trg_txt == NULL) | |
goto out1; | |
txtbuf_set_name(&self->txtbuf_trg, trg_txt); | |
ret = __moetr_parse_response_detail(trg_txt, root); | |
is_err = FALSE; | |
out1: | |
g_object_unref(parser); | |
out0: | |
if (is_err) | |
g_print("error occured: %s\n", "invalid response"); | |
return ret; | |
} | |
static void | |
moetr_translate(struct moetr *self) | |
{ | |
gtk_widget_grab_focus(self->txt_src); | |
if (gtk_text_buffer_get_char_count(txtbuf_get_body(&self->txtbuf_src)) == 0) | |
return; | |
guint id = gtk_drop_down_get_selected(GTK_DROP_DOWN(self->lst_src)); | |
if (lang_get_by_id(&self->lang_src, id) == NULL) | |
return; | |
/* "trg" always > 0 */ | |
id = gtk_drop_down_get_selected(GTK_DROP_DOWN(self->lst_trg)); | |
if (lang_get_by_id(&self->lang_trg, id + 1) == NULL) | |
return; | |
txtbuf_copy_body_to_name(&self->txtbuf_src); | |
char *const uri = moetr_build_request(self, txtbuf_get_name(&self->txtbuf_src)); | |
if (uri == NULL) | |
return; | |
self->is_ready = FALSE; | |
gtk_button_set_label(GTK_BUTTON(self->btn_translate), "Cancel"); | |
gtk_text_view_set_editable(GTK_TEXT_VIEW(self->txt_src), FALSE); | |
http_clear(&self->http); | |
http_request(&self->http, uri); | |
g_free(uri); | |
txtbuf_set_body_text(&self->txtbuf_trg, ""); | |
} | |
static void | |
moetr_write(struct moetr *self, const char resp_json[]) | |
{ | |
char *const res = __moetr_parse_response(self, resp_json); | |
if (res == NULL) { | |
txtbuf_set_body_text(&self->txtbuf_trg, ""); | |
} else { | |
txtbuf_set_body_text(&self->txtbuf_trg, g_strstrip(res)); | |
g_free(res); | |
} | |
// debug | |
g_print("%s\n\n", resp_json); | |
} | |
/* | |
* callbacks | |
*/ | |
static void | |
on_app_activate(GtkApplication *self, struct moetr *moe) | |
{ | |
moe->lst_src = gtk_drop_down_new_from_strings(langs); | |
moe->lst_trg = gtk_drop_down_new_from_strings(&langs[1]); | |
moe->btn_open = g_object_new(GTK_TYPE_BUTTON, | |
//"label", "Open", | |
"icon-name", "document-open", | |
"tooltip-text", "Open text file to be translated", | |
"margin-end", 10, | |
NULL); | |
moe->btn_switch = g_object_new(GTK_TYPE_BUTTON, | |
//"label", "Switch", | |
"icon-name", "media-playlist-repeat", | |
"tooltip-text", "Switch the languages", | |
NULL); | |
moe->btn_clear = g_object_new(GTK_TYPE_BUTTON, | |
//"label", "Clear", | |
"icon-name", "edit-clear", | |
"tooltip-text", "Clear", | |
NULL); | |
moe->btn_translate = g_object_new(GTK_TYPE_BUTTON, | |
"label", "Translate", | |
//"icon-name", "view-refresh", | |
"tooltip-text", "Translate now", | |
NULL); | |
moe->btn_speak_src = g_object_new(GTK_TYPE_BUTTON, | |
//"label", "<>", | |
"sensitive", FALSE, | |
"icon-name", "media-playback-start", | |
"tooltip-text", "Play audio of the source text", | |
NULL); | |
moe->btn_speak_trg = g_object_new(GTK_TYPE_BUTTON, | |
//"label", "<>", | |
"sensitive", FALSE, | |
"icon-name", "media-playback-start", | |
"tooltip-text", "Play audio of the target text", | |
NULL); | |
moe->txt_src = g_object_new(GTK_TYPE_TEXT_VIEW, | |
"buffer", txtbuf_get_body(&moe->txtbuf_src), | |
"wrap-mode", GTK_WRAP_WORD_CHAR, | |
"tooltip-text", "Input text to be translated", | |
"top-margin", 5, | |
"bottom-margin", 5, | |
"right-margin", 5, | |
"left-margin", 5, | |
NULL); | |
moe->txt_trg = g_object_new(GTK_TYPE_TEXT_VIEW, | |
"buffer", txtbuf_get_body(&moe->txtbuf_trg), | |
"wrap-mode", GTK_WRAP_WORD_CHAR, | |
"editable", FALSE, | |
"tooltip-text", "Translated text (not editable)", | |
"top-margin", 5, | |
"bottom-margin", 5, | |
"right-margin", 5, | |
"left-margin", 5, | |
NULL); | |
/* configs */ | |
gtk_widget_set_tooltip_text(moe->lst_src, "Source language list"); | |
gtk_widget_set_tooltip_text(moe->lst_trg, "Target language list"); | |
gtk_widget_set_margin_end(moe->lst_trg, 10); | |
/* box_btns */ | |
GtkBox *const box_btns = g_object_new(GTK_TYPE_BOX, | |
"spacing", 5, | |
"margin-top", 5, | |
"margin-bottom", 5, | |
"margin-start", 5, | |
"margin-end", 5, | |
"halign", GTK_ALIGN_CENTER, | |
NULL); | |
gtk_box_append(box_btns, moe->btn_speak_src); | |
gtk_box_append(box_btns, moe->btn_speak_trg); | |
gtk_box_append(box_btns, moe->btn_clear); | |
gtk_box_append(box_btns, moe->btn_translate); | |
/* scrolled window */ | |
void *const scr_trg = g_object_new(GTK_TYPE_SCROLLED_WINDOW, | |
"child", moe->txt_trg, | |
"margin-bottom", 3, | |
NULL); | |
void *const scr_src = g_object_new(GTK_TYPE_SCROLLED_WINDOW, | |
"child", moe->txt_src, | |
"margin-top", 3, | |
NULL); | |
/* paned */ | |
GtkPaned *const paned = g_object_new(GTK_TYPE_PANED, | |
"orientation", GTK_ORIENTATION_VERTICAL, | |
"start-child", scr_trg, | |
"end_child", scr_src, | |
"position", 300, | |
"margin-start", 5, | |
"margin-end", 5, | |
"vexpand", TRUE, | |
NULL); | |
/* box_rds */ | |
GtkBox *const box_rds = g_object_new(GTK_TYPE_BOX, | |
"halign", GTK_ALIGN_CENTER, | |
"margin-top", 5, | |
"margin-bottom", 5, | |
"margin-start", 5, | |
"margin-end", 5, | |
NULL); | |
gtk_box_append(box_rds, moe->btn_open); | |
gtk_box_append(box_rds, moe->lst_src); | |
gtk_box_append(box_rds, g_object_new(GTK_TYPE_LABEL, | |
"label", "->", | |
"margin-start", 1, | |
"margin-end", 1, | |
NULL)); | |
gtk_box_append(box_rds, moe->lst_trg); | |
gtk_box_append(box_rds, moe->btn_switch); | |
/* box_main */ | |
GtkBox *const box_main = g_object_new(GTK_TYPE_BOX, | |
"orientation", GTK_ORIENTATION_VERTICAL, | |
"spacing", 2, | |
NULL); | |
gtk_box_append(box_main, GTK_WIDGET(box_rds)); | |
gtk_box_append(box_main, GTK_WIDGET(paned)); | |
gtk_box_append(box_main, GTK_WIDGET(box_btns)); | |
/* set all widget signals */ | |
SIGNAL_CONNECT(txtbuf_get_body(&moe->txtbuf_trg), "changed", | |
on_txt_buffer_trg_changed, moe); | |
SIGNAL_CONNECT(txtbuf_get_body(&moe->txtbuf_src), "changed", | |
on_txt_buffer_src_changed, moe); | |
SIGNAL_CONNECT(moe->btn_open, "clicked", on_btn_open_clicked, moe); | |
SIGNAL_CONNECT(moe->btn_clear, "clicked", on_btn_clear_clicked, moe); | |
SIGNAL_CONNECT(moe->btn_translate, "clicked", on_btn_translate_clicked, moe); | |
SIGNAL_CONNECT(moe->btn_switch, "clicked", on_btn_switch_clicked, moe); | |
SIGNAL_CONNECT(moe->btn_speak_src, "clicked", on_btn_speak_src_clicked, moe); | |
SIGNAL_CONNECT(moe->btn_speak_trg, "clicked", on_btn_speak_trg_clicked, moe); | |
/* setup the window */ | |
GtkWindow *const window = GTK_WINDOW(gtk_application_window_new(self)); | |
gtk_window_set_child(window, GTK_WIDGET(box_main)); | |
gtk_window_set_title(window, DEFAULT_WINDOW_TITLE); | |
gtk_window_set_default_size(window, DEFAULT_WINDOW_W, DEFAULT_WINDOW_H); | |
gtk_window_present(window); | |
moe->is_ready = TRUE; | |
gtk_widget_grab_focus(moe->txt_src); | |
} | |
static void | |
on_txt_buffer_trg_changed(GtkTextBuffer *self, struct moetr *moe) | |
{ | |
if (gtk_text_buffer_get_char_count(self) == 0) | |
gtk_widget_set_sensitive(moe->btn_speak_trg, FALSE); | |
else | |
gtk_widget_set_sensitive(moe->btn_speak_trg, TRUE); | |
} | |
static void | |
on_txt_buffer_src_changed(GtkTextBuffer *self, struct moetr *moe) | |
{ | |
if (gtk_text_buffer_get_char_count(self) == 0) | |
gtk_widget_set_sensitive(moe->btn_speak_src, FALSE); | |
else | |
gtk_widget_set_sensitive(moe->btn_speak_src, TRUE); | |
} | |
static void | |
on_btn_open_clicked(GtkButton *self, struct moetr *moe) | |
{ | |
gtk_widget_grab_focus(moe->txt_src); | |
if (moe->is_ready == FALSE) | |
return; | |
(void)self; | |
} | |
static void | |
on_btn_switch_clicked(GtkButton *self, struct moetr *moe) | |
{ | |
gtk_widget_grab_focus(moe->txt_src); | |
if (moe->is_ready == FALSE) | |
return; | |
guint src = gtk_drop_down_get_selected(GTK_DROP_DOWN(moe->lst_src)); | |
if (src == 0) | |
return; | |
src--; | |
guint trg = gtk_drop_down_get_selected(GTK_DROP_DOWN(moe->lst_trg)); | |
if (src == trg) | |
return; | |
trg++; | |
gtk_drop_down_set_selected(GTK_DROP_DOWN(moe->lst_src), trg); | |
gtk_drop_down_set_selected(GTK_DROP_DOWN(moe->lst_trg), src); | |
const char *const name = g_strstrip(txtbuf_get_name(&moe->txtbuf_trg)); | |
txtbuf_set_body_text(&moe->txtbuf_src, name); | |
txtbuf_set_name(&moe->txtbuf_src, name); | |
moetr_translate(moe); | |
(void)self; | |
} | |
static void | |
on_btn_clear_clicked(GtkButton *self, struct moetr *moe) | |
{ | |
if (moe->is_ready == FALSE) | |
return; | |
txtbuf_set_body_text(&moe->txtbuf_src, ""); | |
txtbuf_set_name(&moe->txtbuf_src, ""); | |
txtbuf_set_body_text(&moe->txtbuf_trg, ""); | |
txtbuf_set_name(&moe->txtbuf_trg, ""); | |
gtk_widget_grab_focus(moe->txt_src); | |
(void)self; | |
} | |
static void | |
on_btn_translate_clicked(GtkButton *self, struct moetr *moe) | |
{ | |
if (moe->is_ready) | |
moetr_translate(moe); | |
else | |
http_request_cancel(&moe->http); | |
(void)self; | |
} | |
static void | |
on_btn_speak_src_clicked(GtkButton *self, struct moetr *moe) | |
{ | |
gtk_widget_grab_focus(moe->txt_src); | |
if (moe->is_ready == FALSE) | |
return; | |
(void)self; | |
} | |
static void | |
on_btn_speak_trg_clicked(GtkButton *self, struct moetr *moe) | |
{ | |
gtk_widget_grab_focus(moe->txt_src); | |
if (moe->is_ready == FALSE) | |
return; | |
(void)self; | |
} | |
static void | |
on_http_response_ready(GObject *self, GAsyncResult *res, void *udata) | |
{ | |
GError *err = NULL; | |
struct http *const http = (struct http *)udata; | |
GBytes *const resp = soup_session_send_and_read_finish(SOUP_SESSION(self), res, &err); | |
SoupMessageHeaders *const hdrs = soup_message_get_response_headers(http->message); | |
const char *const content_type = soup_message_headers_get_content_type(hdrs, NULL); | |
if (err != NULL) | |
goto out0; | |
if (!content_type || g_ascii_strcasecmp(content_type, "application/json") != 0) | |
err = g_error_new(1, 1, "invalid response type"); | |
out0: | |
http->callback(http->callback_udata, resp, err); | |
} | |
static void | |
on_moetr_http_response(void *udata, GBytes *resp, GError *err) | |
{ | |
struct moetr *const moe = (struct moetr *)udata; | |
if (err != NULL) { | |
g_print("error occured: %s\n", err->message); | |
txtbuf_set_body_text(&moe->txtbuf_trg, ""); | |
g_error_free(err); | |
} else { | |
moetr_write(moe, g_bytes_get_data(resp, NULL)); | |
} | |
gtk_widget_grab_focus(moe->txt_src); | |
g_bytes_unref(resp); | |
moe->is_ready = TRUE; | |
gtk_button_set_label(GTK_BUTTON(moe->btn_translate), "Translate"); | |
gtk_text_view_set_editable(GTK_TEXT_VIEW(moe->txt_src), TRUE); | |
} | |
/* | |
* main | |
*/ | |
int | |
main(int argc, char *argv[]) | |
{ | |
struct moetr moe; | |
moetr_init(&moe); | |
const int ret = moetr_run(&moe, argc, argv); | |
moetr_deinit(&moe); | |
return ret; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment