/* This file is part of anastasis-gtk. Copyright (C) 2021 Anastasis SARL Anastasis 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 3, or (at your option) any later version. Anastasis 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 Anastasis; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file src/anastasis/anastasis-gtk_autocomplete.c * @brief Autocomplete logic for GtkEntry widgets * @author Christian Grothoff */ #include #include "anastasis-gtk_attributes.h" #include "anastasis-gtk_dispatch.h" #include "anastasis-gtk_helper.h" #include /** * Lookup autocompletion string for @a editable widget. * * @param editable widget to lookup * @return NULL if not found */ static const char * lookup_autocomplete (GtkEditable *editable) { json_t *a; size_t index; json_t *ra = json_object_get (AG_redux_state, "required_attributes"); if (NULL == ra) { GNUNET_break (0); return NULL; } json_array_foreach (ra, index, a) { const char *autocomplete = NULL; const char *widget_name = NULL; const char *type = NULL; char *data_name; bool match; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("widget", &widget_name), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("autocomplete", &autocomplete), NULL), GNUNET_JSON_spec_string ("type", &type), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (a, spec, NULL, NULL)) { GNUNET_break (0); continue; } if ( (NULL == autocomplete) || (NULL == widget_name) ) continue; data_name = AG_expand_name (widget_name, type); match = (editable == GTK_EDITABLE (GCG_get_main_window_object (data_name))); GNUNET_free (data_name); if (match) return autocomplete; } GNUNET_break (0); return NULL; } /** * Function called from an auto-completed entry widget up on upon text * deletion. * * @param editable the widget * @param start_pos starting position * @param end_pos end position * @param user_data unused */ void anastasis_gtk_autocomplete_delete_text (GtkEditable *editable, int start_pos, int end_pos, void *user_data) { const char *ac; (void) user_data; ac = lookup_autocomplete (editable); if (NULL == ac) return; if (0 == start_pos) return; #if CONFUSING_UX_DESIRED if (ac[start_pos] != '?') { g_signal_stop_emission_by_name (editable, "delete-text"); gtk_editable_delete_text (editable, start_pos - 1, end_pos); } #endif } /** * Function called from an auto-completed widget upon text insertion. * * @param editable the widget * @param new_text inserted text * @param new_text_length number of bytes in @a new_text * @param position insertion position * @param user_data unused */ void anastasis_gtk_autocomplete_insert_text (GtkEditable *editable, const char *new_text, int new_text_length, gpointer position, void *user_data) { const char *ac; gint pos; char *replacement; (void) user_data; ac = lookup_autocomplete (editable); if (NULL == ac) return; pos = gtk_editable_get_position (editable); if (strlen (ac) <= pos + new_text_length) return; if (ac[pos + new_text_length] != '?') { g_signal_stop_emission_by_name (editable, "insert-text"); GNUNET_asprintf (&replacement, "%.*s%c", new_text_length, new_text, ac[pos + new_text_length]); gtk_editable_insert_text (editable, replacement, -1, position); GNUNET_free (replacement); } } /** * Function called from an auto-completed PIN widget upon text insertion. * * @param editable the widget * @param new_text inserted text * @param new_text_length number of bytes in @a new_text * @param position insertion position * @param user_data unused */ void anastasis_gtk_pin_autocomplete_insert_text (GtkEditable *editable, const char *new_text, int new_text_length, gpointer position, void *user_data) { /* Use \? to break up trigraphs */ const char *acn = "????\?-??\?-???\?-??\?"; const char *aca = "A-????\?-??\?-???\?-??\?"; const char *ac; gint pos; char *replacement; gchar *pfx; (void) user_data; pfx = gtk_editable_get_chars (editable, 0, 2); if (0 == strncasecmp (pfx, "A-", 2)) ac = aca; else ac = acn; g_free (pfx); pos = gtk_editable_get_position (editable); if (strlen (ac) <= pos + new_text_length) return; if (ac[pos + new_text_length] != '?') { g_signal_stop_emission_by_name (editable, "insert-text"); GNUNET_asprintf (&replacement, "%.*s%c", new_text_length, new_text, ac[pos + new_text_length]); gtk_editable_insert_text (editable, replacement, -1, position); GNUNET_free (replacement); } }