summary refs log tree commit diff
path: root/src/nmtui/nmt-utils.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2026-07-03 19:53:18 +0200
committerMichael Biebl <biebl@debian.org>2026-07-03 19:53:18 +0200
commit537bfce2bda471c92caabd388589230200891509 (patch)
treeaedeccfaf0ba52c238ecf51fc009c0db5d4b60f0 /src/nmtui/nmt-utils.c
parent869e9027026cdbb15d4e4a6327ff41d2697858eb (diff)
New upstream version 1.58~rc1 upstream/1.58_rc1
Diffstat (limited to 'src/nmtui/nmt-utils.c')
-rw-r--r--src/nmtui/nmt-utils.c209
1 files changed, 209 insertions, 0 deletions
diff --git a/src/nmtui/nmt-utils.c b/src/nmtui/nmt-utils.c
index 7a500542..90e3bacb 100644
--- a/src/nmtui/nmt-utils.c
+++ b/src/nmtui/nmt-utils.c
@@ -12,6 +12,8 @@
 
 #include "nmt-utils.h"
 
+#include "libnmt-newt/nmt-newt.h"
+
 /**
  * NmtSyncOp:
  *
@@ -128,3 +130,210 @@ nmt_sync_op_complete_pointer(NmtSyncOp *op, gpointer result, GError *error)
     real->error    = error ? g_error_copy(error) : NULL;
     real->complete = GUINT_TO_POINTER(TRUE);
 }
+
+/**
+ * nmt_utils_filter_match:
+ * @haystack: (nullable): the string to search in
+ * @needle: (nullable): the search term
+ *
+ * Case-insensitive UTF-8 substring test. An empty or %NULL @needle matches
+ * anything; a %NULL @haystack matches only an empty @needle.
+ *
+ * Returns: %TRUE if @haystack contains @needle.
+ */
+gboolean
+nmt_utils_filter_match(const char *haystack, const char *needle)
+{
+    gs_free char *h = NULL;
+    gs_free char *n = NULL;
+
+    if (!needle || !needle[0])
+        return TRUE;
+    if (!haystack)
+        return FALSE;
+
+    h = g_utf8_casefold(haystack, -1);
+    n = g_utf8_casefold(needle, -1);
+    return strstr(h, n) != NULL;
+}
+
+/*
+ * Renders the search state into @label: "Search:" while typing,
+ * "Matching '...' (N)" once a filter is applied with the entry hidden, or
+ * empty when idle.
+ *
+ * When the entry is hidden the text is padded to the width the row occupies
+ * while searching ("Search:" plus the entry), so revealing or hiding the entry
+ * never changes the form's width.
+ */
+static void
+set_search_label(NmtNewtLabel *label, const char *filter_text, int match_count, gboolean searching)
+{
+    gs_free char *body = NULL;
+    int           reserve, body_width;
+
+    if (searching) {
+        nmt_newt_label_set_text(label, _("Search:"));
+        return;
+    }
+
+    reserve = nmt_newt_text_width(_("Search:")) + 1 + NMT_SEARCH_ENTRY_WIDTH;
+
+    if (!nm_str_is_empty(filter_text)) {
+        gs_free char *shown = NULL;
+        int           overhead;
+
+        /* Echo the filter, but elide it so the confirmed label never exceeds
+         * the reserved width; otherwise the form grows when a long search is
+         * confirmed with Enter. */
+        body     = g_strdup_printf(_("Matching '%s' (%d)"), "", match_count);
+        overhead = nmt_newt_text_width(body);
+        nm_clear_g_free(&body);
+
+        shown = nmt_newt_text_truncate(filter_text, reserve - overhead);
+        body  = g_strdup_printf(_("Matching '%s' (%d)"), shown, match_count);
+    } else
+        body = g_strdup("");
+
+    body_width = nmt_newt_text_width(body);
+    if (body_width < reserve) {
+        gs_free char *padded = NULL;
+
+        padded = g_strdup_printf("%s%*s", body, reserve - body_width, "");
+        nmt_newt_label_set_text(label, padded);
+    } else
+        nmt_newt_label_set_text(label, body);
+}
+
+struct _NmtSearch {
+    NmtNewtEntry      *entry;
+    NmtNewtLabel      *label;
+    NmtNewtWidget     *focus;
+    NmtSearchApplyFunc apply;
+    NmtSearchCountFunc count;
+    gpointer           user_data;
+};
+
+void
+nmt_search_update_label(NmtSearch *search)
+{
+    set_search_label(search->label,
+                     nmt_newt_entry_get_text(search->entry),
+                     search->count(search->user_data),
+                     nmt_newt_widget_get_visible(NMT_NEWT_WIDGET(search->entry)));
+}
+
+static void
+search_text_changed(GObject *entry, GParamSpec *pspec, gpointer user_data)
+{
+    NmtSearch *search = user_data;
+
+    search->apply(search->user_data, nmt_newt_entry_get_text(search->entry));
+    nmt_search_update_label(search);
+}
+
+static void
+search_activated(NmtNewtWidget *entry, gpointer user_data)
+{
+    NmtSearch   *search = user_data;
+    NmtNewtForm *form   = nmt_newt_widget_get_form(search->focus);
+
+    /* Enter: hide the entry but keep the filter; the label now reports it. */
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(search->entry), FALSE);
+    nmt_search_update_label(search);
+    if (form)
+        nmt_newt_form_set_focus(form, search->focus);
+}
+
+static gboolean
+search_hotkey(NmtNewtForm *form, int key, gpointer user_data)
+{
+    NmtSearch  *search = user_data;
+    const char *filter = nmt_newt_entry_get_text(search->entry);
+
+    if (key == '/') {
+        nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(search->entry), TRUE);
+        nmt_search_update_label(search);
+        nmt_newt_form_set_focus(form, NMT_NEWT_WIDGET(search->entry));
+        return TRUE;
+    }
+    if (key == NEWT_KEY_ESCAPE
+        && (nmt_newt_widget_get_visible(NMT_NEWT_WIDGET(search->entry))
+            || !nm_str_is_empty(filter))) {
+        /* Esc: clear the filter (via notify::text) and return to the list. */
+        nmt_newt_entry_set_text(search->entry, "");
+        nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(search->entry), FALSE);
+        nmt_search_update_label(search);
+        nmt_newt_form_set_focus(form, search->focus);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+NmtSearch *
+nmt_search_new(NmtNewtEntry      *entry,
+               NmtNewtLabel      *label,
+               NmtNewtWidget     *focus,
+               NmtSearchApplyFunc apply,
+               NmtSearchCountFunc count,
+               gpointer           user_data)
+{
+    NmtSearch *search = g_new0(NmtSearch, 1);
+
+    search->entry     = entry;
+    search->label     = label;
+    search->focus     = focus;
+    search->apply     = apply;
+    search->count     = count;
+    search->user_data = user_data;
+
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(entry), FALSE);
+    g_signal_connect(entry, "notify::text", G_CALLBACK(search_text_changed), search);
+    g_signal_connect(entry, "activated", G_CALLBACK(search_activated), search);
+
+    return search;
+}
+
+void
+nmt_search_bind_form(NmtSearch *search, NmtNewtForm *form)
+{
+    g_signal_connect(form, "hotkey", G_CALLBACK(search_hotkey), search);
+    nmt_newt_form_add_hotkey(form, '/');
+    nmt_newt_form_set_stable_width(form);
+
+    /* Reserve the search row's width up front so the form does not grow when
+     * the entry is first revealed. */
+    nmt_search_update_label(search);
+}
+
+static void
+get_secrets_cb(GObject *object, GAsyncResult *result, gpointer op)
+{
+    GVariant *secrets;
+    GError   *error = NULL;
+
+    secrets = nm_remote_connection_get_secrets_finish(NM_REMOTE_CONNECTION(object), result, &error);
+    nmt_sync_op_complete_pointer(op, secrets, error);
+    g_clear_error(&error);
+}
+
+/**
+ * nmt_sync_get_secrets:
+ * @connection: the #NMRemoteConnection to fetch secrets from
+ * @setting_name: the setting to fetch secrets for
+ * @error: return location for a #GError
+ *
+ * Synchronously requests @setting_name's secrets for @connection, running the
+ * main loop until the request completes.
+ *
+ * Returns: (transfer full): the secrets variant, or %NULL on error.
+ */
+GVariant *
+nmt_sync_get_secrets(NMRemoteConnection *connection, const char *setting_name, GError **error)
+{
+    NmtSyncOp op;
+
+    nmt_sync_op_init(&op);
+    nm_remote_connection_get_secrets_async(connection, setting_name, NULL, get_secrets_cb, &op);
+    return nmt_sync_op_wait_pointer(&op, error);
+}