summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorjhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-19 21:15:26 +0000
committerjhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-19 21:15:26 +0000
commitdb0cb7abdc635e40273f0ad31e1126c98223ff2b (patch)
tree42bdc86ffe3235fcb3a4d0dd929ab25c0cac2ce8 /chrome
parentfd1d6998e6a96569e478c87220f6825a3e47851c (diff)
downloadchromium_src-db0cb7abdc635e40273f0ad31e1126c98223ff2b.zip
chromium_src-db0cb7abdc635e40273f0ad31e1126c98223ff2b.tar.gz
chromium_src-db0cb7abdc635e40273f0ad31e1126c98223ff2b.tar.bz2
Implement the remaining UI of the gtk AutoFill dialog.
BUG=none TEST=none Review URL: http://codereview.chromium.org/551065 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@36547 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/app/generated_resources.grd69
-rw-r--r--chrome/app/resources/locale_settings.grd4
-rw-r--r--chrome/browser/autofill/autofill_dialog_gtk.cc262
-rw-r--r--chrome/common/gtk_util.h5
4 files changed, 321 insertions, 19 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index d8f349f..0e851ba 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4587,6 +4587,75 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_AUTOFILL_NEW_CREDITCARD" desc="The profile label of a new credit card.">
New credit card
</message>
+ <message name="IDS_AUTOFILL_DIALOG_LABEL" desc="The label of the Label entry.">
+ Label
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_FIRST_NAME" desc="The label of the First name entry.">
+ First name
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_MIDDLE_NAME" desc="The label of the Middle name entry.">
+ Middle name
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_LAST_NAME" desc="The label of the Last name entry.">
+ Last name
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_EMAIL" desc="The label of the Email entry.">
+ Email
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_COMPANY_NAME" desc="The label of the Company name entry.">
+ Company name
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_ADDRESS_LINE_1" desc="The label of the Address Line 2 entry.">
+ Address line 1
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_ADDRESS_LINE_2" desc="The label of the Address Line 2 entry.">
+ Address line 2
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_CITY" desc="The label of the City entry.">
+ City/Town
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_STATE" desc="The label of the State entry.">
+ State/Province/District
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_ZIP_CODE" desc="The label of the Zip code entry.">
+ Zip code
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_COUNTRY" desc="The label of the Country entry.">
+ Country
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_PHONE" desc="The label of the Phone entry.">
+ Phone
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_FAX" desc="The label of the Fax entry.">
+ Fax
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_NAME_ON_CARD" desc="The label of the Name on card entry.">
+ Name on card
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_CREDIT_CARD_NUMBER" desc="The label of the Credit card number entry.">
+ Credit card number
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_CVC" desc="The label of the CVC entry.">
+ CVC
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_EXPIRATION_DATE" desc="The label of the Expiration date entry.">
+ Expiration date
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_BILLING_ADDRESS" desc="The label of the Billing address entry.">
+ Billing address
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_SHIPPING_ADDRESS" desc="The label of the Shipping address entry.">
+ Shippping
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_CHOOSE_EXISTING_ADDRESS" desc="The label of the Choose existing address entry.">
+ Choose existing address
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_SAME_AS_BILLING" desc="The label of the same as billing entry.">
+ same as billing
+ </message>
+ <message name="IDS_AUTOFILL_DIALOG_MAKE_DEFAULT" desc="The label of the Make this my default profile entry.">
+ Make this my default profile
+ </message>
<message name="IDS_THEMES_GROUP_NAME" desc="The title of the themes group">
Themes:
diff --git a/chrome/app/resources/locale_settings.grd b/chrome/app/resources/locale_settings.grd
index ae4e79d..0f593bb 100644
--- a/chrome/app/resources/locale_settings.grd
+++ b/chrome/app/resources/locale_settings.grd
@@ -678,12 +678,12 @@
<!-- The width of the AutoFill dialog in characters. -->
<message name="IDS_AUTOFILL_DIALOG_WIDTH_CHARS" use_name_for_id="true">
- 80
+ 98
</message>
<!-- The height of the AutoFill dialog in lines. -->
<message name="IDS_AUTOFILL_DIALOG_HEIGHT_LINES" use_name_for_id="true">
- 30
+ 35
</message>
</messages>
</release>
diff --git a/chrome/browser/autofill/autofill_dialog_gtk.cc b/chrome/browser/autofill/autofill_dialog_gtk.cc
index 3c7b1b3..5dac545 100644
--- a/chrome/browser/autofill/autofill_dialog_gtk.cc
+++ b/chrome/browser/autofill/autofill_dialog_gtk.cc
@@ -27,12 +27,11 @@ const char kDialogGroupTitleMarkup[] = "<span weight='bold'>%s</span>";
// How far we indent dialog widgets, in pixels.
const int kAutoFillDialogIndent = 5;
-// Adds an alignment around |widget| which indents the widget by
-// |kAutoFillDialogIndent|.
-GtkWidget* IndentWidget(GtkWidget* widget) {
+// Adds an alignment around |widget| which indents the widget by |offset|.
+GtkWidget* IndentWidget(GtkWidget* widget, int offset) {
GtkWidget* alignment = gtk_alignment_new(0, 0, 0, 0);
gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 0, 0,
- kAutoFillDialogIndent, 0);
+ offset, 0);
gtk_container_add(GTK_CONTAINER(alignment), widget);
return alignment;
}
@@ -48,8 +47,102 @@ void SetWhiteBackground(GtkWidget* widget) {
gtk_widget_destroy(entry);
}
+////////////////////////////////////////////////////////////////////////////////
+// Form Table helpers.
+//
+// The following functions can be used to create a form with labeled widgets.
+//
+
+// Creates a form table with dimensions |rows| x |cols|.
+GtkWidget* InitFormTable(int rows, int cols) {
+ // We have two table rows per form table row.
+ GtkWidget* table = gtk_table_new(rows * 2, cols, false);
+ gtk_table_set_row_spacings(GTK_TABLE(table), gtk_util::kControlSpacing);
+ gtk_table_set_col_spacings(GTK_TABLE(table), gtk_util::kFormControlSpacing);
+
+ // Leave no space between the label and the widget.
+ for (int i = 0; i < rows; i++)
+ gtk_table_set_row_spacing(GTK_TABLE(table), i * 2, 0);
+
+ return table;
+}
+
+// Sets the label of the form widget at |row|,|col|. The label is |len| columns
+// long.
+void FormTableSetLabel(
+ GtkWidget* table, int row, int col, int len, int label_id) {
+ // We have two table rows per form table row.
+ row *= 2;
+
+ const char* text =
+ (label_id) ? l10n_util::GetStringUTF8(label_id).c_str() : 0;
+ GtkWidget* label = gtk_label_new(text);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_table_attach(GTK_TABLE(table), label,
+ col, col + len, // Left col, right col.
+ row, row + 1, // Top row, bottom row.
+ GTK_FILL, GTK_FILL, // Options.
+ 0, 0); // Padding.
+}
+
+// Sets the form widget at |row|,|col|. The widget fills up |len| columns. If
+// |expand| is true, the widget will expand to fill all of the extra space in
+// the table row.
+void FormTableSetWidget(GtkWidget* table,
+ GtkWidget* widget,
+ int row, int col,
+ int len, bool expand) {
+ const GtkAttachOptions expand_option =
+ static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND);
+ GtkAttachOptions xoption = (expand) ? expand_option : GTK_FILL;
+
+ // We have two table rows per form table row.
+ row *= 2;
+ gtk_table_attach(GTK_TABLE(table), widget,
+ col, col + len, // Left col, right col.
+ row + 1, row + 2, // Top row, bottom row.
+ xoption, GTK_FILL, // Options.
+ 0, 0); // Padding.
+}
+
+// Adds a labeled entry box to the form table at |row|,|col|. The entry widget
+// fills up |len| columns. The returned widget is owned by |table| and should
+// not be destroyed.
+GtkWidget* FormTableAddEntry(
+ GtkWidget* table, int row, int col, int len, int label_id) {
+ FormTableSetLabel(table, row, col, len, label_id);
+
+ GtkWidget* entry = gtk_entry_new();
+ FormTableSetWidget(table, entry, row, col, len, false);
+
+ return entry;
+}
+
+// Adds a labeled entry box to the form table that will expand to fill extra
+// space in the table row.
+GtkWidget* FormTableAddExpandedEntry(
+ GtkWidget* table, int row, int col, int len, int label_id) {
+ FormTableSetLabel(table, row, col, len, label_id);
+
+ GtkWidget* entry = gtk_entry_new();
+ FormTableSetWidget(table, entry, row, col, len, true);
+
+ return entry;
+}
+
+// Adds a sized entry box to the form table. The entry widget width is set to
+// |char_len|.
+void FormTableAddSizedEntry(
+ GtkWidget* table, int row, int col, int char_len, int label_id) {
+ GtkWidget* entry = FormTableAddEntry(table, row, col, 1, label_id);
+ gtk_entry_set_width_chars(GTK_ENTRY(entry), char_len);
+}
+
} // namespace
+////////////////////////////////////////////////////////////////////////////////
+// AutoFillDialog
+//
// The contents of the AutoFill dialog. This dialog allows users to add, edit
// and remove AutoFill profiles.
class AutoFillDialog {
@@ -80,6 +173,12 @@ class AutoFillDialog {
int button_id,
GCallback clicked_callback);
+ // Initializes the expander, frame and table widgets used to hold the address
+ // and credit card forms. |name_id| is the resource id of the label of the
+ // expander widget. The content vbox widget is returned in |content_vbox|.
+ // Returns the expander widget.
+ GtkWidget* InitGroupContentArea(int name_id, GtkWidget** content_vbox);
+
// Returns a GtkExpander that is added to the appropriate vbox. Each method
// adds the necessary widgets and layout required to fill out information
// for either an address or a credit card.
@@ -155,7 +254,8 @@ AutoFillDialog::AutoFillDialog(std::vector<AutoFillProfile>* profiles,
gtk_container_add(GTK_CONTAINER(frame_event_box), frame);
// The content vbox.
- GtkWidget* outer_vbox = gtk_vbox_new(true, 0);
+ GtkWidget* outer_vbox = gtk_vbox_new(false, 0);
+ gtk_box_set_spacing(GTK_BOX(outer_vbox), gtk_util::kContentAreaSpacing);
gtk_container_add(GTK_CONTAINER(frame), outer_vbox);
addresses_vbox_ = InitGroup(IDS_AUTOFILL_ADDRESSES_GROUP_NAME,
@@ -193,7 +293,7 @@ void AutoFillDialog::OnAddAddressClicked(GtkButton* button,
GtkWidget* new_address = dialog->AddNewAddress();
gtk_box_pack_start(GTK_BOX(dialog->addresses_vbox_), new_address,
FALSE, FALSE, 0);
- gtk_widget_show(new_address);
+ gtk_widget_show_all(new_address);
}
// static
@@ -202,7 +302,7 @@ void AutoFillDialog::OnAddCreditCardClicked(GtkButton* button,
GtkWidget* new_creditcard = dialog->AddNewCreditCard();
gtk_box_pack_start(GTK_BOX(dialog->creditcards_vbox_), new_creditcard,
FALSE, FALSE, 0);
- gtk_widget_show(new_creditcard);
+ gtk_widget_show_all(new_creditcard);
}
GtkWidget* AutoFillDialog::InitGroup(int name_id,
@@ -218,7 +318,9 @@ GtkWidget* AutoFillDialog::InitGroup(int name_id,
gtk_label_set_markup(GTK_LABEL(label), markup);
g_free(markup);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
- gtk_box_pack_start(GTK_BOX(vbox), IndentWidget(label), FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox),
+ IndentWidget(label, kAutoFillDialogIndent),
+ FALSE, FALSE, 0);
// Separator.
GtkWidget* separator = gtk_hseparator_new();
@@ -226,27 +328,155 @@ GtkWidget* AutoFillDialog::InitGroup(int name_id,
// Add profile button.
GtkWidget* button = gtk_button_new_with_label(
- l10n_util::GetStringUTF8(name_id).c_str());
+ l10n_util::GetStringUTF8(button_id).c_str());
g_signal_connect(button, "clicked", clicked_callback, this);
- gtk_box_pack_end_defaults(GTK_BOX(vbox), IndentWidget(button));
+ gtk_box_pack_end_defaults(GTK_BOX(vbox),
+ IndentWidget(button, kAutoFillDialogIndent));
return vbox;
}
+GtkWidget* AutoFillDialog::InitGroupContentArea(int name_id,
+ GtkWidget** content_vbox) {
+ GtkWidget* expander = gtk_expander_new(
+ l10n_util::GetStringUTF8(name_id).c_str());
+
+ GtkWidget* frame = gtk_frame_new(NULL);
+ gtk_container_add(GTK_CONTAINER(expander), frame);
+
+ GtkWidget* vbox = gtk_vbox_new(false, 0);
+ gtk_box_set_spacing(GTK_BOX(vbox), gtk_util::kControlSpacing);
+ GtkWidget* vbox_alignment = gtk_alignment_new(0, 0, 0, 0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(vbox_alignment),
+ gtk_util::kControlSpacing,
+ gtk_util::kControlSpacing,
+ gtk_util::kGroupIndent,
+ 0);
+ gtk_container_add(GTK_CONTAINER(vbox_alignment), vbox);
+ gtk_container_add(GTK_CONTAINER(frame), vbox_alignment);
+
+ // Make it expand by default.
+ gtk_expander_set_expanded(GTK_EXPANDER(expander), true);
+
+ *content_vbox = vbox;
+ return expander;
+}
+
GtkWidget* AutoFillDialog::AddNewAddress() {
- GtkWidget* address = gtk_expander_new(
- l10n_util::GetStringUTF8(IDS_AUTOFILL_NEW_ADDRESS).c_str());
+ GtkWidget* vbox;
+ GtkWidget* address = InitGroupContentArea(IDS_AUTOFILL_NEW_ADDRESS, &vbox);
+
+ GtkWidget* table = InitFormTable(5, 3);
+ gtk_box_pack_start_defaults(GTK_BOX(vbox), table);
+
+ FormTableAddEntry(table, 0, 0, 1, IDS_AUTOFILL_DIALOG_LABEL);
+ FormTableAddEntry(table, 1, 0, 1, IDS_AUTOFILL_DIALOG_FIRST_NAME);
+ FormTableAddEntry(table, 1, 1, 1, IDS_AUTOFILL_DIALOG_MIDDLE_NAME);
+ FormTableAddEntry(table, 1, 2, 1, IDS_AUTOFILL_DIALOG_LAST_NAME);
+ FormTableAddEntry(table, 2, 0, 1, IDS_AUTOFILL_DIALOG_EMAIL);
+ FormTableAddEntry(table, 2, 1, 1, IDS_AUTOFILL_DIALOG_COMPANY_NAME);
+ FormTableAddEntry(table, 3, 0, 2, IDS_AUTOFILL_DIALOG_ADDRESS_LINE_1);
+ FormTableAddEntry(table, 4, 0, 2, IDS_AUTOFILL_DIALOG_ADDRESS_LINE_2);
+
+ // TODO(jhawkins): If there's not a default profile, automatically check this
+ // check button.
+ GtkWidget* default_check = gtk_check_button_new_with_label(
+ l10n_util::GetStringUTF8(IDS_AUTOFILL_DIALOG_MAKE_DEFAULT).c_str());
+ FormTableSetWidget(table, default_check, 0, 1, 1, false);
+
+ GtkWidget* address_table = InitFormTable(1, 4);
+ gtk_box_pack_start_defaults(GTK_BOX(vbox), address_table);
+
+ FormTableAddEntry(address_table, 0, 0, 1, IDS_AUTOFILL_DIALOG_CITY);
+ FormTableAddEntry(address_table, 0, 1, 1, IDS_AUTOFILL_DIALOG_STATE);
+ FormTableAddSizedEntry(address_table, 0, 2, 7, IDS_AUTOFILL_DIALOG_ZIP_CODE);
+ FormTableAddSizedEntry(address_table, 0, 3, 10, IDS_AUTOFILL_DIALOG_COUNTRY);
+
+ GtkWidget* phone_table = InitFormTable(1, 8);
+ gtk_box_pack_start_defaults(GTK_BOX(vbox), phone_table);
+
+ FormTableAddSizedEntry(phone_table, 0, 0, 4, IDS_AUTOFILL_DIALOG_PHONE);
+ FormTableAddSizedEntry(phone_table, 0, 1, 4, 0);
+ FormTableAddEntry(phone_table, 0, 2, 2, 0);
+ FormTableAddSizedEntry(phone_table, 0, 4, 4, IDS_AUTOFILL_DIALOG_FAX);
+ FormTableAddSizedEntry(phone_table, 0, 5, 4, 0);
+ FormTableAddEntry(phone_table, 0, 6, 2, 0);
- // TODO(jhawkins): Implement the address form.
+ GtkWidget* button = gtk_button_new_with_label(
+ l10n_util::GetStringUTF8(IDS_AUTOFILL_DELETE_BUTTON).c_str());
+ GtkWidget* alignment = gtk_alignment_new(0, 0, 0, 0);
+ gtk_container_add(GTK_CONTAINER(alignment), button);
+ gtk_box_pack_start_defaults(GTK_BOX(vbox), alignment);
return address;
}
GtkWidget* AutoFillDialog::AddNewCreditCard() {
- GtkWidget* credit_card = gtk_expander_new(
- l10n_util::GetStringUTF8(IDS_AUTOFILL_NEW_CREDITCARD).c_str());
+ GtkWidget* vbox;
+ GtkWidget* credit_card = InitGroupContentArea(IDS_AUTOFILL_NEW_CREDITCARD,
+ &vbox);
+
+ GtkWidget* label_table = InitFormTable(1, 2);
+ gtk_box_pack_start_defaults(GTK_BOX(vbox), label_table);
+
+ FormTableAddEntry(label_table, 0, 0, 1, IDS_AUTOFILL_DIALOG_LABEL);
+
+ // TODO(jhawkins): If there's not a default profile, automatically check this
+ // check button.
+ GtkWidget* default_check = gtk_check_button_new_with_label(
+ l10n_util::GetStringUTF8(IDS_AUTOFILL_DIALOG_MAKE_DEFAULT).c_str());
+ FormTableSetWidget(label_table, default_check, 0, 1, 1, true);
+
+ GtkWidget* name_cc_table = InitFormTable(2, 6);
+ gtk_box_pack_start_defaults(GTK_BOX(vbox), name_cc_table);
- // TODO(jhawkins): Implement the credit card form.
+ FormTableAddExpandedEntry(name_cc_table, 0, 0, 3,
+ IDS_AUTOFILL_DIALOG_NAME_ON_CARD);
+ FormTableAddExpandedEntry(name_cc_table, 1, 0, 3,
+ IDS_AUTOFILL_DIALOG_CREDIT_CARD_NUMBER);
+ FormTableAddSizedEntry(name_cc_table, 1, 3, 2, 0);
+ FormTableAddSizedEntry(name_cc_table, 1, 4, 4, 0);
+ FormTableAddSizedEntry(name_cc_table, 1, 5, 5, IDS_AUTOFILL_DIALOG_CVC);
+
+ FormTableSetLabel(name_cc_table, 1, 3, 2,
+ IDS_AUTOFILL_DIALOG_EXPIRATION_DATE);
+
+ gtk_table_set_col_spacing(GTK_TABLE(name_cc_table), 3, 2);
+
+ GtkWidget* addresses_table = InitFormTable(2, 5);
+ gtk_box_pack_start_defaults(GTK_BOX(vbox), addresses_table);
+
+ FormTableSetLabel(addresses_table, 0, 0, 3,
+ IDS_AUTOFILL_DIALOG_BILLING_ADDRESS);
+
+ GtkWidget* billing = gtk_combo_box_new_text();
+ std::string combo_text = l10n_util::GetStringUTF8(
+ IDS_AUTOFILL_DIALOG_CHOOSE_EXISTING_ADDRESS);
+ gtk_combo_box_append_text(GTK_COMBO_BOX(billing), combo_text.c_str());
+ gtk_combo_box_set_active(GTK_COMBO_BOX(billing), 0);
+ FormTableSetWidget(addresses_table, billing, 0, 0, 2, false);
+
+ FormTableSetLabel(addresses_table, 1, 0, 3,
+ IDS_AUTOFILL_DIALOG_SHIPPING_ADDRESS);
+
+ GtkWidget* shipping = gtk_combo_box_new_text();
+ combo_text = l10n_util::GetStringUTF8(IDS_AUTOFILL_DIALOG_SAME_AS_BILLING);
+ gtk_combo_box_append_text(GTK_COMBO_BOX(shipping), combo_text.c_str());
+ gtk_combo_box_set_active(GTK_COMBO_BOX(shipping), 0);
+ FormTableSetWidget(addresses_table, shipping, 1, 0, 2, false);
+
+ GtkWidget* phone_table = InitFormTable(1, 4);
+ gtk_box_pack_start_defaults(GTK_BOX(vbox), phone_table);
+
+ FormTableAddSizedEntry(phone_table, 0, 0, 4, IDS_AUTOFILL_DIALOG_PHONE);
+ FormTableAddSizedEntry(phone_table, 0, 1, 4, 0);
+ FormTableAddEntry(phone_table, 0, 2, 2, 0);
+
+ GtkWidget* button = gtk_button_new_with_label(
+ l10n_util::GetStringUTF8(IDS_AUTOFILL_DELETE_BUTTON).c_str());
+ GtkWidget* alignment = gtk_alignment_new(0, 0, 0, 0);
+ gtk_container_add(GTK_CONTAINER(alignment), button);
+ gtk_box_pack_start_defaults(GTK_BOX(vbox), alignment);
return credit_card;
}
diff --git a/chrome/common/gtk_util.h b/chrome/common/gtk_util.h
index 4bf75fd..2c0c9f0 100644
--- a/chrome/common/gtk_util.h
+++ b/chrome/common/gtk_util.h
@@ -42,12 +42,15 @@ const int kLabelSpacing = 12;
// Indent of the controls within each group.
const int kGroupIndent = 12;
-// Space around the outsides of a dialog's contents.
+// Space around the outside of a dialog's contents.
const int kContentAreaBorder = 12;
// Spacing between groups of controls.
const int kContentAreaSpacing = 18;
+// Horizontal Spacing between controls in a form.
+const int kFormControlSpacing = 10;
+
// Create a table of labeled controls, using proper spacing and alignment.
// Arguments should be pairs of const char*, GtkWidget*, concluding with a
// NULL. The first argument is a vector in which to place all labels