mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-06-20 12:58:29 +02:00
Merge branch 'jp/nmtui-connection-search'
This commit is contained in:
commit
59b59c7046
14 changed files with 518 additions and 8 deletions
2
NEWS
2
NEWS
|
|
@ -71,6 +71,8 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
|
|||
secrets when activating a connection, matching the connection editor.
|
||||
* Fix an out-of-bounds read in the internal DHCPv4 client that an on-link
|
||||
attacker could trigger with a malformed UDP packet, crashing NetworkManager.
|
||||
* The nmtui connection lists ("nmtui connect" and "nmtui edit") support a
|
||||
vim-style "/" search that filters the list to matching entries as you type.
|
||||
|
||||
=============================================
|
||||
NetworkManager-1.56
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ src/nmtui/nmt-password-fields.c
|
|||
src/nmtui/nmt-port-list.c
|
||||
src/nmtui/nmt-route-editor.c
|
||||
src/nmtui/nmt-route-table.c
|
||||
src/nmtui/nmt-utils.c
|
||||
src/nmtui/nmt-widget-list.c
|
||||
src/nmtui/nmt-wireguard-peer-editor.c
|
||||
src/nmtui/nmt-wireguard-peer-list.c
|
||||
|
|
|
|||
|
|
@ -31,13 +31,16 @@ typedef struct {
|
|||
NmtNewtWidget *content;
|
||||
|
||||
guint x, y, width, height;
|
||||
guint form_width_max;
|
||||
guint padding;
|
||||
gboolean fixed_x, fixed_y;
|
||||
gboolean fixed_width, fixed_height;
|
||||
gboolean stable_width;
|
||||
char *title_lc;
|
||||
|
||||
gboolean dirty;
|
||||
NmtNewtWidget *focus;
|
||||
GArray *hotkeys;
|
||||
#ifdef HAVE_NEWTFORMGETSCROLLPOSITION
|
||||
int scroll_position = 0;
|
||||
#endif
|
||||
|
|
@ -60,6 +63,7 @@ enum {
|
|||
|
||||
enum {
|
||||
QUIT,
|
||||
HOTKEY,
|
||||
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
|
@ -118,6 +122,7 @@ nmt_newt_form_finalize(GObject *object)
|
|||
|
||||
g_free(priv->title_lc);
|
||||
g_clear_object(&priv->focus);
|
||||
nm_clear_pointer(&priv->hotkeys, g_array_unref);
|
||||
|
||||
G_OBJECT_CLASS(nmt_newt_form_parent_class)->finalize(object);
|
||||
}
|
||||
|
|
@ -181,6 +186,15 @@ nmt_newt_form_build(NmtNewtForm *form)
|
|||
nmt_newt_widget_size_request(priv->content, &form_width, &form_height);
|
||||
newtGetScreenSize(&screen_width, &screen_height);
|
||||
|
||||
if (priv->stable_width) {
|
||||
/* Never let the form get narrower across rebuilds. Otherwise content
|
||||
* that shrinks (eg, a filtered list) makes the window recenter and
|
||||
* "jump". Only forms that opt in get this; see
|
||||
* nmt_newt_form_set_stable_width(). */
|
||||
priv->form_width_max = NM_MAX(priv->form_width_max, (guint) NM_MAX(form_width, 0));
|
||||
form_width = priv->form_width_max;
|
||||
}
|
||||
|
||||
if (!priv->fixed_width)
|
||||
priv->width = NM_MIN(form_width + 2 * ((gint64) priv->padding), screen_width - 2);
|
||||
if (!priv->fixed_height)
|
||||
|
|
@ -211,6 +225,12 @@ nmt_newt_form_build(NmtNewtForm *form)
|
|||
priv->form = newtForm(NULL, NULL, NEWT_FLAG_NOF12);
|
||||
|
||||
newtFormAddHotKey(priv->form, NEWT_KEY_ESCAPE);
|
||||
if (priv->hotkeys) {
|
||||
guint h;
|
||||
|
||||
for (h = 0; h < priv->hotkeys->len; h++)
|
||||
newtFormAddHotKey(priv->form, nm_g_array_index(priv->hotkeys, int, h));
|
||||
}
|
||||
|
||||
cos = nmt_newt_widget_get_components(priv->content);
|
||||
for (i = 0; cos[i]; i++)
|
||||
|
|
@ -270,6 +290,17 @@ nmt_newt_form_iterate(NmtNewtForm *form)
|
|||
newtFormSetTimer(priv->form, 1);
|
||||
newtFormRun(priv->form, &es);
|
||||
|
||||
if (es.reason == NEWT_EXIT_HOTKEY) {
|
||||
gboolean handled = FALSE;
|
||||
|
||||
/* Give listeners (eg, the connection-list search) a chance to consume the
|
||||
* key. Esc is included, so a listener can make it cancel a transient mode
|
||||
* instead of closing the form. */
|
||||
g_signal_emit(form, signals[HOTKEY], 0, (int) es.u.key, &handled);
|
||||
if (handled)
|
||||
return;
|
||||
}
|
||||
|
||||
if (es.reason == NEWT_EXIT_HOTKEY || es.reason == NEWT_EXIT_ERROR) {
|
||||
/* The user hit Esc or there was an error. */
|
||||
g_clear_object(&priv->focus);
|
||||
|
|
@ -434,6 +465,43 @@ nmt_newt_form_set_focus(NmtNewtForm *form, NmtNewtWidget *widget)
|
|||
g_object_ref(priv->focus);
|
||||
}
|
||||
|
||||
/**
|
||||
* nmt_newt_form_add_hotkey:
|
||||
* @form: an #NmtNewtForm
|
||||
* @key: a key code (eg, a character, or an %NEWT_KEY_ value)
|
||||
*
|
||||
* Registers @key as a hotkey on @form. When the user presses it, the
|
||||
* #NmtNewtForm::hotkey signal is emitted; a handler returning %TRUE consumes
|
||||
* the key, otherwise the form's default handling applies.
|
||||
*/
|
||||
void
|
||||
nmt_newt_form_add_hotkey(NmtNewtForm *form, int key)
|
||||
{
|
||||
NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE(form);
|
||||
|
||||
if (!priv->hotkeys)
|
||||
priv->hotkeys = g_array_new(FALSE, FALSE, sizeof(int));
|
||||
g_array_append_val(priv->hotkeys, key);
|
||||
|
||||
if (priv->form)
|
||||
newtFormAddHotKey(priv->form, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* nmt_newt_form_set_stable_width:
|
||||
* @form: an #NmtNewtForm
|
||||
*
|
||||
* Stops @form from getting narrower across rebuilds: its width grows to fit
|
||||
* content but never shrinks. Use for forms whose content shrinks in place (eg,
|
||||
* a list filtered by a search box) where recentering would make the window
|
||||
* "jump".
|
||||
*/
|
||||
void
|
||||
nmt_newt_form_set_stable_width(NmtNewtForm *form)
|
||||
{
|
||||
NMT_NEWT_FORM_GET_PRIVATE(form)->stable_width = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
nmt_newt_form_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
|
|
@ -579,6 +647,28 @@ nmt_newt_form_class_init(NmtNewtFormClass *form_class)
|
|||
G_TYPE_NONE,
|
||||
0);
|
||||
|
||||
/**
|
||||
* NmtNewtForm::hotkey:
|
||||
* @form: the #NmtNewtForm
|
||||
* @key: the key that was pressed
|
||||
*
|
||||
* Emitted when a key registered via nmt_newt_form_add_hotkey() (or Esc) is
|
||||
* pressed. A handler returning %TRUE consumes the key; otherwise the form's
|
||||
* default handling applies (Esc closes the form).
|
||||
*
|
||||
* Returns: %TRUE if the key was handled.
|
||||
*/
|
||||
signals[HOTKEY] = g_signal_new("hotkey",
|
||||
G_OBJECT_CLASS_TYPE(object_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET(NmtNewtFormClass, hotkey),
|
||||
g_signal_accumulator_true_handled,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_BOOLEAN,
|
||||
1,
|
||||
G_TYPE_INT);
|
||||
|
||||
/**
|
||||
* NmtNewtForm:title:
|
||||
*
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ typedef struct {
|
|||
|
||||
/* signals */
|
||||
void (*quit)(NmtNewtForm *form);
|
||||
gboolean (*hotkey)(NmtNewtForm *form, int key);
|
||||
|
||||
/* methods */
|
||||
void (*show)(NmtNewtForm *form);
|
||||
|
|
@ -45,4 +46,8 @@ void nmt_newt_form_quit(NmtNewtForm *form);
|
|||
|
||||
void nmt_newt_form_set_focus(NmtNewtForm *form, NmtNewtWidget *widget);
|
||||
|
||||
void nmt_newt_form_add_hotkey(NmtNewtForm *form, int key);
|
||||
|
||||
void nmt_newt_form_set_stable_width(NmtNewtForm *form);
|
||||
|
||||
#endif /* NMT_NEWT_FORM_H */
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <sys/wait.h>
|
||||
|
||||
#include "libnm-glib-aux/nm-io-utils.h"
|
||||
#include "libnm-glib-aux/nm-str-buf.h"
|
||||
|
||||
static void
|
||||
nmt_newt_dialog_g_log_handler(const char *log_domain,
|
||||
|
|
@ -386,6 +387,44 @@ nmt_newt_text_width(const char *str)
|
|||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* nmt_newt_text_truncate
|
||||
* @str: a UTF-8 string
|
||||
* @max_width: the maximum width in terminal columns
|
||||
*
|
||||
* Truncates @str to at most @max_width columns, appending an ellipsis if
|
||||
* anything was dropped (the ellipsis is counted in @max_width).
|
||||
*
|
||||
* Returns: (transfer full): a newly-allocated truncated copy of @str.
|
||||
*/
|
||||
char *
|
||||
nmt_newt_text_truncate(const char *str, int max_width)
|
||||
{
|
||||
nm_auto_str_buf NMStrBuf buf = NM_STR_BUF_INIT(0, FALSE);
|
||||
const char *p;
|
||||
int width = 0;
|
||||
|
||||
if (max_width <= 0)
|
||||
return g_strdup("");
|
||||
if (nmt_newt_text_width(str) <= max_width)
|
||||
return g_strdup(str);
|
||||
|
||||
max_width--; /* reserve one column for the ellipsis */
|
||||
|
||||
for (p = str; *p; p = g_utf8_next_char(p)) {
|
||||
gunichar ch = g_utf8_get_char(p);
|
||||
int w = g_unichar_iszerowidth(ch) ? 0 : (g_unichar_iswide(ch) ? 2 : 1);
|
||||
|
||||
if (width + w > max_width)
|
||||
break;
|
||||
width += w;
|
||||
nm_str_buf_append_len(&buf, p, g_utf8_next_char(p) - p);
|
||||
}
|
||||
nm_str_buf_append(&buf, "\xe2\x80\xa6"); /* U+2026 HORIZONTAL ELLIPSIS */
|
||||
|
||||
return nm_str_buf_dup_str(&buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* nmt_newt_edit_string:
|
||||
* @data: data to edit
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ char *nmt_newt_locale_from_utf8(const char *str_utf8);
|
|||
|
||||
int nmt_newt_text_width(const char *str);
|
||||
|
||||
char *nmt_newt_text_truncate(const char *str, int max_width);
|
||||
|
||||
void nmt_newt_message_dialog(const char *message, ...) _nm_printf(1, 2);
|
||||
int nmt_newt_choice_dialog(const char *button1, const char *button2, const char *message, ...)
|
||||
_nm_printf(3, 4);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "nmtui.h"
|
||||
#include "nmt-connect-connection-list.h"
|
||||
#include "nmt-utils.h"
|
||||
#include "libnmc-base/nm-client-utils.h"
|
||||
|
||||
G_DEFINE_TYPE(NmtConnectConnectionList, nmt_connect_connection_list, NMT_TYPE_NEWT_LISTBOX)
|
||||
|
|
@ -47,6 +48,8 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
GSList *nmt_devices;
|
||||
char *filter_text;
|
||||
int match_count;
|
||||
} NmtConnectConnectionListPrivate;
|
||||
|
||||
/**
|
||||
|
|
@ -444,6 +447,13 @@ connection_find_ac(NMConnection *conn, const GPtrArray *acs)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
connection_matches(NmtConnectConnection *nmtconn, const char *needle)
|
||||
{
|
||||
return nmt_utils_filter_match(nmtconn->name, needle)
|
||||
|| nmt_utils_filter_match(nmtconn->ssid, needle);
|
||||
}
|
||||
|
||||
static void
|
||||
nmt_connect_connection_list_rebuild(NmtConnectConnectionList *list)
|
||||
{
|
||||
|
|
@ -456,6 +466,8 @@ nmt_connect_connection_list_rebuild(NmtConnectConnectionList *list)
|
|||
GSList *nmt_devices, *diter, *citer;
|
||||
NmtConnectDevice *nmtdev;
|
||||
NmtConnectConnection *nmtconn;
|
||||
gboolean did_group;
|
||||
int n_matches = 0;
|
||||
|
||||
g_slist_free_full(priv->nmt_devices, (GDestroyNotify) nmt_connect_device_free);
|
||||
priv->nmt_devices = NULL;
|
||||
|
|
@ -486,18 +498,32 @@ nmt_connect_connection_list_rebuild(NmtConnectConnectionList *list)
|
|||
}
|
||||
}
|
||||
|
||||
did_group = FALSE;
|
||||
for (diter = nmt_devices; diter; diter = diter->next) {
|
||||
gboolean dev_matches = FALSE;
|
||||
|
||||
nmtdev = diter->data;
|
||||
|
||||
if (nmtdev->conns) {
|
||||
if (diter != nmt_devices)
|
||||
nmt_newt_listbox_append(listbox, "", NULL);
|
||||
nmt_newt_listbox_append(listbox, nmtdev->name, NULL);
|
||||
for (citer = nmtdev->conns; citer; citer = citer->next) {
|
||||
if (connection_matches(citer->data, priv->filter_text)) {
|
||||
dev_matches = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dev_matches)
|
||||
continue;
|
||||
|
||||
if (did_group)
|
||||
nmt_newt_listbox_append(listbox, "", NULL);
|
||||
nmt_newt_listbox_append(listbox, nmtdev->name, NULL);
|
||||
did_group = TRUE;
|
||||
|
||||
for (citer = nmtdev->conns; citer; citer = citer->next) {
|
||||
nmtconn = citer->data;
|
||||
|
||||
if (!connection_matches(nmtconn, priv->filter_text))
|
||||
continue;
|
||||
|
||||
if (nmtconn->conn)
|
||||
nmtconn->active = connection_find_ac(nmtconn->conn, acs);
|
||||
if (nmtconn->active) {
|
||||
|
|
@ -523,15 +549,36 @@ nmt_connect_connection_list_rebuild(NmtConnectConnectionList *list)
|
|||
|
||||
nmt_newt_listbox_append(listbox, row, nmtconn);
|
||||
g_free(row);
|
||||
n_matches++;
|
||||
}
|
||||
}
|
||||
|
||||
priv->nmt_devices = nmt_devices;
|
||||
priv->match_count = n_matches;
|
||||
|
||||
g_object_notify(G_OBJECT(listbox), "active");
|
||||
g_object_notify(G_OBJECT(listbox), "active-key");
|
||||
}
|
||||
|
||||
void
|
||||
nmt_connect_connection_list_set_filter_text(NmtConnectConnectionList *list, const char *text)
|
||||
{
|
||||
NmtConnectConnectionListPrivate *priv = NMT_CONNECT_CONNECTION_LIST_GET_PRIVATE(list);
|
||||
|
||||
if (nm_streq0(text, priv->filter_text))
|
||||
return;
|
||||
|
||||
g_free(priv->filter_text);
|
||||
priv->filter_text = g_strdup(text);
|
||||
nmt_connect_connection_list_rebuild(list);
|
||||
}
|
||||
|
||||
int
|
||||
nmt_connect_connection_list_get_match_count(NmtConnectConnectionList *list)
|
||||
{
|
||||
return NMT_CONNECT_CONNECTION_LIST_GET_PRIVATE(list)->match_count;
|
||||
}
|
||||
|
||||
static void
|
||||
rebuild_on_property_changed(GObject *object, GParamSpec *spec, gpointer list)
|
||||
{
|
||||
|
|
@ -567,6 +614,7 @@ nmt_connect_connection_list_finalize(GObject *object)
|
|||
NmtConnectConnectionListPrivate *priv = NMT_CONNECT_CONNECTION_LIST_GET_PRIVATE(object);
|
||||
|
||||
g_slist_free_full(priv->nmt_devices, (GDestroyNotify) nmt_connect_device_free);
|
||||
nm_clear_g_free(&priv->filter_text);
|
||||
|
||||
g_signal_handlers_disconnect_by_func(nm_client,
|
||||
G_CALLBACK(rebuild_on_property_changed),
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ GType nmt_connect_connection_list_get_type(void);
|
|||
|
||||
NmtNewtWidget *nmt_connect_connection_list_new(void);
|
||||
|
||||
void nmt_connect_connection_list_set_filter_text(NmtConnectConnectionList *list, const char *text);
|
||||
|
||||
int nmt_connect_connection_list_get_match_count(NmtConnectConnectionList *list);
|
||||
|
||||
gboolean nmt_connect_connection_list_get_connection(NmtConnectConnectionList *list,
|
||||
const char *identifier,
|
||||
NMConnection **connection,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include "nmtui-edit.h"
|
||||
#include "nmt-edit-connection-list.h"
|
||||
#include "nmt-editor.h"
|
||||
#include "nmt-utils.h"
|
||||
|
||||
#include "nm-editor-utils.h"
|
||||
|
||||
|
|
@ -35,6 +36,10 @@ typedef struct {
|
|||
NmtNewtListbox *listbox;
|
||||
NmtNewtButtonBox *buttons;
|
||||
|
||||
NmtSearch *search;
|
||||
char *filter_text;
|
||||
int match_count;
|
||||
|
||||
NmtNewtWidget *add;
|
||||
NmtNewtWidget *edit;
|
||||
NmtNewtWidget *delete;
|
||||
|
|
@ -68,13 +73,16 @@ static void add_clicked(NmtNewtButton *button, gpointer list);
|
|||
static void edit_clicked(NmtNewtButton *button, gpointer list);
|
||||
static void delete_clicked(NmtNewtButton *button, gpointer list);
|
||||
static void listbox_activated(NmtNewtWidget *listbox, gpointer list);
|
||||
static void edit_search_apply(gpointer list, const char *text);
|
||||
static int edit_search_count(gpointer list);
|
||||
|
||||
static void
|
||||
nmt_edit_connection_list_init(NmtEditConnectionList *list)
|
||||
{
|
||||
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list);
|
||||
NmtNewtWidget *listbox, *buttons;
|
||||
NmtNewtWidget *listbox, *buttons, *search_row, *search_label, *search;
|
||||
NmtNewtGrid *grid = NMT_NEWT_GRID(list);
|
||||
NmtNewtGrid *search_grid;
|
||||
|
||||
listbox = g_object_new(NMT_TYPE_NEWT_LISTBOX,
|
||||
"flags",
|
||||
|
|
@ -90,6 +98,28 @@ nmt_edit_connection_list_init(NmtEditConnectionList *list)
|
|||
| NMT_NEWT_GRID_EXPAND_Y);
|
||||
g_signal_connect(priv->listbox, "activated", G_CALLBACK(listbox_activated), list);
|
||||
|
||||
/* Search row below the listbox. The row is always present so revealing the
|
||||
* entry does not resize the form; vim-style '/' shows the entry, and once a
|
||||
* filter is applied the label reports it ("Matching '...' (N)"). */
|
||||
search_row = nmt_newt_grid_new();
|
||||
search_grid = NMT_NEWT_GRID(search_row);
|
||||
nmt_newt_grid_add(grid, search_row, 0, 1);
|
||||
nmt_newt_grid_set_flags(grid, search_row, NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_EXPAND_X);
|
||||
|
||||
search_label = nmt_newt_label_new("");
|
||||
nmt_newt_grid_add(search_grid, search_label, 0, 0);
|
||||
|
||||
search = nmt_newt_entry_new(NMT_SEARCH_ENTRY_WIDTH, 0);
|
||||
nmt_newt_grid_add(search_grid, search, 1, 0);
|
||||
nmt_newt_widget_set_padding(search, 1, 0, 0, 0);
|
||||
|
||||
priv->search = nmt_search_new(NMT_NEWT_ENTRY(search),
|
||||
NMT_NEWT_LABEL(search_label),
|
||||
NMT_NEWT_WIDGET(priv->listbox),
|
||||
edit_search_apply,
|
||||
edit_search_count,
|
||||
list);
|
||||
|
||||
buttons = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_VERTICAL);
|
||||
priv->buttons = NMT_NEWT_BUTTON_BOX(buttons);
|
||||
nmt_newt_grid_add(grid, buttons, 1, 0);
|
||||
|
|
@ -157,7 +187,7 @@ nmt_edit_connection_list_rebuild(NmtEditConnectionList *list)
|
|||
gboolean did_header = FALSE, did_vpn = FALSE, did_any = FALSE;
|
||||
NMEditorConnectionTypeData **types;
|
||||
NMConnection *conn, *selected_conn;
|
||||
int i, row, selected_row;
|
||||
int i, row, selected_row, n_matches = 0;
|
||||
|
||||
selected_row = nmt_newt_listbox_get_active(priv->listbox);
|
||||
selected_conn = nmt_newt_listbox_get_active_key(priv->listbox);
|
||||
|
|
@ -185,17 +215,21 @@ nmt_edit_connection_list_rebuild(NmtEditConnectionList *list)
|
|||
|
||||
if (!priv->grouped) {
|
||||
/* Just add the connections in order */
|
||||
for (iter = priv->connections, row = 0; iter; iter = iter->next, row++) {
|
||||
for (iter = priv->connections, row = 0; iter; iter = iter->next) {
|
||||
conn = iter->data;
|
||||
if (!nmt_utils_filter_match(nm_connection_get_id(conn), priv->filter_text))
|
||||
continue;
|
||||
nmt_newt_listbox_append(priv->listbox, nm_connection_get_id(conn), conn);
|
||||
if (conn == selected_conn)
|
||||
selected_row = row;
|
||||
row++;
|
||||
n_matches++;
|
||||
}
|
||||
if (selected_row >= row)
|
||||
selected_row = row - 1;
|
||||
nmt_newt_listbox_set_active(priv->listbox, selected_row);
|
||||
|
||||
did_any = !!priv->connections;
|
||||
did_any = n_matches > 0;
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -220,6 +254,8 @@ nmt_edit_connection_list_rebuild(NmtEditConnectionList *list)
|
|||
continue;
|
||||
if (!nm_connection_is_type(conn, nm_setting_get_name(setting)))
|
||||
continue;
|
||||
if (!nmt_utils_filter_match(nm_connection_get_id(conn), priv->filter_text))
|
||||
continue;
|
||||
|
||||
if (!did_header) {
|
||||
nmt_newt_listbox_append(priv->listbox, types[i]->name, NULL);
|
||||
|
|
@ -240,6 +276,7 @@ nmt_edit_connection_list_rebuild(NmtEditConnectionList *list)
|
|||
if (conn == selected_conn)
|
||||
selected_row = row;
|
||||
row++;
|
||||
n_matches++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,6 +285,9 @@ nmt_edit_connection_list_rebuild(NmtEditConnectionList *list)
|
|||
nmt_newt_listbox_set_active(priv->listbox, selected_row);
|
||||
|
||||
done:
|
||||
priv->match_count = n_matches;
|
||||
if (priv->search)
|
||||
nmt_search_update_label(priv->search);
|
||||
nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->edit), did_any);
|
||||
nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->delete), did_any);
|
||||
}
|
||||
|
|
@ -315,6 +355,31 @@ listbox_activated(NmtNewtWidget *listbox, gpointer list)
|
|||
edit_clicked(NMT_NEWT_BUTTON(priv->edit), list);
|
||||
}
|
||||
|
||||
static void
|
||||
edit_search_apply(gpointer list, const char *text)
|
||||
{
|
||||
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list);
|
||||
|
||||
if (nm_streq0(text, priv->filter_text))
|
||||
return;
|
||||
|
||||
g_free(priv->filter_text);
|
||||
priv->filter_text = g_strdup(text);
|
||||
nmt_edit_connection_list_rebuild(list);
|
||||
}
|
||||
|
||||
static int
|
||||
edit_search_count(gpointer list)
|
||||
{
|
||||
return NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list)->match_count;
|
||||
}
|
||||
|
||||
void
|
||||
nmt_edit_connection_list_bind_search(NmtEditConnectionList *list, NmtNewtForm *form)
|
||||
{
|
||||
nmt_search_bind_form(NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list)->search, form);
|
||||
}
|
||||
|
||||
static void
|
||||
connection_saved(GObject *conn, GAsyncResult *result, gpointer user_data)
|
||||
{
|
||||
|
|
@ -348,6 +413,8 @@ nmt_edit_connection_list_finalize(GObject *object)
|
|||
|
||||
free_connections(NMT_EDIT_CONNECTION_LIST(object));
|
||||
g_clear_object(&priv->extra);
|
||||
nm_clear_pointer(&priv->search, g_free);
|
||||
nm_clear_g_free(&priv->filter_text);
|
||||
|
||||
G_OBJECT_CLASS(nmt_edit_connection_list_parent_class)->finalize(object);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,4 +42,6 @@ typedef gboolean (*NmtEditConnectionListFilter)(NmtEditConnectionList *list,
|
|||
|
||||
void nmt_edit_connection_list_recommit(NmtEditConnectionList *list);
|
||||
|
||||
void nmt_edit_connection_list_bind_search(NmtEditConnectionList *list, NmtNewtForm *form);
|
||||
|
||||
#endif /* NMT_EDIT_CONNECTION_LIST_H */
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
#include "nmt-utils.h"
|
||||
|
||||
#include "libnmt-newt/nmt-newt.h"
|
||||
|
||||
/**
|
||||
* NmtSyncOp:
|
||||
*
|
||||
|
|
@ -128,3 +130,178 @@ 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
#ifndef NMT_UTILS_H
|
||||
#define NMT_UTILS_H
|
||||
|
||||
#include "libnmt-newt/nmt-newt-types.h"
|
||||
|
||||
typedef struct {
|
||||
gpointer private[3];
|
||||
} NmtSyncOp;
|
||||
|
|
@ -18,4 +20,34 @@ void nmt_sync_op_complete_boolean(NmtSyncOp *op, gboolean result, GError *er
|
|||
gpointer nmt_sync_op_wait_pointer(NmtSyncOp *op, GError **error);
|
||||
void nmt_sync_op_complete_pointer(NmtSyncOp *op, gpointer result, GError *error);
|
||||
|
||||
gboolean nmt_utils_filter_match(const char *haystack, const char *needle);
|
||||
|
||||
#define NMT_SEARCH_ENTRY_WIDTH 34
|
||||
|
||||
/**
|
||||
* NmtSearch:
|
||||
*
|
||||
* Vim-style '/' search glue shared by the connection lists. The caller builds
|
||||
* the @label and @entry into its own layout and hands them over; NmtSearch
|
||||
* wires '/' to reveal the entry, Enter to confirm, Esc to clear, keeps the
|
||||
* status label in sync, and pins the form width so the list does not jump.
|
||||
*
|
||||
* @apply is invoked with the current text whenever the filter changes; @count
|
||||
* returns the live match count for the label.
|
||||
*/
|
||||
typedef struct _NmtSearch NmtSearch;
|
||||
|
||||
typedef void (*NmtSearchApplyFunc)(gpointer user_data, const char *text);
|
||||
typedef int (*NmtSearchCountFunc)(gpointer user_data);
|
||||
|
||||
NmtSearch *nmt_search_new(NmtNewtEntry *entry,
|
||||
NmtNewtLabel *label,
|
||||
NmtNewtWidget *focus,
|
||||
NmtSearchApplyFunc apply,
|
||||
NmtSearchCountFunc count,
|
||||
gpointer user_data);
|
||||
|
||||
void nmt_search_bind_form(NmtSearch *search, NmtNewtForm *form);
|
||||
void nmt_search_update_label(NmtSearch *search);
|
||||
|
||||
#endif /* NMT_UTILS_H */
|
||||
|
|
|
|||
|
|
@ -583,12 +583,26 @@ wifi_rescan(NmtNewtButton *button, gpointer data_batch)
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
connect_search_apply(gpointer list, const char *text)
|
||||
{
|
||||
nmt_connect_connection_list_set_filter_text(NMT_CONNECT_CONNECTION_LIST(list), text);
|
||||
}
|
||||
|
||||
static int
|
||||
connect_search_count(gpointer list)
|
||||
{
|
||||
return nmt_connect_connection_list_get_match_count(NMT_CONNECT_CONNECTION_LIST(list));
|
||||
}
|
||||
|
||||
static NmtNewtForm *
|
||||
nmt_connect_connection_list(gboolean is_top)
|
||||
{
|
||||
int screen_width, screen_height;
|
||||
NmtNewtForm *form;
|
||||
NmtNewtWidget *list, *activate, *quit, *bbox, *grid, *rescan;
|
||||
NmtNewtWidget *search_row, *search_label, *search_entry;
|
||||
NmtSearch *search;
|
||||
RescanBatch *batch_data;
|
||||
gs_unref_ptrarray GPtrArray *all_active_wifi_devices = NULL;
|
||||
|
||||
|
|
@ -634,6 +648,31 @@ nmt_connect_connection_list(gboolean is_top)
|
|||
quit = nmt_newt_button_box_add_end(NMT_NEWT_BUTTON_BOX(bbox), is_top ? _("Quit") : _("Back"));
|
||||
nmt_newt_widget_set_exit_on_activate(quit, TRUE);
|
||||
|
||||
/* Search row below the list. The row is always present so revealing the
|
||||
* entry does not resize the form; vim-style '/' shows the entry, and once a
|
||||
* filter is applied the label reports it ("Matching '...' (N)"). */
|
||||
search_row = nmt_newt_grid_new();
|
||||
nmt_newt_grid_add(NMT_NEWT_GRID(grid), search_row, 0, 1);
|
||||
nmt_newt_grid_set_flags(NMT_NEWT_GRID(grid),
|
||||
search_row,
|
||||
NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_EXPAND_X);
|
||||
|
||||
search_label = nmt_newt_label_new("");
|
||||
nmt_newt_grid_add(NMT_NEWT_GRID(search_row), search_label, 0, 0);
|
||||
|
||||
search_entry = nmt_newt_entry_new(NMT_SEARCH_ENTRY_WIDTH, 0);
|
||||
nmt_newt_grid_add(NMT_NEWT_GRID(search_row), search_entry, 1, 0);
|
||||
nmt_newt_widget_set_padding(search_entry, 1, 0, 0, 0);
|
||||
|
||||
search = nmt_search_new(NMT_NEWT_ENTRY(search_entry),
|
||||
NMT_NEWT_LABEL(search_label),
|
||||
list,
|
||||
connect_search_apply,
|
||||
connect_search_count,
|
||||
list);
|
||||
nmt_search_bind_form(search, form);
|
||||
g_object_set_data_full(G_OBJECT(form), "search-data", search, g_free);
|
||||
|
||||
nmt_newt_form_set_content(form, grid);
|
||||
return form;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,8 @@ nmt_edit_main_connection_list(gboolean is_top)
|
|||
g_signal_connect(list, "edit-connection", G_CALLBACK(list_edit_connection), form);
|
||||
g_signal_connect(list, "remove-connection", G_CALLBACK(list_remove_connection), form);
|
||||
|
||||
nmt_edit_connection_list_bind_search(NMT_EDIT_CONNECTION_LIST(list), form);
|
||||
|
||||
nmt_newt_form_set_content(form, list);
|
||||
return form;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue