diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/PYPCloudCandidates.cc | 694 | ||||
-rw-r--r-- | src/PYPCloudCandidates.h | 99 |
2 files changed, 793 insertions, 0 deletions
diff --git a/src/PYPCloudCandidates.cc b/src/PYPCloudCandidates.cc new file mode 100644 index 0000000..6be6564 --- /dev/null +++ b/src/PYPCloudCandidates.cc @@ -0,0 +1,694 @@ +/* vim:set et ts=4 sts=4: + * + * ibus-libpinyin - Intelligent Pinyin engine based on libpinyin for IBus + * + * Copyright (c) 2018 linyu Xu <liannaxu07@gmail.com> + * Copyright (c) 2020 Weixuan XIAO <veyx.shaw@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "PYPCloudCandidates.h" +#include "PYString.h" +#include "PYConfig.h" +#include "PYPPhoneticEditor.h" +#include "PYPPinyinEditor.h" + +#include <assert.h> +#include <pinyin.h> +#include <cstring> +#include <glib.h> + + +using namespace PY; + +enum CandidateResponseParserError { + PARSER_NOERR, + PARSER_INVALID_DATA, + PARSER_BAD_FORMAT, + PARSER_NO_CANDIDATE, + PARSER_NETWORK_ERROR, + PARSER_UNKNOWN +}; + +static const std::string CANDIDATE_CLOUD_PREFIX = "☁"; + +static const std::string CANDIDATE_PENDING_TEXT_WITHOUT_PREFIX = "[⏱]"; +static const std::string CANDIDATE_LOADING_TEXT_WITHOUT_PREFIX = "..."; +static const std::string CANDIDATE_NO_CANDIDATE_TEXT_WITHOUT_PREFIX = "[🚫]"; +static const std::string CANDIDATE_INVALID_DATA_TEXT_WITHOUT_PREFIX = "[❌]"; +static const std::string CANDIDATE_BAD_FORMAT_TEXT_WITHOUT_PREFIX = "[❓]"; + +static const std::string CANDIDATE_PENDING_TEXT = CANDIDATE_CLOUD_PREFIX + CANDIDATE_PENDING_TEXT_WITHOUT_PREFIX; +static const std::string CANDIDATE_LOADING_TEXT = CANDIDATE_CLOUD_PREFIX + CANDIDATE_LOADING_TEXT_WITHOUT_PREFIX; +static const std::string CANDIDATE_NO_CANDIDATE_TEXT = CANDIDATE_CLOUD_PREFIX + CANDIDATE_NO_CANDIDATE_TEXT_WITHOUT_PREFIX; +static const std::string CANDIDATE_INVALID_DATA_TEXT = CANDIDATE_CLOUD_PREFIX + CANDIDATE_INVALID_DATA_TEXT_WITHOUT_PREFIX ; +static const std::string CANDIDATE_BAD_FORMAT_TEXT = CANDIDATE_CLOUD_PREFIX + CANDIDATE_BAD_FORMAT_TEXT_WITHOUT_PREFIX; + +static const char *BAIDU_URL_TEMPLATE = "http://olime.baidu.com/py?input=%s&inputtype=py&bg=0&ed=%d&result=hanzi&resultcoding=utf-8&ch_en=1&clientinfo=web&version=1"; +static const char *GOOGLE_URL_TEMPLATE = "https://www.google.com/inputtools/request?ime=pinyin&text=%s&num=%d"; + +typedef struct +{ + guint event_id; + gchar request_str[MAX_PINYIN_LEN + 1]; + CloudCandidates *cloud_candidates; +} DelayedCloudAsyncRequestCallbackUserData; + +class CloudCandidatesResponseParser +{ +public: + CloudCandidatesResponseParser () : m_annotation (NULL) {} + virtual ~CloudCandidatesResponseParser () {} + + virtual guint parse (GInputStream *stream) = 0; + + virtual std::vector<std::string> &getStringCandidates () { return m_candidates; } + virtual const gchar *getAnnotation () { return m_annotation; } + +protected: + std::vector<std::string> m_candidates; + const gchar *m_annotation; +}; + +class CloudCandidatesResponseJsonParser : public CloudCandidatesResponseParser +{ +public: + CloudCandidatesResponseJsonParser (); + virtual ~CloudCandidatesResponseJsonParser (); + + guint parse (GInputStream *stream) + { + GError **error = NULL; + + if (!stream) + return PARSER_NETWORK_ERROR; + + /* parse Json from input steam */ + if (!json_parser_load_from_stream (m_parser, stream, NULL, error) || error != NULL) { + g_input_stream_close (stream, NULL, error); /* Close stream to release libsoup connexion */ + return PARSER_BAD_FORMAT; + } + g_input_stream_close (stream, NULL, error); /* Close stream to release libsoup connexion */ + + return parseJsonResponse (json_parser_get_root (m_parser)); + } + +protected: + JsonParser *m_parser; + + virtual guint parseJsonResponse (JsonNode *root) = 0; +}; + +class GoogleCloudCandidatesResponseJsonParser : public CloudCandidatesResponseJsonParser +{ +protected: + guint parseJsonResponse (JsonNode *root) + { + /* clear the last result */ + m_candidates.clear (); + + if (!JSON_NODE_HOLDS_ARRAY (root)) + return PARSER_BAD_FORMAT; + + /* validate Google source and the structure of response */ + JsonArray *google_root_array = json_node_get_array (root); + + /** + * declare variables to refer to structures in the result + * a typical cloud candidate response from Google is: + * [ <- google_root_array + * "SUCCESS", <- google_response_status + * [ <- google_response_array + * [ <- google_result_array + * "ceshi", <- google_candidate_annotation + * ["测试"], <- google_candidate_array + * [], + * { + * "annotation":["ce shi"], + * "candidate_type":[0], + * "lc":["16 16"] + * } + * ] <- google_result_array end + * ] <- google_response_array end + * ] <- google_root_array end + */ + const gchar *google_response_status; + JsonArray *google_response_array; + JsonArray *google_result_array; + const gchar *google_candidate_annotation; + JsonArray *google_candidate_array; + guint result_counter; + + /* validate google_root_array length */ + if (json_array_get_length (google_root_array) <= 1) + return PARSER_INVALID_DATA; + + /* get and validate google_response_status */ + google_response_status = json_array_get_string_element (google_root_array, 0); + if (g_strcmp0 (google_response_status, "SUCCESS")) + return PARSER_INVALID_DATA; + + /* get google_response_array */ + google_response_array = json_array_get_array_element (google_root_array, 1); + + /* validate google_response_array length */ + if (json_array_get_length (google_response_array) < 1) + return PARSER_INVALID_DATA; + + /* get google_result_array */ + google_result_array = json_array_get_array_element (google_response_array, 0); + + /* get and validate google_candidate_annotation */ + google_candidate_annotation = json_array_get_string_element (google_result_array, 0); + if (!google_candidate_annotation) + return PARSER_INVALID_DATA; + + /* update annotation with the parsed annotation */ + m_annotation = google_candidate_annotation; + + /* get google_candidate_array */ + google_candidate_array = json_array_get_array_element (google_result_array, 1); + + /* there should be at least one candidate */ + result_counter = json_array_get_length (google_candidate_array); + if (result_counter < 1) + return PARSER_NO_CANDIDATE; + + /* put all parsed candidates into m_candidates array of parser instance */ + for (guint i = 0; i < result_counter; ++i) { + std::string candidate = json_array_get_string_element (google_candidate_array, i); + m_candidates.push_back (candidate); + } + + return PARSER_NOERR; + } + +public: + GoogleCloudCandidatesResponseJsonParser () : CloudCandidatesResponseJsonParser () {} +}; + +class BaiduCloudCandidatesResponseJsonParser : public CloudCandidatesResponseJsonParser +{ +private: + guint parseJsonResponse (JsonNode *root) + { + /* clear the last result */ + m_candidates.clear (); + + if (!JSON_NODE_HOLDS_OBJECT (root)) + return PARSER_BAD_FORMAT; + + /* validate Baidu source and the structure of response */ + JsonObject *baidu_root_object = json_node_get_object (root); + + /** + * declare variables to refer to structures in the result + * a typical cloud candidate response from Baidu is: + * { <- baidu_root_object + * "errmsg":"", + * "errno":"0", + * "result": + * [ <- baidu_result_array + * [ <- baidu_candidate_array + * [ <- baidu_candidate + * "测试", + * 5, + * { + * "pinyin":"ce'shi", + * "type":"IMEDICT" + * } + * ] <- baidu_candidate end + * ], <- baidu_candidate_array end + * "ce'shi" <- baidu_candidate_annotation + * ], <- baidu_result_array end + * "status":"T" <- baidu_response_status + * } <- baidu_root_object end + */ + const gchar *baidu_response_status; + JsonArray *baidu_result_array; + JsonArray *baidu_candidate_array; + const gchar *baidu_candidate_annotation; + guint result_counter; + + /* get and validate baidu_response_status */ + if (!json_object_has_member (baidu_root_object, "status")) + return PARSER_INVALID_DATA; + baidu_response_status = json_object_get_string_member (baidu_root_object, "status"); + if (g_strcmp0 (baidu_response_status, "T")) + return PARSER_INVALID_DATA; + + /* get baidu_result_array */ + if (!json_object_has_member (baidu_root_object, "result")) + return PARSER_INVALID_DATA; + baidu_result_array = json_object_get_array_member (baidu_root_object, "result"); + + /* get baidu_candidate_array and baidu_candidate_annotation */ + if (json_array_get_length (baidu_result_array) < 2) + return PARSER_INVALID_DATA; + baidu_candidate_array = json_array_get_array_element (baidu_result_array, 0); + baidu_candidate_annotation = json_array_get_string_element (baidu_result_array, 1); + + /* validate baidu_candidate_annotation */ + if (!baidu_candidate_annotation) + return PARSER_INVALID_DATA; + + /* update annotation with the returned annotation */ + m_annotation = baidu_candidate_annotation; + + /* there should be at least one candidate */ + result_counter = json_array_get_length (baidu_candidate_array); + if (result_counter < 1) + return PARSER_NO_CANDIDATE; + + /* visit all baidu_candidate */ + for (guint i = 0; i < result_counter; ++i) { + std::string candidate; + JsonArray *baidu_candidate = json_array_get_array_element (baidu_candidate_array, i); + + /* confirm the candidate exists in this baidu_candidate */ + if (json_array_get_length (baidu_candidate) < 1) + candidate = CANDIDATE_INVALID_DATA_TEXT_WITHOUT_PREFIX; + else + candidate = json_array_get_string_element (baidu_candidate, 0); + + /* put all parsed candidates into m_candidates array of parser instance */ + m_candidates.push_back (candidate); + } + + return PARSER_NOERR; + } + +public: + BaiduCloudCandidatesResponseJsonParser () : CloudCandidatesResponseJsonParser () {} + ~BaiduCloudCandidatesResponseJsonParser () { if (m_annotation) g_free ((gpointer)m_annotation); } +}; + +gboolean +CloudCandidates::delayedCloudAsyncRequestCallBack (gpointer user_data) +{ + DelayedCloudAsyncRequestCallbackUserData *data = static_cast<DelayedCloudAsyncRequestCallbackUserData *> (user_data); + CloudCandidates *cloudCandidates; + + if (!data) + return FALSE; + + cloudCandidates = data->cloud_candidates; + + if (!cloudCandidates) + return FALSE; + + /* only send with a latest timer */ + if (data->event_id == cloudCandidates->m_source_event_id) { + cloudCandidates->m_source_event_id = 0; + cloudCandidates->cloudAsyncRequest (data->request_str); + } + + return FALSE; +} + +void +CloudCandidates::delayedCloudAsyncRequestDestroyCallBack (gpointer user_data) +{ + /* clean up */ + if (user_data) + g_free (user_data); +} + +CloudCandidates::CloudCandidates (PhoneticEditor * editor) : m_input_mode(FullPinyin) +{ + m_session = soup_session_new (); + m_editor = editor; + + m_source_event_id = 0; + m_message = NULL; + + m_baidu_parser = new BaiduCloudCandidatesResponseJsonParser (); + m_google_parser = new GoogleCloudCandidatesResponseJsonParser (); +} + +CloudCandidates::~CloudCandidates () +{ + if (m_source_event_id != 0) { + g_source_remove (m_source_event_id); + m_source_event_id = 0; + } + + if (m_message) { + soup_session_cancel_message (m_session, m_message, SOUP_STATUS_CANCELLED); + m_message = NULL; + } + + if (m_session) { + g_object_unref (m_session); + m_session = NULL; + } + + if (m_baidu_parser) { + delete m_baidu_parser; + m_baidu_parser = NULL; + } + + if (m_google_parser) { + delete m_google_parser; + m_google_parser = NULL; + } +} + +gboolean +CloudCandidates::processCandidates (std::vector<EnhancedCandidate> & candidates) +{ + /* refer pinyin retrieved in full pinyin mode */ + String full_pinyin_text; + + /* find the first position after n-gram candidates */ + std::vector<EnhancedCandidate>::iterator first_pos; + + /* check the length of candidates */ + if (0 == candidates.size ()) + return FALSE; /* no candidate */ + + const String & display_string = candidates[0].m_display_string; + if (display_string.utf8Length () < CLOUD_MINIMUM_UTF8_TRIGGER_LENGTH) { + m_last_requested_pinyin = ""; + return FALSE; /* do not request because there is only one character */ + } + + /* search the first non-ngram candidate */ + for (first_pos = candidates.begin (); first_pos != candidates.end (); ++first_pos) { + if (CANDIDATE_NBEST_MATCH != first_pos->m_candidate_type) + break; + } + + /* neither double pinyin mode nor bopomofo mode */ + if (m_input_mode == FullPinyin) + full_pinyin_text = m_editor->m_text; + else + full_pinyin_text = getFullPinyin (); + + if (m_last_requested_pinyin == full_pinyin_text) { + /* do not request again and update cached ones */ + + for (int i = 0; i < m_candidates.size (); ++i){ + EnhancedCandidate candidate = m_candidates[i]; + /* insert cloud prefix */ + candidate.m_display_string = CANDIDATE_CLOUD_PREFIX + candidate.m_display_string; + candidates.insert (first_pos + i, candidate); + } + return TRUE; + } + + /* have cloud candidates already */ + if (first_pos->m_candidate_type == CANDIDATE_CLOUD_INPUT) + return FALSE; + + /* insert cloud candidates' placeholders */ + m_candidates.clear (); + for (guint i = 0; i < m_editor->m_config.cloudCandidatesNumber (); ++i) { + EnhancedCandidate enhanced; + enhanced.m_candidate_id = i; + enhanced.m_display_string = CANDIDATE_PENDING_TEXT; + enhanced.m_candidate_type = CANDIDATE_CLOUD_INPUT; + m_candidates.push_back (enhanced); + } + candidates.insert (first_pos, m_candidates.begin (), m_candidates.end ()); + + delayedCloudAsyncRequest (full_pinyin_text); + + return TRUE; +} + +int +CloudCandidates::selectCandidate (EnhancedCandidate & enhanced) +{ + assert (CANDIDATE_CLOUD_INPUT == enhanced.m_candidate_type); + + if (enhanced.m_display_string == CANDIDATE_PENDING_TEXT || + enhanced.m_display_string == CANDIDATE_LOADING_TEXT || + enhanced.m_display_string == CANDIDATE_BAD_FORMAT_TEXT || + enhanced.m_display_string == CANDIDATE_INVALID_DATA_TEXT) + return SELECT_CANDIDATE_ALREADY_HANDLED; + + if (enhanced.m_candidate_id < m_candidates.size ()) { + enhanced.m_display_string = + m_candidates[enhanced.m_candidate_id].m_display_string; + + /* modify in-place and commit */ + return SELECT_CANDIDATE_COMMIT | SELECT_CANDIDATE_MODIFY_IN_PLACE; + } + + return SELECT_CANDIDATE_ALREADY_HANDLED; +} + +void +CloudCandidates::delayedCloudAsyncRequest (const gchar* request_str) +{ + gpointer user_data; + DelayedCloudAsyncRequestCallbackUserData *data; + + /* cancel the latest timer, if applied */ + if (m_source_event_id != 0) + g_source_remove (m_source_event_id); + + /* allocate memory for a DelayedCloudAsyncRequestCallbackUserData instance to take more callback user data */ + user_data = g_malloc (sizeof(DelayedCloudAsyncRequestCallbackUserData)); + data = static_cast<DelayedCloudAsyncRequestCallbackUserData *> (user_data); + + strncpy (data->request_str, request_str, MAX_PINYIN_LEN); + data->request_str[MAX_PINYIN_LEN] = '\0'; + data->cloud_candidates = this; + + /* record the latest timer */ + m_source_event_id = g_timeout_add_full (G_PRIORITY_DEFAULT, + m_editor->m_config.cloudRequestDelayTime (), + delayedCloudAsyncRequestCallBack, + user_data, + delayedCloudAsyncRequestDestroyCallBack); + data->event_id = m_source_event_id; +} + +void +CloudCandidates::cloudAsyncRequest (const gchar* request_str) +{ + GError **error = NULL; + gchar *query_request; + guint cloud_source = m_editor->m_config.cloudInputSource (); + guint cloud_candidates_number = m_editor->m_config.cloudCandidatesNumber (); + + if (cloud_source == BAIDU) + query_request= g_strdup_printf (BAIDU_URL_TEMPLATE, request_str, cloud_candidates_number); + else if (cloud_source == GOOGLE) + query_request= g_strdup_printf (GOOGLE_URL_TEMPLATE, request_str, cloud_candidates_number); + + /* cancel message if there is a pending one */ + if (m_message) + soup_session_cancel_message (m_session, m_message, SOUP_STATUS_CANCELLED); + + SoupMessage *msg = soup_message_new ("GET", query_request); + soup_session_send_async (m_session, msg, NULL, cloudResponseCallBack, static_cast<gpointer> (this)); + m_message = msg; + + /* cache the last request string */ + m_last_requested_pinyin = request_str; + + /* update loading text to replace pending text */ + for (std::vector<EnhancedCandidate>::iterator pos = m_candidates.begin (); pos != m_candidates.end (); ++pos) { + pos->m_display_string = CANDIDATE_LOADING_TEXT_WITHOUT_PREFIX; + } + + /* only update lookup table when there is still pinyin text */ + if (m_editor->m_text.utf8Length () >= CLOUD_MINIMUM_UTF8_TRIGGER_LENGTH) + updateLookupTable (); + + /* free url string */ + if (query_request) + g_free(query_request); +} + +void +CloudCandidates::cloudResponseCallBack (GObject *source_object, GAsyncResult *result, gpointer user_data) +{ + GError **error = NULL; + GInputStream *stream = soup_session_send_finish (SOUP_SESSION (source_object), result, error); + CloudCandidates *cloudCandidates = static_cast<CloudCandidates *> (user_data); + + /* process results */ + cloudCandidates->processCloudResponse (stream, cloudCandidates->m_editor->m_candidates); + + /* only update lookup table when there is still pinyin text */ + if (cloudCandidates->m_editor->m_text.utf8Length () >= + CLOUD_MINIMUM_UTF8_TRIGGER_LENGTH) { + cloudCandidates->updateLookupTable (); + + /* clean up message */ + cloudCandidates->m_message = NULL; + } +} + +void +CloudCandidates::cloudSyncRequest (const gchar* request_str, std::vector<EnhancedCandidate> & candidates) +{ + GError **error = NULL; + gchar *queryRequest; + guint cloud_source = m_editor->m_config.cloudInputSource (); + guint cloud_candidates_number = m_editor->m_config.cloudCandidatesNumber (); + + if (cloud_source == BAIDU) + queryRequest= g_strdup_printf (BAIDU_URL_TEMPLATE, request_str, cloud_candidates_number); + else if (cloud_source == GOOGLE) + queryRequest= g_strdup_printf (GOOGLE_URL_TEMPLATE, request_str, cloud_candidates_number); + SoupMessage *msg = soup_message_new ("GET", queryRequest); + + GInputStream *stream = soup_session_send (m_session, msg, NULL, error); + + processCloudResponse (stream, candidates); +} + +void +CloudCandidates::processCloudResponse (GInputStream *stream, std::vector<EnhancedCandidate> & candidates) +{ + guint retval; + CloudCandidatesResponseJsonParser *parser = NULL; + String text; + gchar annotation[MAX_PINYIN_LEN + 1]; + guint cloud_source = m_editor->m_config.cloudInputSource (); + + if (cloud_source == BAIDU) + parser = m_baidu_parser; + else if (cloud_source == GOOGLE) + parser = m_google_parser; + + retval = parser->parse (stream); + + /* no annotation if there is NETWORK_ERROR, process before detecting parsed annotation */ + if (retval == PARSER_NETWORK_ERROR) { + for (std::vector<EnhancedCandidate>::iterator pos = m_candidates.begin (); pos != m_candidates.end (); ++pos) { + pos->m_display_string = CANDIDATE_INVALID_DATA_TEXT_WITHOUT_PREFIX; + } + } + + if (parser->getAnnotation ()) + strncpy (annotation, parser->getAnnotation (), MAX_PINYIN_LEN); + else /* the request might have been cancelled */ + return; + + if (m_input_mode == FullPinyin) { + /* get current text in editor */ + text = m_editor->m_text; + } else { + /* get current text */ + text = getFullPinyin (); + } + + /* ignore annotation match while using Baidu source */ + if (cloud_source == BAIDU || !g_strcmp0 (annotation, text)) { + if (retval == PARSER_NOERR) { + /* update to the cached candidates list */ + std::vector<std::string> &updated = parser->getStringCandidates (); + assert (m_candidates.size () == updated.size ()); + + for (guint i = 0; i < m_candidates.size (); ++i) { + /* cache candidate without prefix in m_candidates */ + m_candidates[i].m_display_string = updated[i]; + } + } + else { + String display_text; + + switch (retval) { + case PARSER_NO_CANDIDATE: + display_text = CANDIDATE_NO_CANDIDATE_TEXT_WITHOUT_PREFIX; + break; + case PARSER_INVALID_DATA: + display_text = CANDIDATE_INVALID_DATA_TEXT_WITHOUT_PREFIX; + break; + case PARSER_BAD_FORMAT: + display_text = CANDIDATE_BAD_FORMAT_TEXT_WITHOUT_PREFIX; + break; + } + + for (std::vector<EnhancedCandidate>::iterator pos = m_candidates.begin (); pos != m_candidates.end (); ++pos) { + pos->m_display_string = display_text; + } + } + } +} + +void +CloudCandidates::updateLookupTable () +{ + /* retrieve cursor position in lookup table */ + guint cursor = m_editor->m_lookup_table.cursorPos (); + + /* update cached cloud input candidates */ + m_editor->updateCandidates (); + + /* regenerate lookup table */ + m_editor->m_lookup_table.clear (); + m_editor->fillLookupTable (); + + /* recover cursor position in lookup table */ + m_editor->m_lookup_table.setCursorPos (cursor); + + /* notify ibus */ + m_editor->updateLookupTableFast (); +} + +String +CloudCandidates::getFullPinyin () +{ + String buffer; + + gchar * aux_text = NULL; + gchar * pinyin_text = NULL; + gchar * pinyin_text_with_quote = NULL; + gchar** tempArray = NULL; + + /* get full pinyin auxiliary text */ + pinyin_get_full_pinyin_auxiliary_text (m_editor->m_instance, m_editor->m_cursor, &aux_text); + + /* remove tone and cursor */ + tempArray = g_strsplit_set (aux_text, "|12345", -1); + pinyin_text = g_strjoinv ("", tempArray); + g_strfreev (tempArray); + + /* remove space */ + pinyin_text = g_strstrip(pinyin_text); + + /* replace space with quote */ + tempArray = g_strsplit_set (pinyin_text, " ", -1); + pinyin_text_with_quote = g_strjoinv ("'", tempArray); + buffer << pinyin_text_with_quote; + g_strfreev (tempArray); + + /* free */ + g_free(aux_text); + g_free(pinyin_text); + g_free(pinyin_text_with_quote); + + return buffer; +} + +CloudCandidatesResponseJsonParser::CloudCandidatesResponseJsonParser () : m_parser (NULL) +{ + m_parser = json_parser_new (); +} + +CloudCandidatesResponseJsonParser::~CloudCandidatesResponseJsonParser () +{ + /* free json parser object if necessary */ + if (m_parser) + g_object_unref (m_parser); +} diff --git a/src/PYPCloudCandidates.h b/src/PYPCloudCandidates.h new file mode 100644 index 0000000..aaccdba --- /dev/null +++ b/src/PYPCloudCandidates.h @@ -0,0 +1,99 @@ +/* vim:set et ts=4 sts=4: + * + * ibus-libpinyin - Intelligent Pinyin engine based on libpinyin for IBus + * + * Copyright (c) 2018 linyu Xu <liannaxu07@gmail.com> + * Copyright (c) 2020 Weixuan XIAO <veyx.shaw@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __PY_LIB_PINYIN_ClOUD_CANDIDATES_H_ +#define __PY_LIB_PINYIN_ClOUD_CANDIDATES_H_ + +#include "PYString.h" +#include "PYPointer.h" +#include "PYPEnhancedCandidates.h" +#include <vector> +#include <libsoup/soup.h> +#include <json-glib/json-glib.h> +#include "PYConfig.h" + + + +class BaiduCloudCandidatesResponseJsonParser; +class GoogleCloudCandidatesResponseJsonParser; + +namespace PY { + +#define BUFFERLENGTH 2048 +#define CLOUD_MINIMUM_UTF8_TRIGGER_LENGTH 2 + +enum InputMode { + FullPinyin = 0, + DoublePinyin, + Bopomofo +}; + +class PhoneticEditor; + +class CloudCandidates : public EnhancedCandidates<PhoneticEditor> +{ +public: + + CloudCandidates (PhoneticEditor *editor); + ~CloudCandidates(); + + void setInputMode (InputMode mode) { m_input_mode = mode; } + + gboolean processCandidates (std::vector<EnhancedCandidate> & candidates); + + int selectCandidate (EnhancedCandidate & enhanced); + + void cloudAsyncRequest (const gchar* requestStr); + void cloudSyncRequest (const gchar* requestStr, std::vector<EnhancedCandidate> & candidates); + + void delayedCloudAsyncRequest (const gchar* requestStr); + + void updateLookupTable (); + + guint m_source_event_id; + SoupMessage *m_message; + std::string m_last_requested_pinyin; + +private: + static gboolean delayedCloudAsyncRequestCallBack (gpointer user_data); + static void delayedCloudAsyncRequestDestroyCallBack (gpointer user_data); + static void cloudResponseCallBack (GObject *object, GAsyncResult *result, gpointer user_data); + + void processCloudResponse (GInputStream *stream, std::vector<EnhancedCandidate> & candidates); + + /* get internal full pinyin representation */ + String getFullPinyin (); +private: + SoupSession *m_session; + InputMode m_input_mode; + + BaiduCloudCandidatesResponseJsonParser *m_baidu_parser; + GoogleCloudCandidatesResponseJsonParser *m_google_parser; +protected: + std::vector<EnhancedCandidate> m_candidates; +}; + +}; + +#endif + + |