NetworkManager/tui/nmt-edit-connection-list.c
Dan Winship bbc6434e96 tui: fix selection bugs after deleting a connection
nmt_newt_listbox_clear() did not reset active and active_key, which in
the case of NmtEditConnectionList meant that after the connection list
was rebuilt, the selection would appear to be in the same place, but
active_key would still point to the connection that used to be in that
row, rather than the one currently in that row, so if you immediately
hit Edit or Delete, you'd get unexpected results. (It also meant that
it was possible for the selection to land on a header row instead of a
connection row.)

This was particularly bad in the case of the Delete button, since
active_key would be left pointing to a freed NMConnection in that
case.

Fix NmtNewtListbox, and then add code to NmtEditConnectionList to
preserve the selection itself when rebuilding the list.
2014-03-21 13:32:24 -04:00

568 lines
18 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
* 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 of the
* License, 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, see <http://www.gnu.org/licenses/>.
*
* Copyright 2013 Red Hat, Inc.
*/
/**
* SECTION:nmt-edit-connection-list
* @short_description: Connection list for "nmtui edit"
*
* #NmtEditConnectionList is the list of connections displayed by
* "nmtui edit".
*/
#include "config.h"
#include <glib/gi18n-lib.h>
#include "nmtui.h"
#include "nmtui-edit.h"
#include "nmt-edit-connection-list.h"
#include "nmt-editor.h"
#include "nm-editor-utils.h"
G_DEFINE_TYPE (NmtEditConnectionList, nmt_edit_connection_list, NMT_TYPE_NEWT_GRID)
#define NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NMT_TYPE_EDIT_CONNECTION_LIST, NmtEditConnectionListPrivate))
typedef struct {
GSList *connections;
gboolean grouped;
NmtEditConnectionListFilter connection_filter;
gpointer connection_filter_data;
NmtNewtListbox *listbox;
NmtNewtButtonBox *buttons;
NmtNewtWidget *add;
NmtNewtWidget *edit;
NmtNewtWidget *delete;
NmtNewtWidget *extra;
} NmtEditConnectionListPrivate;
enum {
PROP_0,
PROP_GROUPED,
PROP_CONNECTION_FILTER,
PROP_CONNECTION_FILTER_DATA,
PROP_EXTRA_WIDGET,
PROP_CONNECTIONS,
PROP_NUM_CONNECTIONS,
LAST_PROP
};
enum {
ADD_CONNECTION,
EDIT_CONNECTION,
REMOVE_CONNECTION,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
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);
/**
* nmt_edit_connection_list_get_connections:
* @list: an #NmtEditConnectionList
*
* Gets the list's list of connections
*
* Returns: (transfer none) (element-type #NMConnection): the
* list of connections.
*/
GSList *
nmt_edit_connection_list_get_connections (NmtEditConnectionList *list)
{
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (list);
return priv->connections;
}
static void
nmt_edit_connection_list_init (NmtEditConnectionList *list)
{
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (list);
NmtNewtWidget *listbox, *buttons;
NmtNewtGrid *grid = NMT_NEWT_GRID (list);
listbox = g_object_new (NMT_TYPE_NEWT_LISTBOX,
"flags", NMT_NEWT_LISTBOX_SCROLL | NMT_NEWT_LISTBOX_BORDER,
"skip-null-keys", TRUE,
NULL);
priv->listbox = NMT_NEWT_LISTBOX (listbox);
nmt_newt_grid_add (grid, listbox, 0, 0);
nmt_newt_grid_set_flags (grid, listbox,
NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y |
NMT_NEWT_GRID_EXPAND_X | NMT_NEWT_GRID_EXPAND_Y);
g_signal_connect (priv->listbox, "activated", G_CALLBACK (listbox_activated), 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);
nmt_newt_widget_set_padding (buttons, 1, 1, 0, 1);
nmt_newt_grid_set_flags (grid, buttons,
NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y |
NMT_NEWT_GRID_EXPAND_Y);
priv->add = nmt_newt_button_box_add_start (priv->buttons, _("Add"));
g_signal_connect (priv->add, "clicked", G_CALLBACK (add_clicked), list);
priv->edit = nmt_newt_button_box_add_start (priv->buttons, _("Edit..."));
g_signal_connect (priv->edit, "clicked", G_CALLBACK (edit_clicked), list);
priv->delete = nmt_newt_button_box_add_start (priv->buttons, _("Delete"));
g_signal_connect (priv->delete, "clicked", G_CALLBACK (delete_clicked), list);
}
static int
sort_by_timestamp (gconstpointer a,
gconstpointer b)
{
NMSettingConnection *s_con_a, *s_con_b;
guint64 time_a, time_b;
s_con_a = nm_connection_get_setting_connection ((NMConnection *) a);
s_con_b = nm_connection_get_setting_connection ((NMConnection *) b);
time_a = nm_setting_connection_get_timestamp (s_con_a);
time_b = nm_setting_connection_get_timestamp (s_con_b);
return (int) (time_b - time_a);
}
static void nmt_edit_connection_list_rebuild (NmtEditConnectionList *list);
static void
rebuild_on_connection_updated (NMRemoteConnection *connection,
gpointer list)
{
nmt_edit_connection_list_rebuild (list);
}
static void
free_connections (NmtEditConnectionList *list)
{
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (list);
NMConnection *conn;
GSList *iter;
for (iter = priv->connections; iter; iter = iter->next) {
conn = iter->data;
g_signal_handlers_disconnect_by_func (conn, G_CALLBACK (rebuild_on_connection_updated), list);
g_object_unref (conn);
}
g_slist_free (priv->connections);
}
static void
nmt_edit_connection_list_rebuild (NmtEditConnectionList *list)
{
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (list);
GSList *iter, *next;
gboolean did_header = FALSE, did_vpn = FALSE;
NMEditorConnectionTypeData **types;
NMConnection *conn, *selected_conn;
int i, row, selected_row;
selected_row = nmt_newt_listbox_get_active (priv->listbox);
selected_conn = nmt_newt_listbox_get_active_key (priv->listbox);
free_connections (list);
priv->connections = nm_remote_settings_list_connections (nm_settings);
for (iter = priv->connections; iter; iter = next) {
conn = iter->data;
next = iter->next;
if ( priv->connection_filter
&& !priv->connection_filter (list, conn, priv->connection_filter_data)) {
priv->connections = g_slist_delete_link (priv->connections, iter);
continue;
}
g_signal_connect (conn, NM_REMOTE_CONNECTION_UPDATED,
G_CALLBACK (rebuild_on_connection_updated), list);
g_signal_connect (conn, NM_REMOTE_CONNECTION_REMOVED,
G_CALLBACK (rebuild_on_connection_updated), list);
g_object_ref (iter->data);
}
priv->connections = g_slist_sort (priv->connections, sort_by_timestamp);
g_object_notify (G_OBJECT (list), "connections");
g_object_notify (G_OBJECT (list), "num-connections");
nmt_newt_component_set_sensitive (NMT_NEWT_COMPONENT (priv->edit),
priv->connections != NULL);
nmt_newt_component_set_sensitive (NMT_NEWT_COMPONENT (priv->delete),
priv->connections != NULL);
nmt_newt_listbox_clear (priv->listbox);
if (!priv->grouped) {
/* Just add the connections in order */
for (iter = priv->connections, row = 0; iter; iter = iter->next, row++) {
conn = iter->data;
nmt_newt_listbox_append (priv->listbox, nm_connection_get_id (conn), conn);
if (conn == selected_conn)
selected_row = row;
}
if (selected_row >= row)
selected_row = row - 1;
nmt_newt_listbox_set_active (priv->listbox, selected_row);
return;
}
types = nm_editor_utils_get_connection_type_list ();
for (i = row = 0; types[i]; i++) {
if (types[i]->setting_type == NM_TYPE_SETTING_VPN) {
if (did_vpn)
continue;
did_vpn = TRUE;
}
did_header = FALSE;
for (iter = priv->connections; iter; iter = iter->next) {
NMSetting *setting;
char *indented;
conn = iter->data;
setting = nm_connection_get_setting (conn, types[i]->setting_type);
if (!setting)
continue;
if (!nm_connection_is_type (conn, nm_setting_get_name (setting)))
continue;
if (!did_header) {
nmt_newt_listbox_append (priv->listbox, types[i]->name, NULL);
if (row == selected_row)
selected_row++;
row++;
did_header = TRUE;
}
indented = g_strdup_printf (" %s", nm_connection_get_id (conn));
nmt_newt_listbox_append (priv->listbox, indented, conn);
g_free (indented);
if (conn == selected_conn)
selected_row = row;
row++;
}
}
if (selected_row >= row)
selected_row = row - 1;
nmt_newt_listbox_set_active (priv->listbox, selected_row);
}
static void
rebuild_on_new_connection (NMRemoteSettings *settings,
NMRemoteConnection *connection,
gpointer list)
{
nmt_edit_connection_list_rebuild (list);
}
static void
nmt_edit_connection_list_constructed (GObject *object)
{
NmtEditConnectionList *list = NMT_EDIT_CONNECTION_LIST (object);
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (list);
if (priv->extra)
nmt_newt_button_box_add_widget_end (priv->buttons, priv->extra);
g_signal_connect (nm_settings, NM_REMOTE_SETTINGS_NEW_CONNECTION,
G_CALLBACK (rebuild_on_new_connection), list);
nmt_edit_connection_list_rebuild (list);
G_OBJECT_CLASS (nmt_edit_connection_list_parent_class)->constructed (object);
}
static void
add_clicked (NmtNewtButton *button, gpointer list)
{
g_signal_emit (list, signals[ADD_CONNECTION], 0);
}
static void
edit_clicked (NmtNewtButton *button, gpointer list)
{
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (list);
NMConnection *connection;
connection = nmt_newt_listbox_get_active_key (priv->listbox);
g_return_if_fail (connection != NULL);
g_signal_emit (list, signals[EDIT_CONNECTION], 0, connection);
}
static void
delete_clicked (NmtNewtButton *button, gpointer list)
{
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (list);
NMRemoteConnection *connection;
connection = nmt_newt_listbox_get_active_key (priv->listbox);
g_return_if_fail (connection != NULL);
g_signal_emit (list, signals[REMOVE_CONNECTION], 0, connection);
}
static void
listbox_activated (NmtNewtWidget *listbox, gpointer list)
{
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (list);
edit_clicked (NMT_NEWT_BUTTON (priv->edit), list);
}
static void
nmt_edit_connection_list_finalize (GObject *object)
{
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (object);
free_connections (NMT_EDIT_CONNECTION_LIST (object));
g_clear_object (&priv->extra);
G_OBJECT_CLASS (nmt_edit_connection_list_parent_class)->finalize (object);
}
static void
nmt_edit_connection_list_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (object);
switch (prop_id) {
case PROP_GROUPED:
priv->grouped = g_value_get_boolean (value);
break;
case PROP_CONNECTION_FILTER:
priv->connection_filter = g_value_get_pointer (value);
break;
case PROP_CONNECTION_FILTER_DATA:
priv->connection_filter_data = g_value_get_pointer (value);
break;
case PROP_EXTRA_WIDGET:
priv->extra = g_value_get_object (value);
if (priv->extra)
g_object_ref_sink (priv->extra);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
nmt_edit_connection_list_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE (object);
GPtrArray *connections;
GSList *iter;
switch (prop_id) {
case PROP_GROUPED:
g_value_set_boolean (value, priv->grouped);
break;
case PROP_CONNECTION_FILTER:
g_value_set_pointer (value, priv->connection_filter);
break;
case PROP_CONNECTION_FILTER_DATA:
g_value_set_pointer (value, priv->connection_filter_data);
break;
case PROP_EXTRA_WIDGET:
g_value_set_object (value, priv->extra);
break;
case PROP_CONNECTIONS:
connections = g_ptr_array_new_with_free_func (g_object_unref);
for (iter = priv->connections; iter; iter = iter->next)
g_ptr_array_add (connections, g_object_ref (iter->data));
g_value_take_boxed (value, connections);
break;
case PROP_NUM_CONNECTIONS:
g_value_set_int (value, g_slist_length (priv->connections));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
nmt_edit_connection_list_class_init (NmtEditConnectionListClass *list_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (list_class);
g_type_class_add_private (list_class, sizeof (NmtEditConnectionListPrivate));
/* virtual methods */
object_class->constructed = nmt_edit_connection_list_constructed;
object_class->set_property = nmt_edit_connection_list_set_property;
object_class->get_property = nmt_edit_connection_list_get_property;
object_class->finalize = nmt_edit_connection_list_finalize;
/* signals */
/**
* NmtEditConnectionList::add-connection:
* @list: the #NmtEditConnectionList
*
* Emitted when the user clicks the list's "Add" button.
*/
signals[ADD_CONNECTION] =
g_signal_new ("add-connection",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NmtEditConnectionListClass, add_connection),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
/**
* NmtEditConnectionList::edit-connection:
* @list: the #NmtEditConnectionList
* @connection: the connection to edit
*
* Emitted when the user clicks the list's "Edit" button, or
* hits "Return" on the listbox.
*/
signals[EDIT_CONNECTION] =
g_signal_new ("edit-connection",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NmtEditConnectionListClass, edit_connection),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
NM_TYPE_CONNECTION);
/**
* NmtEditConnectionList::remove-connection:
* @list: the #NmtEditConnectionList
* @connection: the connection to remove
*
* Emitted when the user clicks the list's "Delete" button.
*/
signals[REMOVE_CONNECTION] =
g_signal_new ("remove-connection",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NmtEditConnectionListClass, remove_connection),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
NM_TYPE_CONNECTION);
/* properties */
/**
* NmtEditConnectionList:grouped:
*
* If %TRUE, connections should be grouped by type, with headers
* indicating the types (as in the main connection list). If %FALSE,
* they will not be grouped (as in slave connection lists).
*/
g_object_class_install_property (object_class, PROP_GROUPED,
g_param_spec_boolean ("grouped", "", "",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* NmtEditConnectionListFilter:
* @list: the #NmtEditConnectionList
* @connection: an #NMConnection
* @user_data: the user data
*
* Decides whether @connection should be displayed in @list.
*
* Returns: %TRUE or %FALSE
*/
/**
* NmtEditConnectionList:connection-filter:
*
* A callback function for filtering which connections appear in
* the list.
*/
g_object_class_install_property (object_class, PROP_CONNECTION_FILTER,
g_param_spec_pointer ("connection-filter", "", "",
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* NmtEditConnectionList:connection-filter-data:
*
* Data for the #NmtEditConnectionList:connection-filter.
*/
g_object_class_install_property (object_class, PROP_CONNECTION_FILTER_DATA,
g_param_spec_pointer ("connection-filter-data", "", "",
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* NmtEditConnectionList:extra-widget:
*
* An extra button widget to display at the bottom of the button
* box.
*/
g_object_class_install_property (object_class, PROP_EXTRA_WIDGET,
g_param_spec_object ("extra-widget", "", "",
NMT_TYPE_NEWT_WIDGET,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* NmtEditConnectionList:connections:
*
* The list of connections in the widget.
*
* Element-Type: #NMConnection
*/
g_object_class_install_property (object_class, PROP_CONNECTIONS,
g_param_spec_boxed ("connections", "", "",
G_TYPE_PTR_ARRAY,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* NmtEditConnectionList:num-connections:
*
* The number of connections in the widget.
*/
g_object_class_install_property (object_class, PROP_NUM_CONNECTIONS,
g_param_spec_int ("num-connections", "", "",
0, G_MAXINT, 0,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
}