[network-manager-applet] connection-editor: factor out master/slave code from CEPageBond



commit 6aae62212e1ad375971eb44da591c502bbe7063a
Author: Dan Winship <danw gnome org>
Date:   Thu Nov 1 13:45:42 2012 -0400

    connection-editor: factor out master/slave code from CEPageBond
    
    Create a new superclass CEPageMaster containing the code that will be
    shared between CEPageBond and CEPageBridge (and eventually
    CEPageTeam).

 po/POTFILES.in                        |    1 +
 src/connection-editor/Makefile.am     |    2 +
 src/connection-editor/ce-page-bond.ui |   24 +-
 src/connection-editor/page-bond.c     |  428 ++-----------------------
 src/connection-editor/page-bond.h     |    6 +-
 src/connection-editor/page-master.c   |  565 +++++++++++++++++++++++++++++++++
 src/connection-editor/page-master.h   |   59 ++++
 7 files changed, 666 insertions(+), 419 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 673145f..3dd00b2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -46,6 +46,7 @@ src/connection-editor/page-general.c
 src/connection-editor/page-infiniband.c
 src/connection-editor/page-ip4.c
 src/connection-editor/page-ip6.c
+src/connection-editor/page-master.c
 src/connection-editor/page-mobile.c
 src/connection-editor/page-ppp.c
 src/connection-editor/page-vlan.c
diff --git a/src/connection-editor/Makefile.am b/src/connection-editor/Makefile.am
index 912d472..742a7da 100644
--- a/src/connection-editor/Makefile.am
+++ b/src/connection-editor/Makefile.am
@@ -52,6 +52,8 @@ nm_connection_editor_SOURCES = \
 	page-ppp.c \
 	page-vpn.h \
 	page-vpn.c \
+	page-master.h \
+	page-master.c \
 	page-bond.h \
 	page-bond.c \
 	page-vlan.h \
diff --git a/src/connection-editor/ce-page-bond.ui b/src/connection-editor/ce-page-bond.ui
index 15859b1..dc89154 100644
--- a/src/connection-editor/ce-page-bond.ui
+++ b/src/connection-editor/ce-page-bond.ui
@@ -22,7 +22,7 @@
     <property name="step_increment">100</property>
     <property name="page_increment">100</property>
   </object>
-  <object class="GtkListStore" id="bond_connections_model">
+  <object class="GtkListStore" id="master_connections_model">
     <columns>
       <!-- column-name connection -->
       <column type="NMRemoteConnection"/>
@@ -82,7 +82,7 @@
     <property name="column_spacing">12</property>
     <property name="row_spacing">6</property>
     <child>
-      <object class="GtkLabel" id="bond_connections_label">
+      <object class="GtkLabel" id="master_connections_label">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <property name="xalign">0</property>
@@ -144,18 +144,18 @@
             <property name="can_focus">True</property>
             <property name="shadow_type">in</property>
             <child>
-              <object class="GtkTreeView" id="bond_connections">
+              <object class="GtkTreeView" id="master_connections">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
-                <property name="model">bond_connections_model</property>
+                <property name="model">master_connections_model</property>
                 <property name="headers_visible">False</property>
                 <child internal-child="selection">
                   <object class="GtkTreeSelection" id="treeview-selection1"/>
                 </child>
                 <child>
-                  <object class="GtkTreeViewColumn" id="bond_connection_name">
+                  <object class="GtkTreeViewColumn" id="master_connection_name">
                     <child>
-                      <object class="GtkCellRendererText" id="bond_connection_renderer"/>
+                      <object class="GtkCellRendererText" id="master_connection_renderer"/>
                       <attributes>
                         <attribute name="text">1</attribute>
                       </attributes>
@@ -179,7 +179,7 @@
             <property name="spacing">10</property>
             <property name="layout_style">start</property>
             <child>
-              <object class="GtkButton" id="bond_connection_add">
+              <object class="GtkButton" id="master_connection_add">
                 <property name="label">gtk-add</property>
                 <property name="use_action_appearance">False</property>
                 <property name="visible">True</property>
@@ -195,7 +195,7 @@
               </packing>
             </child>
             <child>
-              <object class="GtkButton" id="bond_connection_edit">
+              <object class="GtkButton" id="master_connection_edit">
                 <property name="label" translatable="yes">_Edit</property>
                 <property name="use_action_appearance">False</property>
                 <property name="visible">True</property>
@@ -211,7 +211,7 @@
               </packing>
             </child>
             <child>
-              <object class="GtkButton" id="bond_connection_delete">
+              <object class="GtkButton" id="master_connection_delete">
                 <property name="label" translatable="yes">_Delete</property>
                 <property name="use_action_appearance">False</property>
                 <property name="visible">True</property>
@@ -297,20 +297,20 @@
       </packing>
     </child>
     <child>
-      <object class="GtkLabel" id="bond_interface_label">
+      <object class="GtkLabel" id="master_interface_label">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <property name="xalign">0</property>
         <property name="label" translatable="yes">_Interface name:</property>
         <property name="use_underline">True</property>
-        <property name="mnemonic_widget">bond_interface</property>
+        <property name="mnemonic_widget">master_interface</property>
       </object>
       <packing>
         <property name="y_options">GTK_FILL</property>
       </packing>
     </child>
     <child>
-      <object class="GtkEntry" id="bond_interface">
+      <object class="GtkEntry" id="master_interface">
         <property name="visible">True</property>
         <property name="can_focus">True</property>
         <property name="invisible_char">â</property>
diff --git a/src/connection-editor/page-bond.c b/src/connection-editor/page-bond.c
index ba2b766..a211f79 100644
--- a/src/connection-editor/page-bond.c
+++ b/src/connection-editor/page-bond.c
@@ -33,24 +33,18 @@
 #include "nm-connection-editor.h"
 #include "new-connection.h"
 
-G_DEFINE_TYPE (CEPageBond, ce_page_bond, CE_TYPE_PAGE)
+G_DEFINE_TYPE (CEPageBond, ce_page_bond, CE_TYPE_PAGE_MASTER)
 
 #define CE_PAGE_BOND_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CE_TYPE_PAGE_BOND, CEPageBondPrivate))
 
 typedef struct {
-	NMRemoteConnection *connection;
-	NMClient *client;
-	NMRemoteSettings *settings;
-
 	NMSettingBond *setting;
-	const char *uuid;
 
 	GType slave_type;
 	PageNewConnectionFunc new_slave_func;
 
 	GtkWindow *toplevel;
 
-	GtkEntry *interface_name;
 	GtkComboBox *mode;
 	GtkComboBox *monitoring;
 	GtkSpinButton *frequency;
@@ -69,10 +63,6 @@ typedef struct {
 	int downdelay_row;
 	int arp_targets_row;
 
-	GtkTreeView *connections;
-	GtkTreeModel *connections_model;
-	GtkButton *add, *edit, *delete;
-
 } CEPageBondPrivate;
 
 #define MODE_BALANCE_RR    0
@@ -86,27 +76,6 @@ typedef struct {
 #define MONITORING_MII 0
 #define MONITORING_ARP 1
 
-enum {
-	COL_CONNECTION,
-	COL_NAME
-};
-
-static int
-name_sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
-{
-	NMConnection *conn_a, *conn_b;
-	int ret;
-
-	/* We fetch COL_CONNECTION rather than COL_NAME to avoid a strdup/free. */
-	gtk_tree_model_get (model, a, COL_CONNECTION, &conn_a, -1);
-	gtk_tree_model_get (model, b, COL_CONNECTION, &conn_b, -1);
-	ret = strcmp (nm_connection_get_id (conn_a), nm_connection_get_id (conn_b));
-	g_object_unref (conn_a);
-	g_object_unref (conn_b);
-
-	return ret;
-}
-
 static void
 bond_private_init (CEPageBond *self)
 {
@@ -115,15 +84,6 @@ bond_private_init (CEPageBond *self)
 
 	builder = CE_PAGE (self)->builder;
 
-	priv->interface_name = GTK_ENTRY (gtk_builder_get_object (builder, "bond_interface"));
-	priv->connections = GTK_TREE_VIEW (gtk_builder_get_object (builder, "bond_connections"));
-	priv->connections_model = GTK_TREE_MODEL (gtk_builder_get_object (builder, "bond_connections_model"));
-	gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->connections_model),
-	                                 COL_NAME, name_sort_func,
-	                                 NULL, NULL);
-	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->connections_model),
-	                                      COL_NAME, GTK_SORT_ASCENDING);
-
 	priv->mode = GTK_COMBO_BOX (gtk_builder_get_object (builder, "bond_mode"));
 	priv->monitoring = GTK_COMBO_BOX (gtk_builder_get_object (builder, "bond_monitoring"));
 	priv->frequency = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "bond_frequency"));
@@ -135,11 +95,8 @@ bond_private_init (CEPageBond *self)
 	priv->downdelay_box = GTK_WIDGET (gtk_builder_get_object (builder, "bond_downdelay_box"));
 	priv->arp_targets = GTK_ENTRY (gtk_builder_get_object (builder, "bond_arp_targets"));
 	priv->arp_targets_label = GTK_WIDGET (gtk_builder_get_object (builder, "bond_arp_targets_label"));
-	priv->add = GTK_BUTTON (gtk_builder_get_object (builder, "bond_connection_add"));
-	priv->edit = GTK_BUTTON (gtk_builder_get_object (builder, "bond_connection_edit"));
-	priv->delete = GTK_BUTTON (gtk_builder_get_object (builder, "bond_connection_delete"));
 
-	priv->toplevel = GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (priv->connections),
+	priv->toplevel = GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (priv->mode),
 	                                                      GTK_TYPE_WINDOW));
 
 	priv->table = GTK_TABLE (gtk_builder_get_object (builder, "BondPage"));
@@ -156,150 +113,31 @@ bond_private_init (CEPageBond *self)
 }
 
 static void
-dispose (GObject *object)
-{
-	CEPageBond *self = CE_PAGE_BOND (object);
-	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	GtkTreeIter iter;
-
-	if (priv->settings) {
-		g_signal_handlers_disconnect_matched (priv->settings, G_SIGNAL_MATCH_DATA,
-		                                      0, 0, NULL, NULL, self);
-		g_object_unref (priv->settings);
-		priv->settings = NULL;
-	}
-	if (priv->client) {
-		g_object_unref (priv->client);
-		priv->client = NULL;
-	}
-	if (priv->connection) {
-		g_object_unref (priv->connection);
-		priv->connection = NULL;
-	}
-
-	if (gtk_tree_model_get_iter_first (priv->connections_model, &iter)) {
-		do {
-			NMRemoteConnection *connection = NULL;
-
-			gtk_tree_model_get (priv->connections_model, &iter,
-			                    COL_CONNECTION, &connection,
-			                    -1);
-			g_signal_handlers_disconnect_matched (connection, G_SIGNAL_MATCH_DATA,
-			                                      0, 0, NULL, NULL, self);
-			g_object_unref (connection);
-		} while (gtk_tree_model_iter_next (priv->connections_model, &iter));
-	}
-
-	G_OBJECT_CLASS (ce_page_bond_parent_class)->dispose (object);
-}
-
-static void
 stuff_changed (GtkWidget *w, gpointer user_data)
 {
 	ce_page_changed (CE_PAGE (user_data));
 }
 
-static gboolean
-find_connection (CEPageBond *self, NMRemoteConnection *connection, GtkTreeIter *iter)
-{
-	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-
-	if (!gtk_tree_model_get_iter_first (priv->connections_model, iter))
-		return FALSE;
-
-	do {
-		NMRemoteConnection *candidate = NULL;
-
-		gtk_tree_model_get (priv->connections_model, iter,
-		                    COL_CONNECTION, &candidate,
-		                    -1);
-		if (candidate == connection)
-			return TRUE;
-	} while (gtk_tree_model_iter_next (priv->connections_model, iter));
-
-	return FALSE;
-}
-
 static void
-connection_removed (NMRemoteConnection *connection, gpointer user_data)
+connection_removed (CEPageMaster *master, NMConnection *connection)
 {
-	CEPageBond *self = CE_PAGE_BOND (user_data);
+	CEPageBond *self = CE_PAGE_BOND (master);
 	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	GtkTreeIter iter;
-
-	if (!find_connection (self, connection, &iter))
-		return;
-
-	gtk_list_store_remove (GTK_LIST_STORE (priv->connections_model), &iter);
-	stuff_changed (NULL, self);
 
-	if (!gtk_tree_model_get_iter_first (priv->connections_model, &iter)) {
+	if (!ce_page_master_has_slaves (master)) {
 		priv->slave_type = G_TYPE_INVALID;
 		priv->new_slave_func = NULL;
 	}
 }
 
 static void
-connection_updated (NMRemoteConnection *connection, gpointer user_data)
+connection_added (CEPageMaster *master, NMConnection *connection)
 {
-	CEPageBond *self = CE_PAGE_BOND (user_data);
+	CEPageBond *self = CE_PAGE_BOND (master);
 	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	GtkTreeIter iter;
-	NMSettingConnection *s_con;
 
-	if (!find_connection (self, connection, &iter))
-		return;
-
-	/* Name might have changed */
-	s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
-	gtk_list_store_set (GTK_LIST_STORE (priv->connections_model), &iter,
-	                    COL_NAME, nm_setting_connection_get_id (s_con),
-	                    -1);
-}
-
-static void
-connection_added (NMRemoteSettings *settings,
-                  NMRemoteConnection *connection,
-                  gpointer user_data)
-{
-	CEPageBond *self = CE_PAGE_BOND (user_data);
-	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	NMSettingConnection *s_con;
-	const char *slave_type, *master;
-	const char *interface_name;
-	GtkTreeIter iter;
-
-	s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
-	if (!s_con)
-		return;
-
-	slave_type = nm_setting_connection_get_slave_type (s_con);
-	if (!slave_type || strcmp (slave_type, NM_SETTING_BOND_SETTING_NAME) != 0)
-		return;
-
-	master = nm_setting_connection_get_master (s_con);
-	if (!master)
-		return;
-
-	interface_name = nm_setting_bond_get_interface_name (priv->setting);
-	if (!strcmp (master, interface_name)) {
-		/* Ugh. Fix that... */
-		g_object_set (G_OBJECT (connection),
-		              NM_SETTING_CONNECTION_MASTER, priv->uuid,
-		              NULL);
-		nm_remote_connection_commit_changes (connection, NULL, NULL);
-	} else if (strcmp (master, priv->uuid) != 0)
-		return;
-
-	gtk_list_store_append (GTK_LIST_STORE (priv->connections_model), &iter);
-	gtk_list_store_set (GTK_LIST_STORE (priv->connections_model), &iter,
-	                    COL_CONNECTION, connection,
-	                    COL_NAME, nm_setting_connection_get_id (s_con),
-	                    -1);
-	stuff_changed (NULL, self);
-
-	/* FIXME: a bit kludgy */
-	if (nm_connection_is_type (NM_CONNECTION (connection), NM_SETTING_INFINIBAND_SETTING_NAME)) {
+	/* A bit kludgy... */
+	if (nm_connection_is_type (connection, NM_SETTING_INFINIBAND_SETTING_NAME)) {
 		priv->slave_type = NM_TYPE_SETTING_INFINIBAND;
 		priv->new_slave_func = infiniband_connection_new;
 		gtk_combo_box_set_active (priv->mode, MODE_ACTIVE_BACKUP);
@@ -309,11 +147,6 @@ connection_added (NMRemoteSettings *settings,
 		priv->new_slave_func = ethernet_connection_new;
 		gtk_widget_set_sensitive (GTK_WIDGET (priv->mode), TRUE);
 	}
-
-	g_signal_connect (connection, NM_REMOTE_CONNECTION_REMOVED,
-	                  G_CALLBACK (connection_removed), self);
-	g_signal_connect (connection, NM_REMOTE_CONNECTION_UPDATED,
-	                  G_CALLBACK (connection_updated), self);
 }
 
 static void
@@ -444,25 +277,10 @@ populate_ui (CEPageBond *self)
 {
 	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
 	NMSettingBond *setting = priv->setting;
-	NMSettingConnection *s_con;
-	const char *iface;
-	GSList *connections, *c;
 	const char *mode, *frequency, *updelay, *downdelay, *raw_targets;
 	char *targets;
 	int mode_idx = MODE_BALANCE_RR;
 
-	s_con = nm_connection_get_setting_connection (NM_CONNECTION (priv->connection));
-	g_return_if_fail (s_con != NULL);
-
-	/* Interface name */
-	iface = nm_setting_bond_get_interface_name (setting);
-	gtk_entry_set_text (priv->interface_name, iface ? iface : "");
-
-	/* Bonded connections */
-	connections = nm_remote_settings_list_connections (priv->settings);
-	for (c = connections; c; c = c->next)
-		connection_added (priv->settings, c->data, self);
-
 	/* Mode */
 	mode = nm_setting_bond_get_option_by_name (setting, NM_SETTING_BOND_OPTION_MODE);
 	if (mode) {
@@ -530,87 +348,6 @@ populate_ui (CEPageBond *self)
 	}
 }
 
-static void
-connections_selection_changed_cb (GtkTreeSelection *selection, gpointer user_data)
-{
-	CEPageBond *self = user_data;
-	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	GtkTreeIter iter;
-	GtkTreeModel *model;
-	NMRemoteConnection *connection;
-	NMSettingConnection *s_con;
-	gboolean sensitive = FALSE;
-
-	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
-		gtk_tree_model_get (model, &iter,
-		                    0, &connection,
-		                    -1);
-		s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
-		g_assert (s_con);
-	
-		sensitive = !nm_setting_connection_get_read_only (s_con);
-	}
-
-	gtk_widget_set_sensitive (GTK_WIDGET (priv->edit), sensitive);
-	gtk_widget_set_sensitive (GTK_WIDGET (priv->delete), sensitive);
-}
-
-static void
-add_response_cb (NMConnectionEditor *editor, NMRemoteConnection *connection,
-                 gboolean added, gpointer user_data)
-{
-	g_object_unref (editor);
-}
-
-static void
-add_bond_connection (NMConnection *connection,
-                     gpointer user_data)
-{
-	CEPageBond *self = user_data;
-	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	NMSettingConnection *s_con;
-	NMConnectionEditor *editor;
-	const char *iface_name;
-	char *name;
-
-	if (!connection)
-		return;
-
-	/* Mark the connection as a bond slave so that the editor knows not
-	 * to add IPv4 and IPv6 pages, and rename it.
-	 */
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con != NULL);
-
-	iface_name = gtk_entry_get_text (priv->interface_name);
-	if (!*iface_name)
-		iface_name = nm_setting_bond_get_interface_name (priv->setting);
-	if (!*iface_name)
-		iface_name = "bond";
-	name = g_strdup_printf (_("%s slave %d"), iface_name,
-	                        gtk_tree_model_iter_n_children (priv->connections_model, NULL) + 1);
-
-	g_object_set (G_OBJECT (s_con),
-	              NM_SETTING_CONNECTION_ID, name,
-	              NM_SETTING_CONNECTION_MASTER, priv->uuid,
-	              NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_BOND_SETTING_NAME,
-	              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
-	              NULL);
-	g_free (name);
-
-	editor = nm_connection_editor_new (priv->toplevel,
-	                                   connection,
-	                                   priv->client,
-	                                   priv->settings);
-	if (!editor) {
-		g_object_unref (connection);
-		return;
-	}
-
-	g_signal_connect (editor, "done", G_CALLBACK (add_response_cb), self);
-	nm_connection_editor_run (editor);
-}
-
 static gboolean
 connection_type_filter (GType type, gpointer user_data)
 {
@@ -622,138 +359,43 @@ connection_type_filter (GType type, gpointer user_data)
 }
 
 static void
-add_clicked (GtkButton *button, gpointer user_data)
+add_slave (CEPageMaster *master, NewConnectionResultFunc result_func)
 {
-	CEPageBond *self = user_data;
+	CEPageBond *self = CE_PAGE_BOND (master);
 	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
 
 	if (priv->new_slave_func) {
 		new_connection_of_type (priv->toplevel,
 		                        NULL,
-		                        priv->settings,
+		                        CE_PAGE (self)->settings,
 		                        priv->new_slave_func,
-		                        add_bond_connection,
-		                        self);
+		                        result_func,
+		                        master);
 	} else {
 		new_connection_dialog (priv->toplevel,
-		                       priv->settings,
+		                       CE_PAGE (self)->settings,
 		                       connection_type_filter,
-		                       add_bond_connection,
-		                       self);
-	}
-}
-
-static NMRemoteConnection *
-get_selected_connection (CEPageBond *self)
-{
-	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	GtkTreeSelection *selection;
-	GList *selected_rows;
-	GtkTreeModel *model = NULL;
-	GtkTreeIter iter;
-	NMRemoteConnection *connection = NULL;
-
-	selection = gtk_tree_view_get_selection (priv->connections);
-	selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
-	if (!selected_rows)
-		return NULL;
-
-	if (gtk_tree_model_get_iter (model, &iter, (GtkTreePath *) selected_rows->data))
-		gtk_tree_model_get (model, &iter, 0, &connection, -1);
-
-	/* free memory */
-	g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
-	g_list_free (selected_rows);
-
-	return connection;
-}
-
-static void
-edit_done_cb (NMConnectionEditor *editor, GtkResponseType response, gpointer user_data)
-{
-	g_object_unref (editor);
-}
-
-static void
-edit_clicked (GtkButton *button, gpointer user_data)
-{
-	CEPageBond *self = user_data;
-	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	NMConnectionEditor *editor;
-	NMRemoteConnection *connection;
-
-	connection = get_selected_connection (self);
-	if (!connection)
-		return;
-
-	editor = nm_connection_editor_get (NM_CONNECTION (connection));
-	if (editor) {
-		nm_connection_editor_present (editor);
-		return;
+		                       result_func,
+		                       master);
 	}
-
-	editor = nm_connection_editor_new (priv->toplevel,
-	                                   NM_CONNECTION (connection),
-	                                   priv->client,
-	                                   priv->settings);
-	if (!editor)
-		return;
-
-	g_signal_connect (editor, "done", G_CALLBACK (edit_done_cb), self);
-	nm_connection_editor_run (editor);
-}
-
-static void
-connection_double_clicked_cb (GtkTreeView *tree_view,
-                              GtkTreePath *path,
-                              GtkTreeViewColumn *column,
-                              gpointer user_data)
-{
-	edit_clicked (NULL, user_data);
-}
-
-static void
-delete_clicked (GtkButton *button, gpointer user_data)
-{
-	CEPageBond *self = user_data;
-	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	NMRemoteConnection *connection;
-
-	connection = get_selected_connection (self);
-	if (!connection)
-		return;
-
-	delete_connection (priv->toplevel, connection, NULL, NULL);
 }
 
 static void
 finish_setup (CEPageBond *self, gpointer unused, GError *error, gpointer user_data)
 {
 	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	GtkTreeSelection *selection;
 
 	if (error)
 		return;
 
 	populate_ui (self);
 
-	g_signal_connect (priv->interface_name, "changed", G_CALLBACK (stuff_changed), self);
 	g_signal_connect (priv->mode, "changed", G_CALLBACK (stuff_changed), self);
 	g_signal_connect (priv->monitoring, "changed", G_CALLBACK (stuff_changed), self);
 	g_signal_connect (priv->frequency, "value-changed", G_CALLBACK (stuff_changed), self);
 	g_signal_connect (priv->updelay, "value-changed", G_CALLBACK (stuff_changed), self);
 	g_signal_connect (priv->downdelay, "value-changed", G_CALLBACK (stuff_changed), self);
 	g_signal_connect (priv->arp_targets, "changed", G_CALLBACK (stuff_changed), self);
-
-	g_signal_connect (priv->add, "clicked", G_CALLBACK (add_clicked), self);
-	g_signal_connect (priv->edit, "clicked", G_CALLBACK (edit_clicked), self);
-	g_signal_connect (priv->delete, "clicked", G_CALLBACK (delete_clicked), self);
-
-	g_signal_connect (priv->connections, "row-activated", G_CALLBACK (connection_double_clicked_cb), self);
-
-	selection = gtk_tree_view_get_selection (priv->connections);
-	g_signal_connect (selection, "changed", G_CALLBACK (connections_selection_changed_cb), self);
-	connections_selection_changed_cb (selection, self);
 }
 
 CEPage *
@@ -766,7 +408,6 @@ ce_page_bond_new (NMConnection *connection,
 {
 	CEPageBond *self;
 	CEPageBondPrivate *priv;
-	NMSettingConnection *s_con;
 
 	self = CE_PAGE_BOND (ce_page_new (CE_TYPE_PAGE_BOND,
 	                                  connection,
@@ -785,17 +426,7 @@ ce_page_bond_new (NMConnection *connection,
 	bond_private_init (self);
 	priv = CE_PAGE_BOND_GET_PRIVATE (self);
 
-	priv->connection = g_object_ref (connection);
-	priv->client = g_object_ref (client);
-	priv->settings = g_object_ref (settings);
-
-	s_con = nm_connection_get_setting_connection (connection);
-	priv->uuid = nm_setting_connection_get_uuid (s_con);
-
-	g_signal_connect (settings, NM_REMOTE_SETTINGS_NEW_CONNECTION,
-	                  G_CALLBACK (connection_added), self);
-
-	priv->setting = (NMSettingBond *) nm_connection_get_setting (connection, NM_TYPE_SETTING_BOND);
+	priv->setting = nm_connection_get_setting_bond (connection);
 	if (!priv->setting) {
 		priv->setting = NM_SETTING_BOND (nm_setting_bond_new ());
 		nm_connection_add_setting (connection, NM_SETTING (priv->setting));
@@ -810,19 +441,12 @@ static void
 ui_to_setting (CEPageBond *self)
 {
 	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	const char *interface_name;
 	const char *mode;
 	const char *frequency;
 	const char *updelay;
 	const char *downdelay;
 	char *targets;
 
-	/* Interface name */
-	interface_name = gtk_entry_get_text (priv->interface_name);
-	g_object_set (priv->setting,
-	              NM_SETTING_BOND_INTERFACE_NAME, interface_name,
-	              NULL);
-
 	/* Mode */
 	switch (gtk_combo_box_get_active (priv->mode)) {
 	case MODE_BALANCE_RR:
@@ -881,8 +505,6 @@ ui_to_setting (CEPageBond *self)
 	}
 
 	g_free (targets);
-
-	/* Slaves are updated as they're edited, so nothing to do */
 }
 
 static gboolean
@@ -890,13 +512,8 @@ validate (CEPage *page, NMConnection *connection, GError **error)
 {
 	CEPageBond *self = CE_PAGE_BOND (page);
 	CEPageBondPrivate *priv = CE_PAGE_BOND_GET_PRIVATE (self);
-	GtkTreeIter iter;
 
-	/* Need at least one slave connection; we don't need to
-	 * recursively check that the connections are valid because they
-	 * can't end up in the table if they're not.
-	 */
-	if (!gtk_tree_model_get_iter_first (priv->connections_model, &iter))
+	if (!CE_PAGE_CLASS (ce_page_bond_parent_class)->validate (page, connection, error))
 		return FALSE;
 
 	ui_to_setting (self);
@@ -913,13 +530,16 @@ ce_page_bond_class_init (CEPageBondClass *bond_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (bond_class);
 	CEPageClass *parent_class = CE_PAGE_CLASS (bond_class);
+	CEPageMasterClass *master_class = CE_PAGE_MASTER_CLASS (bond_class);
 
 	g_type_class_add_private (object_class, sizeof (CEPageBondPrivate));
 
 	/* virtual methods */
-	object_class->dispose = dispose;
-
 	parent_class->validate = validate;
+
+	master_class->connection_added = connection_added;
+	master_class->connection_removed = connection_removed;
+	master_class->add_slave = add_slave;
 }
 
 
diff --git a/src/connection-editor/page-bond.h b/src/connection-editor/page-bond.h
index 15b460f..dcc8e1f 100644
--- a/src/connection-editor/page-bond.h
+++ b/src/connection-editor/page-bond.h
@@ -26,7 +26,7 @@
 #include <glib.h>
 #include <glib-object.h>
 
-#include "ce-page.h"
+#include "page-master.h"
 
 #define CE_TYPE_PAGE_BOND            (ce_page_bond_get_type ())
 #define CE_PAGE_BOND(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CE_TYPE_PAGE_BOND, CEPageBond))
@@ -36,11 +36,11 @@
 #define CE_PAGE_BOND_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), CE_TYPE_PAGE_BOND, CEPageBondClass))
 
 typedef struct {
-	CEPage parent;
+	CEPageMaster parent;
 } CEPageBond;
 
 typedef struct {
-	CEPageClass parent;
+	CEPageMasterClass parent;
 } CEPageBondClass;
 
 GType ce_page_bond_get_type (void);
diff --git a/src/connection-editor/page-master.c b/src/connection-editor/page-master.c
new file mode 100644
index 0000000..41a084c
--- /dev/null
+++ b/src/connection-editor/page-master.c
@@ -0,0 +1,565 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Connection editor -- Connection editor for NetworkManager
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2012 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <nm-setting-connection.h>
+
+#include "page-master.h"
+#include "nm-connection-editor.h"
+
+G_DEFINE_TYPE (CEPageMaster, ce_page_master, CE_TYPE_PAGE)
+
+#define CE_PAGE_MASTER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CE_TYPE_PAGE_MASTER, CEPageMasterPrivate))
+
+typedef struct {
+	const char *uuid;
+
+	GtkWindow *toplevel;
+
+	GtkEntry *interface_name;
+
+	GtkTreeView *connections;
+	GtkTreeModel *connections_model;
+	GtkButton *add, *edit, *delete;
+
+} CEPageMasterPrivate;
+
+enum {
+	CONNECTION_ADDED,
+	CONNECTION_REMOVED,
+
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void finish_setup (CEPageMaster *self, gpointer unused, GError *error, gpointer user_data);
+
+enum {
+	COL_CONNECTION,
+	COL_NAME
+};
+
+static int
+name_sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
+{
+	NMConnection *conn_a, *conn_b;
+	int ret;
+
+	/* We fetch COL_CONNECTION rather than COL_NAME to avoid a strdup/free. */
+	gtk_tree_model_get (model, a, COL_CONNECTION, &conn_a, -1);
+	gtk_tree_model_get (model, b, COL_CONNECTION, &conn_b, -1);
+	ret = strcmp (nm_connection_get_id (conn_a), nm_connection_get_id (conn_b));
+	g_object_unref (conn_a);
+	g_object_unref (conn_b);
+
+	return ret;
+}
+
+static void
+constructed (GObject *object)
+{
+	CEPageMaster *self = CE_PAGE_MASTER (object);
+
+	g_signal_connect (self, "initialized", G_CALLBACK (finish_setup), NULL);
+
+	G_OBJECT_CLASS (ce_page_master_parent_class)->constructed (object);
+}
+
+static void
+dispose (GObject *object)
+{
+	CEPageMaster *self = CE_PAGE_MASTER (object);
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	GtkTreeIter iter;
+
+	g_signal_handlers_disconnect_matched (CE_PAGE (self)->settings, G_SIGNAL_MATCH_DATA,
+	                                      0, 0, NULL, NULL, self);
+
+	if (gtk_tree_model_get_iter_first (priv->connections_model, &iter)) {
+		do {
+			NMRemoteConnection *connection = NULL;
+
+			gtk_tree_model_get (priv->connections_model, &iter,
+			                    COL_CONNECTION, &connection,
+			                    -1);
+			g_signal_handlers_disconnect_matched (connection, G_SIGNAL_MATCH_DATA,
+			                                      0, 0, NULL, NULL, self);
+			g_object_unref (connection);
+		} while (gtk_tree_model_iter_next (priv->connections_model, &iter));
+	}
+
+	G_OBJECT_CLASS (ce_page_master_parent_class)->dispose (object);
+}
+
+static void
+stuff_changed (GtkWidget *widget, gpointer user_data)
+{
+	ce_page_changed (CE_PAGE (user_data));
+}
+
+static gboolean
+find_connection (CEPageMaster *self, NMRemoteConnection *connection, GtkTreeIter *iter)
+{
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+
+	if (!gtk_tree_model_get_iter_first (priv->connections_model, iter))
+		return FALSE;
+
+	do {
+		NMRemoteConnection *candidate = NULL;
+
+		gtk_tree_model_get (priv->connections_model, iter,
+		                    COL_CONNECTION, &candidate,
+		                    -1);
+		if (candidate == connection)
+			return TRUE;
+	} while (gtk_tree_model_iter_next (priv->connections_model, iter));
+
+	return FALSE;
+}
+
+static void
+connection_removed (NMRemoteConnection *connection, gpointer user_data)
+{
+	CEPageMaster *self = CE_PAGE_MASTER (user_data);
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	GtkTreeIter iter;
+
+	if (!find_connection (self, connection, &iter))
+		return;
+
+	gtk_list_store_remove (GTK_LIST_STORE (priv->connections_model), &iter);
+	ce_page_changed (CE_PAGE (self));
+
+	g_signal_emit (self, signals[CONNECTION_REMOVED], 0, connection);
+}
+
+static void
+connection_updated (NMRemoteConnection *connection, gpointer user_data)
+{
+	CEPageMaster *self = CE_PAGE_MASTER (user_data);
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	GtkTreeIter iter;
+	NMSettingConnection *s_con;
+
+	if (!find_connection (self, connection, &iter))
+		return;
+
+	/* Name might have changed */
+	s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
+	gtk_list_store_set (GTK_LIST_STORE (priv->connections_model), &iter,
+	                    COL_NAME, nm_setting_connection_get_id (s_con),
+	                    -1);
+}
+
+static void
+connection_added (NMRemoteSettings *settings,
+                  NMRemoteConnection *connection,
+                  gpointer user_data)
+{
+	CEPageMaster *self = CE_PAGE_MASTER (user_data);
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	NMSettingConnection *s_con;
+	const char *master_type;
+	const char *slave_type, *master;
+	const char *interface_name;
+	GtkTreeIter iter;
+
+	s_con = nm_connection_get_setting_connection (CE_PAGE (self)->connection);
+	master_type = nm_setting_connection_get_connection_type (s_con);
+
+	s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
+	if (!s_con)
+		return;
+
+	slave_type = nm_setting_connection_get_slave_type (s_con);
+	if (g_strcmp0 (slave_type, master_type) != 0)
+		return;
+
+	master = nm_setting_connection_get_master (s_con);
+	if (!master)
+		return;
+
+	interface_name = nm_connection_get_virtual_iface_name (CE_PAGE (self)->connection);
+	if (!strcmp (master, interface_name)) {
+		/* Ugh. Fix that... */
+		g_object_set (G_OBJECT (connection),
+		              NM_SETTING_CONNECTION_MASTER, priv->uuid,
+		              NULL);
+		nm_remote_connection_commit_changes (connection, NULL, NULL);
+	} else if (strcmp (master, priv->uuid) != 0)
+		return;
+
+	gtk_list_store_append (GTK_LIST_STORE (priv->connections_model), &iter);
+	gtk_list_store_set (GTK_LIST_STORE (priv->connections_model), &iter,
+	                    COL_CONNECTION, connection,
+	                    COL_NAME, nm_setting_connection_get_id (s_con),
+	                    -1);
+	ce_page_changed (CE_PAGE (self));
+
+	g_signal_connect (connection, NM_REMOTE_CONNECTION_REMOVED,
+	                  G_CALLBACK (connection_removed), self);
+	g_signal_connect (connection, NM_REMOTE_CONNECTION_UPDATED,
+	                  G_CALLBACK (connection_updated), self);
+
+	g_signal_emit (self, signals[CONNECTION_ADDED], 0, connection);
+}
+
+static void
+connections_selection_changed_cb (GtkTreeSelection *selection, gpointer user_data)
+{
+	CEPageMaster *self = user_data;
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	NMRemoteConnection *connection;
+	NMSettingConnection *s_con;
+	gboolean sensitive = FALSE;
+
+	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+		gtk_tree_model_get (model, &iter,
+		                    0, &connection,
+		                    -1);
+		s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
+		g_assert (s_con);
+
+		sensitive = !nm_setting_connection_get_read_only (s_con);
+	}
+
+	gtk_widget_set_sensitive (GTK_WIDGET (priv->edit), sensitive);
+	gtk_widget_set_sensitive (GTK_WIDGET (priv->delete), sensitive);
+}
+
+static void
+add_response_cb (NMConnectionEditor *editor, NMRemoteConnection *connection,
+                 gboolean added, gpointer user_data)
+{
+	g_object_unref (editor);
+}
+
+static void
+add_connection (NMConnection *connection,
+                gpointer user_data)
+{
+	CEPageMaster *self = user_data;
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	NMSettingConnection *s_con;
+	NMConnectionEditor *editor;
+	const char *iface_name, *master_type;
+	char *name;
+
+	if (!connection)
+		return;
+
+	s_con = nm_connection_get_setting_connection (CE_PAGE (self)->connection);
+	master_type = nm_setting_connection_get_connection_type (s_con);
+
+	/* Mark the connection as a slave, and rename it. */
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (s_con != NULL);
+
+	iface_name = gtk_entry_get_text (priv->interface_name);
+	if (!*iface_name)
+		iface_name = nm_connection_get_virtual_iface_name (connection);
+	if (!*iface_name)
+		iface_name = nm_connection_get_id (connection);
+	name = g_strdup_printf (_("%s slave %d"), iface_name,
+	                        gtk_tree_model_iter_n_children (priv->connections_model, NULL) + 1);
+
+	g_object_set (G_OBJECT (s_con),
+	              NM_SETTING_CONNECTION_ID, name,
+	              NM_SETTING_CONNECTION_MASTER, priv->uuid,
+	              NM_SETTING_CONNECTION_SLAVE_TYPE, master_type,
+	              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
+	              NULL);
+	g_free (name);
+
+	editor = nm_connection_editor_new (priv->toplevel,
+	                                   connection,
+	                                   CE_PAGE (self)->client,
+	                                   CE_PAGE (self)->settings);
+	if (!editor) {
+		g_object_unref (connection);
+		return;
+	}
+
+	g_signal_connect (editor, "done", G_CALLBACK (add_response_cb), self);
+	nm_connection_editor_run (editor);
+}
+
+static void
+add_clicked (GtkButton *button, gpointer user_data)
+{
+	CEPageMaster *self = user_data;
+
+	CE_PAGE_MASTER_GET_CLASS (self)->add_slave (self, add_connection);
+}
+
+static NMRemoteConnection *
+get_selected_connection (CEPageMaster *self)
+{
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	GtkTreeSelection *selection;
+	GList *selected_rows;
+	GtkTreeModel *model = NULL;
+	GtkTreeIter iter;
+	NMRemoteConnection *connection = NULL;
+
+	selection = gtk_tree_view_get_selection (priv->connections);
+	selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
+	if (!selected_rows)
+		return NULL;
+
+	if (gtk_tree_model_get_iter (model, &iter, (GtkTreePath *) selected_rows->data))
+		gtk_tree_model_get (model, &iter, 0, &connection, -1);
+
+	/* free memory */
+	g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (selected_rows);
+
+	return connection;
+}
+
+static void
+edit_done_cb (NMConnectionEditor *editor, GtkResponseType response, gpointer user_data)
+{
+	g_object_unref (editor);
+}
+
+static void
+edit_clicked (GtkButton *button, gpointer user_data)
+{
+	CEPageMaster *self = user_data;
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	NMConnectionEditor *editor;
+	NMRemoteConnection *connection;
+
+	connection = get_selected_connection (self);
+	if (!connection)
+		return;
+
+	editor = nm_connection_editor_get (NM_CONNECTION (connection));
+	if (editor) {
+		nm_connection_editor_present (editor);
+		return;
+	}
+
+	editor = nm_connection_editor_new (priv->toplevel,
+	                                   NM_CONNECTION (connection),
+	                                   CE_PAGE (self)->client,
+	                                   CE_PAGE (self)->settings);
+	if (!editor)
+		return;
+
+	g_signal_connect (editor, "done", G_CALLBACK (edit_done_cb), self);
+	nm_connection_editor_run (editor);
+}
+
+static void
+connection_double_clicked_cb (GtkTreeView *tree_view,
+                              GtkTreePath *path,
+                              GtkTreeViewColumn *column,
+                              gpointer user_data)
+{
+	edit_clicked (NULL, user_data);
+}
+
+static void
+delete_clicked (GtkButton *button, gpointer user_data)
+{
+	CEPageMaster *self = user_data;
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	NMRemoteConnection *connection;
+
+	connection = get_selected_connection (self);
+	if (!connection)
+		return;
+
+	delete_connection (priv->toplevel, connection, NULL, NULL);
+}
+
+static void
+populate_ui (CEPageMaster *self)
+{
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	NMSettingConnection *s_con;
+	const char *iface;
+	GSList *connections, *c;
+
+	s_con = nm_connection_get_setting_connection (CE_PAGE (self)->connection);
+	g_return_if_fail (s_con != NULL);
+
+	/* Interface name */
+	iface = nm_connection_get_virtual_iface_name (CE_PAGE (self)->connection);
+	gtk_entry_set_text (priv->interface_name, iface ? iface : "");
+
+	/* Slave connections */
+	connections = nm_remote_settings_list_connections (CE_PAGE (self)->settings);
+	for (c = connections; c; c = c->next)
+		connection_added (CE_PAGE (self)->settings, c->data, self);
+}
+
+static void
+finish_setup (CEPageMaster *self, gpointer unused, GError *error, gpointer user_data)
+{
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	GtkTreeSelection *selection;
+	GtkBuilder *builder;
+	NMSettingConnection *s_con;
+
+	if (error)
+		return;
+
+	builder = CE_PAGE (self)->builder;
+
+	priv->interface_name = GTK_ENTRY (gtk_builder_get_object (builder, "master_interface"));
+
+	priv->connections = GTK_TREE_VIEW (gtk_builder_get_object (builder, "master_connections"));
+	priv->connections_model = GTK_TREE_MODEL (gtk_builder_get_object (builder, "master_connections_model"));
+	gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (priv->connections_model),
+	                                 COL_NAME, name_sort_func,
+	                                 NULL, NULL);
+	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->connections_model),
+	                                      COL_NAME, GTK_SORT_ASCENDING);
+
+	priv->add = GTK_BUTTON (gtk_builder_get_object (builder, "master_connection_add"));
+	priv->edit = GTK_BUTTON (gtk_builder_get_object (builder, "master_connection_edit"));
+	priv->delete = GTK_BUTTON (gtk_builder_get_object (builder, "master_connection_delete"));
+
+	priv->toplevel = GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (priv->connections),
+	                                                      GTK_TYPE_WINDOW));
+
+	s_con = nm_connection_get_setting_connection (CE_PAGE (self)->connection);
+	priv->uuid = nm_setting_connection_get_uuid (s_con);
+
+	populate_ui (self);
+
+	g_signal_connect (CE_PAGE (self)->settings, NM_REMOTE_SETTINGS_NEW_CONNECTION,
+	                  G_CALLBACK (connection_added), self);
+
+	g_signal_connect (priv->interface_name, "changed", G_CALLBACK (stuff_changed), self);
+
+	g_signal_connect (priv->add, "clicked", G_CALLBACK (add_clicked), self);
+	g_signal_connect (priv->edit, "clicked", G_CALLBACK (edit_clicked), self);
+	g_signal_connect (priv->delete, "clicked", G_CALLBACK (delete_clicked), self);
+
+	g_signal_connect (priv->connections, "row-activated", G_CALLBACK (connection_double_clicked_cb), self);
+
+	selection = gtk_tree_view_get_selection (priv->connections);
+	g_signal_connect (selection, "changed", G_CALLBACK (connections_selection_changed_cb), self);
+	connections_selection_changed_cb (selection, self);
+}
+
+static void
+ui_to_setting (CEPageMaster *self)
+{
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	NMSettingConnection *s_con;
+	const char *connection_type;
+	NMSetting *setting;
+	const char *interface_name;
+
+	/* Find the "toplevel" NMSetting for this connection */
+	s_con = nm_connection_get_setting_connection (CE_PAGE (self)->connection);
+	connection_type = nm_setting_connection_get_connection_type (s_con);
+	setting = nm_connection_get_setting_by_name (CE_PAGE (self)->connection, connection_type);
+
+	/* Interface name */
+	interface_name = gtk_entry_get_text (priv->interface_name);
+	g_object_set (setting,
+	              "interface-name", interface_name,
+	              NULL);
+
+	/* Slaves are updated as they're edited, so nothing to do */
+}
+
+static gboolean
+validate (CEPage *page, NMConnection *connection, GError **error)
+{
+	CEPageMaster *self = CE_PAGE_MASTER (page);
+
+	/* Need at least one slave connection; we don't need to
+	 * recursively check that the connections are valid because they
+	 * can't end up in the table if they're not.
+	 */
+	if (!ce_page_master_has_slaves (self))
+		return FALSE;
+
+	ui_to_setting (self);
+
+	/* Subtype validate() method will validate the interface name */
+	return TRUE;
+}
+
+static void
+ce_page_master_init (CEPageMaster *self)
+{
+}
+
+static void
+ce_page_master_class_init (CEPageMasterClass *master_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (master_class);
+	CEPageClass *parent_class = CE_PAGE_CLASS (master_class);
+
+	g_type_class_add_private (object_class, sizeof (CEPageMasterPrivate));
+
+	/* virtual methods */
+	object_class->constructed = constructed;
+	object_class->dispose = dispose;
+
+	parent_class->validate = validate;
+
+	/* Signals */
+	signals[CONNECTION_ADDED] = 
+		g_signal_new ("connection-added",
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_FIRST,
+		              G_STRUCT_OFFSET (CEPageMasterClass, connection_added),
+		              NULL, NULL,
+		              g_cclosure_marshal_VOID__OBJECT,
+		              G_TYPE_NONE, 1,
+		              NM_TYPE_CONNECTION);
+
+	signals[CONNECTION_REMOVED] = 
+		g_signal_new ("connection-removed",
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_FIRST,
+		              G_STRUCT_OFFSET (CEPageMasterClass, connection_removed),
+		              NULL, NULL,
+		              g_cclosure_marshal_VOID__OBJECT,
+		              G_TYPE_NONE, 1,
+		              NM_TYPE_CONNECTION);
+}
+
+gboolean
+ce_page_master_has_slaves (CEPageMaster *self)
+{
+	CEPageMasterPrivate *priv = CE_PAGE_MASTER_GET_PRIVATE (self);
+	GtkTreeIter iter;
+
+	return gtk_tree_model_get_iter_first (priv->connections_model, &iter);
+}
diff --git a/src/connection-editor/page-master.h b/src/connection-editor/page-master.h
new file mode 100644
index 0000000..8d512a3
--- /dev/null
+++ b/src/connection-editor/page-master.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Connection editor -- Connection editor for NetworkManager
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2012 Red Hat, Inc.
+ */
+
+#ifndef __PAGE_MASTER_H__
+#define __PAGE_MASTER_H__
+
+#include <nm-connection.h>
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "ce-page.h"
+#include "new-connection.h"
+
+#define CE_TYPE_PAGE_MASTER            (ce_page_master_get_type ())
+#define CE_PAGE_MASTER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CE_TYPE_PAGE_MASTER, CEPageMaster))
+#define CE_PAGE_MASTER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CE_TYPE_PAGE_MASTER, CEPageMasterClass))
+#define CE_IS_PAGE_MASTER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CE_TYPE_PAGE_MASTER))
+#define CE_IS_PAGE_MASTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CE_TYPE_PAGE_MASTER))
+#define CE_PAGE_MASTER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), CE_TYPE_PAGE_MASTER, CEPageMasterClass))
+
+typedef struct {
+	CEPage parent;
+} CEPageMaster;
+
+typedef struct {
+	CEPageClass parent;
+
+	/* signals */
+	void (*connection_added)   (CEPageMaster *self, NMConnection *connection);
+	void (*connection_removed) (CEPageMaster *self, NMConnection *connection);
+
+	/* methods */
+	void (*add_slave) (CEPageMaster *self, NewConnectionResultFunc result_func);
+} CEPageMasterClass;
+
+GType ce_page_master_get_type (void);
+
+gboolean ce_page_master_has_slaves (CEPageMaster *self);
+
+#endif  /* __PAGE_MASTER_H__ */
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]