[gnome-control-center] Add user-accounts dialog
- From: William Jon McCann <mccann src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-control-center] Add user-accounts dialog
- Date: Sat, 30 Oct 2010 22:42:56 +0000 (UTC)
commit bf5cb4401669f4881bb7191ecbbcc957ef456950
Author: William Jon McCann <jmccann redhat com>
Date: Sat Oct 30 16:14:30 2010 -0400
Add user-accounts dialog
Copied from the accounts-dialog git module.
configure.ac | 16 +-
panels/Makefile.am | 1 +
panels/user-accounts/Makefile.am | 102 ++
panels/user-accounts/data/Makefile.am | 29 +
panels/user-accounts/data/account-dialog.ui | 268 ++++
panels/user-accounts/data/account-fingerprint.ui | 278 +++++
.../data/gnome-user-accounts-panel.desktop.in.in | 15 +
panels/user-accounts/data/icons/Makefile.am | 31 +
.../data/icons/gnome-about-me-lock-open.png | Bin 0 -> 2913 bytes
.../data/icons/gnome-about-me-lock.png | Bin 0 -> 2978 bytes
.../user-accounts/data/icons/left-index-finger.png | Bin 0 -> 1515 bytes
.../user-accounts/data/icons/left-index-finger.svg | 177 +++
.../data/icons/left-little-finger.png | Bin 0 -> 1500 bytes
.../data/icons/left-little-finger.svg | 180 +++
.../data/icons/left-middle-finger.png | Bin 0 -> 1483 bytes
.../data/icons/left-middle-finger.svg | 180 +++
.../user-accounts/data/icons/left-ring-finger.png | Bin 0 -> 1512 bytes
.../user-accounts/data/icons/left-ring-finger.svg | 180 +++
panels/user-accounts/data/icons/left-thumb.png | Bin 0 -> 1512 bytes
panels/user-accounts/data/icons/left-thumb.svg | 180 +++
panels/user-accounts/data/icons/print_error.png | Bin 0 -> 4160 bytes
panels/user-accounts/data/icons/print_error.svg | 525 ++++++++
panels/user-accounts/data/icons/print_ok.png | Bin 0 -> 3677 bytes
panels/user-accounts/data/icons/print_ok.svg | 310 +++++
.../data/icons/right-index-finger.png | Bin 0 -> 1506 bytes
.../data/icons/right-index-finger.svg | 179 +++
.../data/icons/right-little-finger.png | Bin 0 -> 1479 bytes
.../data/icons/right-little-finger.svg | 182 +++
.../data/icons/right-middle-finger.png | Bin 0 -> 1468 bytes
.../data/icons/right-middle-finger.svg | 182 +++
.../user-accounts/data/icons/right-ring-finger.png | Bin 0 -> 1506 bytes
.../user-accounts/data/icons/right-ring-finger.svg | 182 +++
panels/user-accounts/data/icons/right-thumb.png | Bin 0 -> 1486 bytes
panels/user-accounts/data/icons/right-thumb.svg | 182 +++
panels/user-accounts/data/language-chooser.ui | 90 ++
panels/user-accounts/data/password-dialog.ui | 531 ++++++++
panels/user-accounts/data/photo-dialog.ui | 304 +++++
panels/user-accounts/data/user-accounts-dialog.ui | 726 +++++++++++
panels/user-accounts/fingerprint-strings.h | 111 ++
panels/user-accounts/fprintd-marshal.list | 1 +
panels/user-accounts/gdm-languages.c | 1003 +++++++++++++++
panels/user-accounts/gdm-languages.h | 41 +
panels/user-accounts/locarchive.h | 97 ++
panels/user-accounts/run-passwd.c | 772 ++++++++++++
panels/user-accounts/run-passwd.h | 58 +
panels/user-accounts/um-account-dialog.c | 493 ++++++++
panels/user-accounts/um-account-dialog.h | 43 +
panels/user-accounts/um-account-type.c | 43 +
panels/user-accounts/um-account-type.h | 37 +
panels/user-accounts/um-crop-area.c | 817 +++++++++++++
panels/user-accounts/um-crop-area.h | 65 +
panels/user-accounts/um-editable-button.c | 403 ++++++
panels/user-accounts/um-editable-button.h | 72 ++
panels/user-accounts/um-editable-combo.c | 439 +++++++
panels/user-accounts/um-editable-combo.h | 76 ++
panels/user-accounts/um-editable-entry.c | 489 ++++++++
panels/user-accounts/um-editable-entry.h | 72 ++
panels/user-accounts/um-fingerprint-dialog.c | 674 ++++++++++
panels/user-accounts/um-fingerprint-dialog.h | 28 +
panels/user-accounts/um-language-dialog.c | 555 +++++++++
panels/user-accounts/um-language-dialog.h | 49 +
panels/user-accounts/um-lockbutton.c | 637 ++++++++++
panels/user-accounts/um-lockbutton.h | 70 ++
panels/user-accounts/um-login-options.c | 433 +++++++
panels/user-accounts/um-login-options.h | 34 +
panels/user-accounts/um-password-dialog.c | 834 +++++++++++++
panels/user-accounts/um-password-dialog.h | 43 +
panels/user-accounts/um-photo-dialog.c | 693 +++++++++++
panels/user-accounts/um-photo-dialog.h | 39 +
panels/user-accounts/um-strength-bar.c | 261 ++++
panels/user-accounts/um-strength-bar.h | 62 +
panels/user-accounts/um-user-manager.c | 624 ++++++++++
panels/user-accounts/um-user-manager.h | 112 ++
panels/user-accounts/um-user-module.c | 41 +
panels/user-accounts/um-user-panel.c | 1286 ++++++++++++++++++++
panels/user-accounts/um-user-panel.h | 59 +
panels/user-accounts/um-user.c | 1093 +++++++++++++++++
panels/user-accounts/um-user.h | 107 ++
panels/user-accounts/um-utils.c | 382 ++++++
panels/user-accounts/um-utils.h | 56 +
po/POTFILES.in | 19 +
po/POTFILES.skip | 2 +
82 files changed, 18354 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 72f60a1..867f15c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -94,7 +94,7 @@ PKG_CHECK_MODULES(DBUS, dbus-1 dbus-glib-1)
PKG_CHECK_MODULES(GNOME_DESKTOP, gnome-desktop-3.0)
PKG_CHECK_MODULES(DEFAULT_APPLICATIONS_CAPPLET, libxml-2.0)
PKG_CHECK_MODULES(GSD_DBUS, gnome-settings-daemon)
-PKG_CHECK_MODULES(GIO, gio-2.0)
+PKG_CHECK_MODULES(GIO, gio-2.0 gio-unix-2.0)
PKG_CHECK_MODULES(XML, libxml-2.0)
PKG_CHECK_MODULES(CANBERRA, libcanberra-gtk3 >= $CANBERRA_REQUIRED_VERSION)
AC_SUBST(CANBERRA_CFLAGS)
@@ -112,6 +112,16 @@ AC_SUBST(GTK_ENGINE_DIR)
PKG_CHECK_MODULES(GLIB, glib-2.0)
+PKG_CHECK_MODULES(POLKIT, polkit-gobject-1)
+PKG_CHECK_MODULES(CHEESE, gstreamer-0.10 cheese-gtk >= 2.29.90, have_cheese=yes, have_cheese=no)
+
+if test x$have_cheese = xyes ; then
+ AC_DEFINE(HAVE_CHEESE, 1, [Define to 1 to enable cheese webcam support])
+fi
+
+AC_DEFINE_UNQUOTED([ISO_CODES_PREFIX],["`$PKG_CONFIG --variable=prefix iso-codes`"],[ISO codes prefix])
+ISO_CODES=iso-codes
+
dnl
dnl Check for Xft version 2; we build in extra functionality to the font capplet
dnl when we have it.
@@ -361,6 +371,10 @@ panels/sound/data/icons/scalable/devices/Makefile
panels/sound/data/sounds/Makefile
panels/universal-access/Makefile
panels/universal-access/gnome-universal-access-panel.desktop.in
+panels/user-accounts/Makefile
+panels/user-accounts/data/Makefile
+panels/user-accounts/data/gnome-user-accounts-panel.desktop.in
+panels/user-accounts/data/icons/Makefile
po/Makefile.in
shell/Makefile
shell/gnome-control-center.desktop.in
diff --git a/panels/Makefile.am b/panels/Makefile.am
index 225fcbc..945270e 100644
--- a/panels/Makefile.am
+++ b/panels/Makefile.am
@@ -8,6 +8,7 @@ SUBDIRS= \
default-applications \
keybindings \
universal-access \
+ user-accounts \
datetime
-include $(top_srcdir)/git.mk
diff --git a/panels/user-accounts/Makefile.am b/panels/user-accounts/Makefile.am
new file mode 100644
index 0000000..8cebc10
--- /dev/null
+++ b/panels/user-accounts/Makefile.am
@@ -0,0 +1,102 @@
+SUBDIRS = data
+
+# This is used in GNOMECC_CAPPLETS_CFLAGS
+cappletname = user-accounts
+NULL =
+
+ccpanelsdir = $(PANELS_DIR)
+ccpanels_LTLIBRARIES = libuser-accounts.la
+
+AM_CPPFLAGS = \
+ $(GNOMECC_CAPPLETS_CFLAGS) \
+ -DDATADIR=\""$(datadir)"\" \
+ -DUIDIR=\""$(pkgdatadir)/ui/user-accounts"\" \
+ -DLIBLOCALEDIR=\""$(prefix)/lib/locale"\" \
+ -DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
+ -DUM_PIXMAP_DIR=\""$(pkgdatadir)/pixmaps"\" \
+ $(PANEL_CFLAGS) \
+ $(GNOME_DESKTOP_CFLAGS) \
+ $(POLKIT_CFLAGS) \
+ $(CHEESE_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GIO_CFLAGS) \
+ $(WARN_CFLAGS)
+
+MARSHALFILES = marshal.c marshal.h
+BUILT_SOURCES = $(MARSHALFILES)
+
+marshal.h: fprintd-marshal.list
+ @GLIB_GENMARSHAL@ --prefix=fprintd_marshal $< --header > $@
+marshal.c: fprintd-marshal.list
+ @GLIB_GENMARSHAL@ --prefix=fprintd_marshal $< --body --header > $@
+
+libuser_accounts_la_CFLAGS = \
+ $(PANEL_CFLAGS)
+
+libuser_accounts_la_SOURCES = \
+ gdm-languages.h \
+ gdm-languages.c \
+ um-account-type.h \
+ um-account-type.c \
+ um-user.h \
+ um-user.c \
+ um-user-manager.h \
+ um-user-manager.c \
+ um-account-dialog.h \
+ um-account-dialog.c \
+ um-language-dialog.h \
+ um-language-dialog.c \
+ um-lockbutton.h \
+ um-lockbutton.c \
+ um-login-options.h \
+ um-login-options.c \
+ um-password-dialog.h \
+ um-password-dialog.c \
+ um-photo-dialog.h \
+ um-photo-dialog.c \
+ um-crop-area.h \
+ um-crop-area.c \
+ um-fingerprint-dialog.h \
+ um-fingerprint-dialog.c \
+ um-utils.h \
+ um-utils.c \
+ fingerprint-strings.h \
+ um-strength-bar.h \
+ um-strength-bar.c \
+ run-passwd.h \
+ run-passwd.c \
+ $(MARSHALFILES) \
+ um-editable-button.h \
+ um-editable-button.c \
+ um-editable-entry.h \
+ um-editable-entry.c \
+ um-editable-combo.h \
+ um-editable-combo.c \
+ um-user-panel.h \
+ um-user-panel.c \
+ um-user-module.c
+
+libuser_accounts_la_LIBADD = \
+ $(PANEL_LIBS) \
+ $(GNOME_DESKTOP_LIBS) \
+ $(POLKIT_LIBS) \
+ $(CHEESE_LIBS) \
+ $(DBUS_LIBS) \
+ $(GIO_LIBS) \
+ -lcrypt
+
+libuser_accounts_la_LDFLAGS = $(PANEL_LDFLAGS)
+
+EXTRA_DIST = \
+ locarchive.h \
+ fprintd-marshal.list
+
+CLEANFILES = \
+ $(BUILT_SOURCES) \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in
+
+-include $(top_srcdir)/git.mk
diff --git a/panels/user-accounts/data/Makefile.am b/panels/user-accounts/data/Makefile.am
new file mode 100644
index 0000000..fbc3825
--- /dev/null
+++ b/panels/user-accounts/data/Makefile.am
@@ -0,0 +1,29 @@
+SUBDIRS = icons
+
+uidir = $(pkgdatadir)/ui/user-accounts
+ui_DATA = \
+ account-dialog.ui \
+ language-chooser.ui \
+ password-dialog.ui \
+ photo-dialog.ui \
+ user-accounts-dialog.ui \
+ account-fingerprint.ui
+
+ INTLTOOL_DESKTOP_RULE@
+
+desktopdir = $(datadir)/applications
+Desktop_in_files = gnome-user-accounts-panel.desktop.in
+desktop_DATA = $(Desktop_in_files:.desktop.in=.desktop)
+
+EXTRA_DIST = \
+ gnome-user-accounts-panel.desktop.in.in \
+ $(ui_DATA)
+
+CLEANFILES = \
+ gnome-user-accounts-panel.desktop \
+ $(NULL)
+
+DISTCLEANFILES = \
+ $(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/panels/user-accounts/data/account-dialog.ui b/panels/user-accounts/data/account-dialog.ui
new file mode 100644
index 0000000..5e229ce
--- /dev/null
+++ b/panels/user-accounts/data/account-dialog.ui
@@ -0,0 +1,268 @@
+<?xml version="1.0"?>
+<interface>
+ <!-- interface-requires gtk+ 2.12 -->
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkListStore" id="username-model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="account-type-model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gint -->
+ <column type="gint"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" context="Account type">Standard</col>
+ <col id="1">0</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="Account type">Administrator</col>
+ <col id="1">1</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="Account type">Supervised</col>
+ <col id="1">2</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkDialog" id="dialog">
+ <property name="border_width">5</property>
+ <property name="title"> </property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="icon_name">system-users</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="content-area">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="border_width">10</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">5</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkComboBoxEntry" id="username-combo">
+ <property name="visible">True</property>
+ <property name="model">username-model</property>
+ <property name="text_column">0</property>
+ <child internal-child="entry">
+ <object class="GtkEntry" id="username-entry">
+ <property name="activates_default">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Username:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">username-combo</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Create new account</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.200000"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="activates_default">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Full name:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">name-entry</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Account Type:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">account-type-combo</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="account-type-combo">
+ <property name="visible">True</property>
+ <property name="model">account-type-model</property>
+ <child>
+ <object class="GtkCellRendererText" id="account-type-cell"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area9">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel-button">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ok-button">
+ <property name="label" translatable="yes">Cr_eate</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">cancel-button</action-widget>
+ <action-widget response="0">ok-button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/panels/user-accounts/data/account-fingerprint.ui b/panels/user-accounts/data/account-fingerprint.ui
new file mode 100644
index 0000000..93a194e
--- /dev/null
+++ b/panels/user-accounts/data/account-fingerprint.ui
@@ -0,0 +1,278 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkListStore" id="model1">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Left thumb</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Left middle finger</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Left ring finger</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Left little finger</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Right thumb</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Right middle finger</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Right ring finger</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Right little finger</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkAssistant" id="assistant">
+ <property name="border_width">12</property>
+ <property name="title" translatable="yes">Enable Fingerprint Login</property>
+ <property name="icon_name">system-users</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkVBox" id="page1">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox74">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkImage" id="image">
+ <property name="visible">True</property>
+ <property name="stock">gtk-dialog-info</property>
+ <property name="icon-size">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="intro-label">
+ <property name="visible">True</property>
+ <property name="label">To enable fingerprint login, you need to save one of your fingerprints, using the Acme Foobar 5000.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox70">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton1">
+ <property name="label" translatable="yes">Right index finger</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton2">
+ <property name="label" translatable="yes">Left index finger</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="radiobutton3-hbox">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Other finger: </property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="finger_combobox">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="model">model1</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkVBox" id="page2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="enroll-label">
+ <property name="visible">True</property>
+ <property name="label">In order to save your fingerprints, you need to swipe your thumb on the "Acme foobar" device.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="enroll_hbox">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-no</property>
+ <property name="icon-size">6</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="stock">gtk-no</property>
+ <property name="icon-size">6</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="stock">gtk-no</property>
+ <property name="icon-size">6</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="stock">gtk-no</property>
+ <property name="icon-size">6</property>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="stock">gtk-no</property>
+ <property name="icon-size">6</property>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status-label">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="page3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Your fingerprint was successfully saved. You should now be able to log in using your fingerprint reader.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="page_type">summary</property>
+ </packing>
+ </child>
+ </object>
+ <object class="GtkSizeGroup" id="sizegroup">
+ <property name="mode">both</property>
+ <widgets>
+ <widget name="radiobutton1"/>
+ <widget name="radiobutton2"/>
+ <widget name="radiobutton3-hbox"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in b/panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in
new file mode 100644
index 0000000..af3102f
--- /dev/null
+++ b/panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in
@@ -0,0 +1,15 @@
+[Desktop Entry]
+_Name=User Accounts
+_Comment=Add or remove users
+Exec=gnome-control-center user-accounts
+Icon=system-users
+Terminal=false
+Type=Application
+StartupNotify=true
+Categories=System;Settings;X-GNOME-Settings-Panel;
+OnlyShowIn=GNOME;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=gnome-control-center
+X-GNOME-Bugzilla-Component=user-accounts
+X-GNOME-Bugzilla-Version= VERSION@
+X-GNOME-Settings-Panel=user-accounts
diff --git a/panels/user-accounts/data/icons/Makefile.am b/panels/user-accounts/data/icons/Makefile.am
new file mode 100644
index 0000000..218ac84
--- /dev/null
+++ b/panels/user-accounts/data/icons/Makefile.am
@@ -0,0 +1,31 @@
+pixmapsdir = $(pkgdatadir)/pixmaps
+pixmaps_DATA = \
+ gnome-about-me-lock.png \
+ gnome-about-me-lock-open.png \
+ left-index-finger.svg \
+ left-little-finger.svg \
+ left-middle-finger.svg \
+ left-ring-finger.svg \
+ left-thumb.svg \
+ print_error.svg \
+ print_ok.svg \
+ right-index-finger.svg \
+ right-little-finger.svg \
+ right-middle-finger.svg \
+ right-ring-finger.svg \
+ right-thumb.svg \
+ left-index-finger.png \
+ left-middle-finger.png \
+ left-little-finger.png \
+ left-ring-finger.png \
+ left-thumb.png \
+ print_error.png \
+ print_ok.png \
+ right-index-finger.png \
+ right-middle-finger.png \
+ right-little-finger.png \
+ right-ring-finger.png \
+ right-thumb.png
+
+EXTRA_DIST = $(pixmaps_DATA)
+
diff --git a/panels/user-accounts/data/icons/gnome-about-me-lock-open.png b/panels/user-accounts/data/icons/gnome-about-me-lock-open.png
new file mode 100644
index 0000000..1f01905
Binary files /dev/null and b/panels/user-accounts/data/icons/gnome-about-me-lock-open.png differ
diff --git a/panels/user-accounts/data/icons/gnome-about-me-lock.png b/panels/user-accounts/data/icons/gnome-about-me-lock.png
new file mode 100644
index 0000000..f24ae3a
Binary files /dev/null and b/panels/user-accounts/data/icons/gnome-about-me-lock.png differ
diff --git a/panels/user-accounts/data/icons/left-index-finger.png b/panels/user-accounts/data/icons/left-index-finger.png
new file mode 100644
index 0000000..1a9cb2c
Binary files /dev/null and b/panels/user-accounts/data/icons/left-index-finger.png differ
diff --git a/panels/user-accounts/data/icons/left-index-finger.svg b/panels/user-accounts/data/icons/left-index-finger.svg
new file mode 100644
index 0000000..3c36aea
--- /dev/null
+++ b/panels/user-accounts/data/icons/left-index-finger.svg
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 40.425 46.214"
+ enable-background="new 0 0 40.425 46.214"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="left-index-finger.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
+ id="metadata44"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs42"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 23.107 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="40.424999 : 23.107 : 1"
+ inkscape:persp3d-origin="20.2125 : 15.404667 : 1"
+ id="perspective46" />
+
+
+
+
+
+
+<radialGradient
+ r="8.341651"
+ fy="9.3411446"
+ fx="38.658855"
+ cy="9.3411446"
+ cx="38.658855"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2479"
+ xlink:href="#linearGradient2378"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient2378"><stop
+ id="stop2386"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ style="stop-color:#27dc16;stop-opacity:1;"
+ offset="1"
+ id="stop2382" /></linearGradient><linearGradient
+ id="linearGradient3702"><stop
+ id="stop3704"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" /><stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3710" /><stop
+ id="stop3706"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient6732"><stop
+ id="stop6734"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop6736"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient4585"><stop
+ id="stop4587"
+ offset="0"
+ style="stop-color:#9e9e9e;stop-opacity:1;" /><stop
+ id="stop4589"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective
+ id="perspective2516"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86956"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86964"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86966"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /></defs><sodipodi:namedview
+ inkscape:window-height="933"
+ inkscape:window-width="1054"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.122171"
+ inkscape:cx="22.316511"
+ inkscape:cy="30.299841"
+ inkscape:window-x="36"
+ inkscape:window-y="91"
+ inkscape:current-layer="svg2" />
+<g
+ id="g35"
+ style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(1.1605241,0,0,1.3370602,-4.3871473,-0.7984997)">
+ <circle
+ style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:ry="3.829"
+ sodipodi:rx="3.829"
+ sodipodi:cy="5.5700002"
+ sodipodi:cx="26.49"
+ id="circle37"
+ r="3.829"
+ cy="5.5700002"
+ cx="26.49"
+ stroke-miterlimit="3.8637" />
+ <path
+ style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path39"
+ stroke-miterlimit="3.8637"
+ d="" />
+ </g><g
+ id="Background">
+</g>
+<g
+ id="Guides"
+ display="none"
+ style="display:none">
+</g>
+<g
+ id="g7"
+ style="fill:#2f2f2f;fill-opacity:1">
+ <path
+ style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd"
+ id="path9"
+ d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.1
32,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z"
+ clip-rule="evenodd" />
+ </g>
+<g
+ style="display:inline"
+ inkscape:label="Base"
+ id="layer1"
+ transform="translate(-52.466647,2.6102791)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/left-little-finger.png b/panels/user-accounts/data/icons/left-little-finger.png
new file mode 100644
index 0000000..978942e
Binary files /dev/null and b/panels/user-accounts/data/icons/left-little-finger.png differ
diff --git a/panels/user-accounts/data/icons/left-little-finger.svg b/panels/user-accounts/data/icons/left-little-finger.svg
new file mode 100644
index 0000000..0835854
--- /dev/null
+++ b/panels/user-accounts/data/icons/left-little-finger.svg
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 40.425 46.214"
+ enable-background="new 0 0 40.425 46.214"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="left-pinky-finger.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-ring-finger.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"><metadata
+ id="metadata44"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs42"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 23.107 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="40.424999 : 23.107 : 1"
+ inkscape:persp3d-origin="20.2125 : 15.404667 : 1"
+ id="perspective46" />
+
+
+
+
+
+
+<radialGradient
+ r="8.341651"
+ fy="9.3411446"
+ fx="38.658855"
+ cy="9.3411446"
+ cx="38.658855"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2479"
+ xlink:href="#linearGradient2378"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient2378"><stop
+ id="stop2386"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ style="stop-color:#27dc16;stop-opacity:1;"
+ offset="1"
+ id="stop2382" /></linearGradient><linearGradient
+ id="linearGradient3702"><stop
+ id="stop3704"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" /><stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3710" /><stop
+ id="stop3706"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient6732"><stop
+ id="stop6734"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop6736"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient4585"><stop
+ id="stop4587"
+ offset="0"
+ style="stop-color:#9e9e9e;stop-opacity:1;" /><stop
+ id="stop4589"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective
+ id="perspective2516"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86956"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86964"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86966"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /></defs><sodipodi:namedview
+ inkscape:window-height="933"
+ inkscape:window-width="1054"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.122171"
+ inkscape:cx="22.316511"
+ inkscape:cy="30.299841"
+ inkscape:window-x="122"
+ inkscape:window-y="443"
+ inkscape:current-layer="svg2" />
+<g
+ id="g35"
+ style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(1.1074589,0,0,1.2726911,-25.531655,5.5330271)">
+ <circle
+ style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:ry="3.829"
+ sodipodi:rx="3.829"
+ sodipodi:cy="5.5700002"
+ sodipodi:cx="26.49"
+ id="circle37"
+ r="3.829"
+ cy="5.5700002"
+ cx="26.49"
+ stroke-miterlimit="3.8637" />
+ <path
+ style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path39"
+ stroke-miterlimit="3.8637"
+ d="" />
+ </g><g
+ id="Background">
+</g>
+<g
+ id="Guides"
+ display="none"
+ style="display:none">
+</g>
+<g
+ id="g7"
+ style="fill:#2f2f2f;fill-opacity:1">
+ <path
+ style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd"
+ id="path9"
+ d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.1
32,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z"
+ clip-rule="evenodd" />
+ </g>
+<g
+ style="display:inline"
+ inkscape:label="Base"
+ id="layer1"
+ transform="translate(-52.466647,2.6102791)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/left-middle-finger.png b/panels/user-accounts/data/icons/left-middle-finger.png
new file mode 100644
index 0000000..406925e
Binary files /dev/null and b/panels/user-accounts/data/icons/left-middle-finger.png differ
diff --git a/panels/user-accounts/data/icons/left-middle-finger.svg b/panels/user-accounts/data/icons/left-middle-finger.svg
new file mode 100644
index 0000000..1082da2
--- /dev/null
+++ b/panels/user-accounts/data/icons/left-middle-finger.svg
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 40.425 46.214"
+ enable-background="new 0 0 40.425 46.214"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="left-middle-finger.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-index-finger.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"><metadata
+ id="metadata44"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs42"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 23.107 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="40.424999 : 23.107 : 1"
+ inkscape:persp3d-origin="20.2125 : 15.404667 : 1"
+ id="perspective46" />
+
+
+
+
+
+
+<radialGradient
+ r="8.341651"
+ fy="9.3411446"
+ fx="38.658855"
+ cy="9.3411446"
+ cx="38.658855"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2479"
+ xlink:href="#linearGradient2378"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient2378"><stop
+ id="stop2386"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ style="stop-color:#27dc16;stop-opacity:1;"
+ offset="1"
+ id="stop2382" /></linearGradient><linearGradient
+ id="linearGradient3702"><stop
+ id="stop3704"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" /><stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3710" /><stop
+ id="stop3706"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient6732"><stop
+ id="stop6734"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop6736"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient4585"><stop
+ id="stop4587"
+ offset="0"
+ style="stop-color:#9e9e9e;stop-opacity:1;" /><stop
+ id="stop4589"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective
+ id="perspective2516"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86956"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86964"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86966"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /></defs><sodipodi:namedview
+ inkscape:window-height="933"
+ inkscape:window-width="1054"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.122171"
+ inkscape:cx="22.316511"
+ inkscape:cy="30.299841"
+ inkscape:window-x="122"
+ inkscape:window-y="443"
+ inkscape:current-layer="svg2" />
+<g
+ id="g35"
+ style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(1.1824583,0,0,1.3363867,-12.845608,-2.0066594)">
+ <circle
+ style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:ry="3.829"
+ sodipodi:rx="3.829"
+ sodipodi:cy="5.5700002"
+ sodipodi:cx="26.49"
+ id="circle37"
+ r="3.829"
+ cy="5.5700002"
+ cx="26.49"
+ stroke-miterlimit="3.8637" />
+ <path
+ style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path39"
+ stroke-miterlimit="3.8637"
+ d="" />
+ </g><g
+ id="Background">
+</g>
+<g
+ id="Guides"
+ display="none"
+ style="display:none">
+</g>
+<g
+ id="g7"
+ style="fill:#2f2f2f;fill-opacity:1">
+ <path
+ style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd"
+ id="path9"
+ d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.1
32,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z"
+ clip-rule="evenodd" />
+ </g>
+<g
+ style="display:inline"
+ inkscape:label="Base"
+ id="layer1"
+ transform="translate(-52.466647,2.6102791)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/left-ring-finger.png b/panels/user-accounts/data/icons/left-ring-finger.png
new file mode 100644
index 0000000..169ff68
Binary files /dev/null and b/panels/user-accounts/data/icons/left-ring-finger.png differ
diff --git a/panels/user-accounts/data/icons/left-ring-finger.svg b/panels/user-accounts/data/icons/left-ring-finger.svg
new file mode 100644
index 0000000..50ace80
--- /dev/null
+++ b/panels/user-accounts/data/icons/left-ring-finger.svg
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 40.425 46.214"
+ enable-background="new 0 0 40.425 46.214"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="left-ring-finger.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-middle-finger.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"><metadata
+ id="metadata44"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs42"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 23.107 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="40.424999 : 23.107 : 1"
+ inkscape:persp3d-origin="20.2125 : 15.404667 : 1"
+ id="perspective46" />
+
+
+
+
+
+
+<radialGradient
+ r="8.341651"
+ fy="9.3411446"
+ fx="38.658855"
+ cy="9.3411446"
+ cx="38.658855"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2479"
+ xlink:href="#linearGradient2378"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient2378"><stop
+ id="stop2386"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ style="stop-color:#27dc16;stop-opacity:1;"
+ offset="1"
+ id="stop2382" /></linearGradient><linearGradient
+ id="linearGradient3702"><stop
+ id="stop3704"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" /><stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3710" /><stop
+ id="stop3706"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient6732"><stop
+ id="stop6734"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop6736"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient4585"><stop
+ id="stop4587"
+ offset="0"
+ style="stop-color:#9e9e9e;stop-opacity:1;" /><stop
+ id="stop4589"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective
+ id="perspective2516"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86956"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86964"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86966"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /></defs><sodipodi:namedview
+ inkscape:window-height="933"
+ inkscape:window-width="1054"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.122171"
+ inkscape:cx="22.316511"
+ inkscape:cy="30.299841"
+ inkscape:window-x="122"
+ inkscape:window-y="443"
+ inkscape:current-layer="svg2" />
+<g
+ id="g35"
+ style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(1.1824583,0,0,1.3363867,-20.636466,-0.7947482)">
+ <circle
+ style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:ry="3.829"
+ sodipodi:rx="3.829"
+ sodipodi:cy="5.5700002"
+ sodipodi:cx="26.49"
+ id="circle37"
+ r="3.829"
+ cy="5.5700002"
+ cx="26.49"
+ stroke-miterlimit="3.8637" />
+ <path
+ style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path39"
+ stroke-miterlimit="3.8637"
+ d="" />
+ </g><g
+ id="Background">
+</g>
+<g
+ id="Guides"
+ display="none"
+ style="display:none">
+</g>
+<g
+ id="g7"
+ style="fill:#2f2f2f;fill-opacity:1">
+ <path
+ style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd"
+ id="path9"
+ d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.1
32,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z"
+ clip-rule="evenodd" />
+ </g>
+<g
+ style="display:inline"
+ inkscape:label="Base"
+ id="layer1"
+ transform="translate(-52.466647,2.6102791)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/left-thumb.png b/panels/user-accounts/data/icons/left-thumb.png
new file mode 100644
index 0000000..eaf875d
Binary files /dev/null and b/panels/user-accounts/data/icons/left-thumb.png differ
diff --git a/panels/user-accounts/data/icons/left-thumb.svg b/panels/user-accounts/data/icons/left-thumb.svg
new file mode 100644
index 0000000..fd0f582
--- /dev/null
+++ b/panels/user-accounts/data/icons/left-thumb.svg
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 40.425 46.214"
+ enable-background="new 0 0 40.425 46.214"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="left-thumb.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-pinky-finger.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"><metadata
+ id="metadata44"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs42"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 23.107 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="40.424999 : 23.107 : 1"
+ inkscape:persp3d-origin="20.2125 : 15.404667 : 1"
+ id="perspective46" />
+
+
+
+
+
+
+<radialGradient
+ r="8.341651"
+ fy="9.3411446"
+ fx="38.658855"
+ cy="9.3411446"
+ cx="38.658855"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2479"
+ xlink:href="#linearGradient2378"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient2378"><stop
+ id="stop2386"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ style="stop-color:#27dc16;stop-opacity:1;"
+ offset="1"
+ id="stop2382" /></linearGradient><linearGradient
+ id="linearGradient3702"><stop
+ id="stop3704"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" /><stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3710" /><stop
+ id="stop3706"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient6732"><stop
+ id="stop6734"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop6736"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient4585"><stop
+ id="stop4587"
+ offset="0"
+ style="stop-color:#9e9e9e;stop-opacity:1;" /><stop
+ id="stop4589"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective
+ id="perspective2516"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86956"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86964"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86966"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /></defs><sodipodi:namedview
+ inkscape:window-height="933"
+ inkscape:window-width="1054"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.122171"
+ inkscape:cx="22.316511"
+ inkscape:cy="30.299841"
+ inkscape:window-x="116"
+ inkscape:window-y="498"
+ inkscape:current-layer="svg2" />
+<g
+ id="g35"
+ style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(1.1916623,0,0,1.4021101,4.5265732,14.334323)">
+ <circle
+ style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:ry="3.829"
+ sodipodi:rx="3.829"
+ sodipodi:cy="5.5700002"
+ sodipodi:cx="26.49"
+ id="circle37"
+ r="3.829"
+ cy="5.5700002"
+ cx="26.49"
+ stroke-miterlimit="3.8637" />
+ <path
+ style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path39"
+ stroke-miterlimit="3.8637"
+ d="" />
+ </g><g
+ id="Background">
+</g>
+<g
+ id="Guides"
+ display="none"
+ style="display:none">
+</g>
+<g
+ id="g7"
+ style="fill:#2f2f2f;fill-opacity:1">
+ <path
+ style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd"
+ id="path9"
+ d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.1
32,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z"
+ clip-rule="evenodd" />
+ </g>
+<g
+ style="display:inline"
+ inkscape:label="Base"
+ id="layer1"
+ transform="translate(-52.466647,2.6102791)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/print_error.png b/panels/user-accounts/data/icons/print_error.png
new file mode 100644
index 0000000..d67150b
Binary files /dev/null and b/panels/user-accounts/data/icons/print_error.png differ
diff --git a/panels/user-accounts/data/icons/print_error.svg b/panels/user-accounts/data/icons/print_error.svg
new file mode 100644
index 0000000..4ad6bee
--- /dev/null
+++ b/panels/user-accounts/data/icons/print_error.svg
@@ -0,0 +1,525 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 36.184 43.865"
+ enable-background="new 0 0 36.184 43.865"
+ xml:space="preserve"
+ id="svg2419"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="print_error.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/mlanglie/Desktop/print_error.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"><metadata
+ id="metadata2435"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs2433"><linearGradient
+ inkscape:collect="always"
+ id="linearGradient84104"><stop
+ style="stop-color:#fffffc;stop-opacity:1;"
+ offset="0"
+ id="stop84106" /><stop
+ style="stop-color:#fffffc;stop-opacity:0;"
+ offset="1"
+ id="stop84108" /></linearGradient><linearGradient
+ id="linearGradient84076"><stop
+ style="stop-color:#73d216;stop-opacity:1;"
+ offset="0"
+ id="stop84078" /><stop
+ id="stop84090"
+ offset="0.31459025"
+ style="stop-color:#73d216;stop-opacity:1;" /><stop
+ style="stop-color:#4e9a06;stop-opacity:1;"
+ offset="1"
+ id="stop84080" /></linearGradient><linearGradient
+ id="linearGradient3531"><stop
+ id="stop3533"
+ offset="0"
+ style="stop-color:#9b9b9b;stop-opacity:1;" /><stop
+ id="stop3535"
+ offset="1"
+ style="stop-color:#414141;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient3483"><stop
+ id="stop3485"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" /><stop
+ id="stop3487"
+ offset="1"
+ style="stop-color:#787878;stop-opacity:1" /></linearGradient><linearGradient
+ id="linearGradient3263"><stop
+ id="stop3265"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop3267"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient3247"><stop
+ style="stop-color:#52a714;stop-opacity:1;"
+ offset="0"
+ id="stop3249" /><stop
+ style="stop-color:#398800;stop-opacity:1;"
+ offset="1"
+ id="stop3251" /></linearGradient><linearGradient
+ id="linearGradient3233"><stop
+ id="stop3235"
+ offset="0"
+ style="stop-color:#398800;stop-opacity:1;" /><stop
+ id="stop3237"
+ offset="1"
+ style="stop-color:#84c706;stop-opacity:1;" /></linearGradient><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 21.932501 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="36.183998 : 21.932501 : 1"
+ inkscape:persp3d-origin="18.091999 : 14.621667 : 1"
+ id="perspective2437" />
+
+
+
+
+
+ <filter
+ inkscape:collect="always"
+ id="filter3471"><feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.057808254"
+ id="feGaussianBlur3473" /></filter><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3233"
+ id="linearGradient3517"
+ gradientUnits="userSpaceOnUse"
+ x1="25.144751"
+ y1="43.865002"
+ x2="25.144751"
+ y2="23.838018" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3247"
+ id="linearGradient3519"
+ gradientUnits="userSpaceOnUse"
+ x1="30.691881"
+ y1="23.365002"
+ x2="30.691881"
+ y2="44.365963" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3263"
+ id="linearGradient3521"
+ gradientUnits="userSpaceOnUse"
+ x1="26.455547"
+ y1="24.322035"
+ x2="26.455547"
+ y2="30.466549" /><linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="55.692348"
+ x2="18.072493"
+ y1="29.205048"
+ x1="21.55229"
+ id="linearGradient5138"
+ xlink:href="#linearGradient5132"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient5132"
+ inkscape:collect="always"><stop
+ id="stop5134"
+ offset="0"
+ style="stop-color:white;stop-opacity:1;" /><stop
+ id="stop5136"
+ offset="1"
+ style="stop-color:white;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient1913"><stop
+ id="stop1915"
+ offset="0"
+ style="stop-color:#73d216;stop-opacity:1" /><stop
+ id="stop1917"
+ offset="1"
+ style="stop-color:#8ae234;stop-opacity:1" /></linearGradient><inkscape:perspective
+ id="perspective84036"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient84076"
+ id="radialGradient84088"
+ cx="26.183998"
+ cy="40.111427"
+ fx="26.183998"
+ fy="40.111427"
+ r="10.5"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.9228377,7.3282173e-8,-7.9001009e-8,0.8924238,2.0204218,4.4167191)" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3263"
+ id="linearGradient84092"
+ gradientUnits="userSpaceOnUse"
+ x1="26.455547"
+ y1="24.322035"
+ x2="26.455547"
+ y2="30.466549" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient84104"
+ id="linearGradient84110"
+ x1="28.185518"
+ y1="22.649143"
+ x2="27.596079"
+ y2="42.648415"
+ gradientUnits="userSpaceOnUse" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient84076"
+ id="radialGradient84134"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0862379,-2.5925308e-8,1.5464794e-8,0.8186283,-2.2580516,7.302016)"
+ cx="26.183998"
+ cy="39.098457"
+ fx="26.183998"
+ fy="39.098457"
+ r="10.5" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3263"
+ id="linearGradient84136"
+ gradientUnits="userSpaceOnUse"
+ x1="26.455547"
+ y1="24.322035"
+ x2="26.455547"
+ y2="30.466549" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient84104"
+ id="linearGradient84138"
+ gradientUnits="userSpaceOnUse"
+ x1="21.515692"
+ y1="23.09075"
+ x2="34.488232"
+ y2="40.661182" /><filter
+ inkscape:collect="always"
+ id="filter84266"
+ x="-0.07103052"
+ width="1.142061"
+ y="-0.5276553"
+ height="2.0553105"><feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.45756194"
+ id="feGaussianBlur84268" /></filter><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3263"
+ id="linearGradient84277"
+ gradientUnits="userSpaceOnUse"
+ x1="26.455547"
+ y1="24.322035"
+ x2="26.455547"
+ y2="30.466549" /><linearGradient
+ id="linearGradient5171"><stop
+ style="stop-color:#fe3a00;stop-opacity:1"
+ offset="0"
+ id="stop5173" /><stop
+ style="stop-color:#c00;stop-opacity:1;"
+ offset="1"
+ id="stop5175" /></linearGradient><inkscape:perspective
+ id="perspective7871"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><inkscape:perspective
+ id="perspective7973"
+ inkscape:persp3d-origin="14 : 9.3333333 : 1"
+ inkscape:vp_z="28 : 14 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 14 : 1"
+ sodipodi:type="inkscape:persp3d" />
+
+<radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5171"
+ id="radialGradient8812"
+ cx="26.184002"
+ cy="39.797016"
+ fx="26.184002"
+ fy="39.797016"
+ r="10.65866"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.9444304,0,0,0.7220468,1.4550326,11.504981)" /><linearGradient
+ y2="40.661182"
+ x2="34.488232"
+ y1="23.09075"
+ x1="21.515692"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient84279"
+ xlink:href="#linearGradient84104"
+ inkscape:collect="always" /><linearGradient
+ y2="30.466549"
+ x2="26.455547"
+ y1="24.322035"
+ x1="26.455547"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient9095"
+ xlink:href="#linearGradient3263"
+ inkscape:collect="always" /><linearGradient
+ y2="40.661182"
+ x2="34.488232"
+ y1="23.09075"
+ x1="21.515692"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient9089"
+ xlink:href="#linearGradient84104"
+ inkscape:collect="always" /><linearGradient
+ y2="30.466549"
+ x2="26.455547"
+ y1="24.322035"
+ x1="26.455547"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient9087"
+ xlink:href="#linearGradient3263"
+ inkscape:collect="always" /><radialGradient
+ r="10.5"
+ fy="39.098457"
+ fx="26.183998"
+ cy="39.098457"
+ cx="26.183998"
+ gradientTransform="matrix(1.0862379,-2.5925308e-8,1.5464794e-8,0.8186283,-2.2580516,7.302016)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient9085"
+ xlink:href="#linearGradient84076"
+ inkscape:collect="always" /><linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="42.648415"
+ x2="27.596079"
+ y1="22.649143"
+ x1="28.185518"
+ id="linearGradient9083"
+ xlink:href="#linearGradient84104"
+ inkscape:collect="always" /><linearGradient
+ y2="30.466549"
+ x2="26.455547"
+ y1="24.322035"
+ x1="26.455547"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient9081"
+ xlink:href="#linearGradient3263"
+ inkscape:collect="always" /><radialGradient
+ gradientTransform="matrix(0.9228377,7.3282173e-8,-7.9001009e-8,0.8924238,2.0204218,4.4167191)"
+ gradientUnits="userSpaceOnUse"
+ r="10.5"
+ fy="40.111427"
+ fx="26.183998"
+ cy="40.111427"
+ cx="26.183998"
+ id="radialGradient9079"
+ xlink:href="#linearGradient84076"
+ inkscape:collect="always" /><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ id="perspective9077" /><linearGradient
+ id="linearGradient9071"><stop
+ style="stop-color:#73d216;stop-opacity:1"
+ offset="0"
+ id="stop9073" /><stop
+ style="stop-color:#8ae234;stop-opacity:1"
+ offset="1"
+ id="stop9075" /></linearGradient><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5132"
+ id="linearGradient9063"
+ x1="21.55229"
+ y1="29.205048"
+ x2="18.072493"
+ y2="55.692348"
+ gradientUnits="userSpaceOnUse" /><linearGradient
+ y2="30.466549"
+ x2="26.455547"
+ y1="24.322035"
+ x1="26.455547"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient9061"
+ xlink:href="#linearGradient3263"
+ inkscape:collect="always" /><linearGradient
+ y2="44.365963"
+ x2="30.691881"
+ y1="23.365002"
+ x1="30.691881"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient9059"
+ xlink:href="#linearGradient3247"
+ inkscape:collect="always" /><linearGradient
+ y2="23.838018"
+ x2="25.144751"
+ y1="43.865002"
+ x1="25.144751"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient9057"
+ xlink:href="#linearGradient3233"
+ inkscape:collect="always" />
+
+
+
+
+
+ <inkscape:perspective
+ id="perspective9051"
+ inkscape:persp3d-origin="18.091999 : 14.621667 : 1"
+ inkscape:vp_z="36.183998 : 21.932501 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 21.932501 : 1"
+ sodipodi:type="inkscape:persp3d" /><linearGradient
+ id="linearGradient9045"><stop
+ style="stop-color:#398800;stop-opacity:1;"
+ offset="0"
+ id="stop9047" /><stop
+ style="stop-color:#84c706;stop-opacity:1;"
+ offset="1"
+ id="stop9049" /></linearGradient><linearGradient
+ id="linearGradient9039"><stop
+ id="stop9041"
+ offset="0"
+ style="stop-color:#52a714;stop-opacity:1;" /><stop
+ id="stop9043"
+ offset="1"
+ style="stop-color:#398800;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient9033"><stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop9035" /><stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop9037" /></linearGradient><linearGradient
+ id="linearGradient9027"><stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop9029" /><stop
+ style="stop-color:#787878;stop-opacity:1"
+ offset="1"
+ id="stop9031" /></linearGradient><linearGradient
+ id="linearGradient9021"><stop
+ style="stop-color:#9b9b9b;stop-opacity:1;"
+ offset="0"
+ id="stop9023" /><stop
+ style="stop-color:#414141;stop-opacity:1;"
+ offset="1"
+ id="stop9025" /></linearGradient><linearGradient
+ id="linearGradient9013"><stop
+ id="stop9015"
+ offset="0"
+ style="stop-color:#73d216;stop-opacity:1;" /><stop
+ style="stop-color:#73d216;stop-opacity:1;"
+ offset="0.31459025"
+ id="stop9017" /><stop
+ id="stop9019"
+ offset="1"
+ style="stop-color:#4e9a06;stop-opacity:1;" /></linearGradient><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3263"
+ id="linearGradient9115"
+ gradientUnits="userSpaceOnUse"
+ x1="26.455547"
+ y1="24.322035"
+ x2="26.455547"
+ y2="30.466549" /></defs><sodipodi:namedview
+ inkscape:window-height="733"
+ inkscape:window-width="1263"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="10.958333"
+ inkscape:cx="16.307224"
+ inkscape:cy="24"
+ inkscape:window-x="6"
+ inkscape:window-y="140"
+ inkscape:current-layer="svg2419" />
+<path
+ id="path2424"
+ d="M 11.485207,8.6743869 C 11.872117,8.5219533 18.066562,7.5772471 17.547442,14.819684 C 16.836811,24.751336 10.199071,21.863582 8.4570051,28.091683 C 9.1530536,27.855507 9.3319265,27.184615 9.77036,26.633222 C 11.09052,24.971517 11.912946,24.144427 13.199081,23.591154 C 17.777838,21.620819 20.261644,13.237019 16.405184,9.3659791 C 14.958646,7.9131637 12.270692,8.0514823 11.485207,8.6743869 z M -0.41567362,30.622819 C -0.62079412,30.073309 -0.81036052,29.508743 -0.98534482,28.932886 C 0.28134733,30.510848 2.408639,29.511665 3.7800623,29.851546 C 7.7792565,30.842672 10.201988,29.700696 12.4972,27.002072 C 14.538683,24.600785 15.730521,26.026314 17.692291,22.415916 C 18.187107,21.506024 19.527682,20.515211 20.049718,16.478567 C 20.335526,14.26641 21.73087,14.007651 21.560747,12.407106 C 21.300215,9.95501 21.209476,9.6012157 20.182901,7.9460967 C 18.706228,5.5636294 16.443158,4.9249489 14.396731,4.9187631 C 12.674334,4.9140584 11.84411,5.6088349 11.260829,5.8365431 C 15.3902
32,4.8356399 18.977454,6.2618089 19.977781,8.6743869 C 20.486207,9.9004346 20.744795,10.273048 20.835204,11.578136 C 20.978108,13.644446 20.307334,14.416019 20.263588,13.790293 C 20.079855,11.11331 19.152438,7.8049552 16.050354,6.738867 C 14.094417,6.0670338 11.302445,6.5779657 9.6303729,7.8454158 C 7.5908326,9.7988133 6.4952361,12.616937 6.4952361,14.474358 C 6.4952361,18.037708 6.4689884,19.168722 5.854599,22.494014 C 5.596011,23.89696 4.6627611,25.357303 5.2022963,27.278709 C 5.9975029,26.985134 6.7110502,25.878585 6.9949136,25.111717 C 8.9868188,19.727642 10.292396,20.99227 12.343602,18.829042 C 12.831614,18.314346 13.669595,16.922691 13.771669,16.409877 C 14.018591,15.167833 14.629092,12.200099 12.557472,12.269729 C 10.832904,13.36875 11.557145,15.649595 10.237957,17.872102 C 12.271664,16.963152 12.138481,13.127869 12.885082,13.037538 C 13.533495,12.95944 13.493638,14.504468 13.351707,15.328734 C 13.046456,17.100529 12.422345,18.101693 11.019554,19.120734 C 9.9006261,19
.933709 8.8604412,20.065441 7.8484484,21.773252 C 7.6579099,22.073413 6.3027534,25.346012 6.134574,25.654641 C 5.2858998,27.216607 5.3879741,25.564311 6.3212239,22.946607 C 6.3212239,22.946607 7.2311425,20.110606 7.1115699,17.014904 C 7.0571303,15.581848 7.1368454,9.9201951 10.843597,7.9150456 C 12.438871,7.052201 14.9149,6.5102179 16.620025,7.776727 C 21.95802,11.740919 19.47227,21.463682 15.405828,24.214058 C 13.770697,25.320606 12.413596,25.389296 11.019554,27.516768 C 9.932706,29.17565 6.8500655,30.289726 4.7074794,29.321496 C 5.5649027,28.845379 6.8957558,28.803036 7.2787772,27.73883 C 10.085332,19.931827 15.530262,23.306048 16.183536,14.966471 C 16.414904,12.009088 14.877959,9.6953099 12.69746,9.6953099 C 10.466409,9.6953099 8.9664042,12.675276 8.6281009,15.440706 C 8.4657544,16.774023 8.3782621,18.528881 8.2713273,19.450064 C 10.056167,18.413145 8.4851971,11.162239 12.484562,10.747283 C 15.963834,10.386902 15.66636,14.964589 15.26973,16.96127 C 14.967396,18.483716 14.
270374,19.795389 11.270365,21.516375 C 9.690645,22.422502 8.643655,23.573275 7.8426157,25.457043 C 7.3730743,26.56171 6.4126046,28.452065 4.547077,28.632726 C 4.3332073,28.653426 3.9268547,26.573943 3.8004772,25.47304 C 3.6138271,23.848031 4.453752,22.765006 4.7327549,21.015793 C 5.0438382,19.072746 5.2615964,17.525836 5.2781228,14.197721 C 4.4936096,9.0893427 5.2917327,7.5791289 7.4226532,5.1562011 C 9.9142358,2.3220811 12.045219,2.0705493 13.508882,2.0570557 C 15.652565,2.0393053 16.899028,3.5509526 16.263252,3.4276891 C 14.62326,3.1105913 11.630055,2.6909308 9.0577845,5.2945196 C 7.6229131,6.7454536 6.0402768,8.6122845 6.2084562,10.886543 C 6.8802017,7.6195895 10.334508,3.7516736 13.747676,3.7516736 C 17.577206,3.7516736 19.976808,5.1712562 21.120039,7.0155042 C 22.280769,8.8889209 22.761976,10.297513 22.761976,12.822062 C 22.761976,14.686071 21.730541,15.451057 21.262943,16.962211 C 21.083098,17.542773 20.463848,19.045459 20.191651,20.211286 C 19.097998,24.896241 18.3095
96,27.152622 14.271346,30.220096 C 12.511782,31.556235 11.111907,31.646565 10.179629,31.285243 C 10.187406,31.13281 12.366934,30.934272 13.724035,28.937591 C 15.002392,27.055705 16.650162,27.007717 17.396761,24.840726 C 16.622941,25.778846 15.419438,26.673683 14.41425,27.115925 C 14.130387,27.241071 13.618072,27.886557 13.421701,28.116148 C 11.368551,30.531548 9.713004,31.103642 7.3458545,31.373692 C 7.7220709,31.550589 8.110925,31.687026 8.5231103,31.739719 C 11.53187,32.124565 13.387675,32.163143 15.774267,30.055431 C 19.142717,27.08111 19.696834,25.201106 20.047775,22.967308 C 20.307334,21.313131 21.669648,17.436727 22.261326,15.868175 C 23.094574,13.65922 22.570465,18.105457 22.332292,18.688842 C 20.93825,22.115755 21.500144,26.735784 17.955739,30.380997 C 15.523456,32.882964 12.32416,33.811675 7.1358733,32.637379 C 5.7525248,32.324044 6.253216,31.857336 4.671552,31.373692 C 4.1971499,31.159157 1.6540024,31.013311 1.3623619,31.011429 C 0.72269676,31.007665 0.11997297,30.
875933 -0.41567362,30.622819 z M 4.2680743,5.1477326 C 2.8681995,6.7162844 3.1442858,7.9310416 2.2158966,9.2445983 C 0.39119867,11.826545 1.4517984,15.393659 1.0658607,17.99913 C 0.56618308,21.378997 0.52283784,20.110089 0.0065640818,17.990301 C -0.75463862,14.864849 -0.20180392,13.731954 -0.076398418,12.407106 C 0.6186783,9.1298026 1.2262629,7.7560263 2.8691715,5.6887748 C 4.2253003,3.9828454 6.149156,3.0371977 4.2680743,5.1477326 z M 24.036445,36.450079 C 23.488161,37.083333 22.896131,37.673305 22.262298,38.211523 L 21.475841,38.506979 C 18.241547,39.711387 16.58114,39.821477 11.857534,39.082837 C 9.932706,38.781735 7.3050248,38.411944 5.4978253,39.277612 C 6.5839482,39.510065 10.395401,38.361053 11.576587,39.986142 C 11.459931,40.156453 9.2113823,39.88358 7.0960157,40.347465 C 6.5837004,40.058595 6.0888836,39.731146 5.6115651,39.367002 C 5.1527173,39.009443 4.8173306,38.720573 4.4926375,38.413826 C 6.9570004,37.030641 10.904842,37.507699 13.34879,37.820091 C 17.581468,38.
362075 19.260345,38.181414 21.964825,37.188718 C 22.696843,36.919609 23.388031,36.670259 24.036445,36.450079 z M 26.094456,7.8981086 C 27.151167,9.9813566 28.257457,11.575313 28.257457,14.734999 C 28.257457,18.212845 25.759069,19.908303 25.360494,22.223022 C 24.987194,24.390014 24.927894,26.193802 24.599312,26.193802 C 24.132687,26.193802 23.98395,24.125609 24.054915,22.674675 C 24.126854,21.205864 24.314124,20.404861 24.655344,19.310545 C 25.039337,18.081673 25.62266,15.331577 25.504059,13.251152 C 25.348517,10.505481 25.590559,11.089486 26.303134,12.745546 C 26.769759,13.828571 26.261622,15.950138 26.303134,17.484815 C 27.329709,15.648996 27.135261,12.882863 26.349775,10.877714 C 26.087298,10.20494 25.726989,8.8446969 26.094456,7.8981086 z M 24.622001,9.2587722 C 24.485902,8.654687 23.723397,6.4935811 22.726959,5.2026076 C 21.232786,3.2689699 22.578201,3.6726143 23.649494,4.846911 C 24.696483,5.9948613 25.472289,7.3598893 25.501453,8.680032 C 25.560753,11.39277 25.122651,1
1.470929 24.622001,9.2587722 z M 28.324534,16.874703 C 28.602565,16.515264 28.874762,18.765998 28.371196,19.556391 C 27.026733,21.665043 27.509885,25.128655 26.639823,27.894085 C 25.254531,32.293934 20.848813,35.618286 15.998831,35.528896 C 19.543236,34.354599 22.714341,33.813557 24.183237,26.840229 C 24.312532,26.225793 25.174816,26.557005 24.397107,29.459814 C 25.254531,28.223415 25.999186,25.124891 26.111954,22.623864 C 26.186809,20.930167 27.729588,18.502534 28.324534,16.874703 z M 0.35036898,7.0776065 C 3.1160939,2.0115701 8.6912897,0.54275818 13.214636,0.54275818 C 16.041605,0.54275818 18.507912,1.0273437 20.818678,2.3550142 C 21.174479,2.6147144 21.713042,2.9355759 22.002738,3.2253863 C 22.938905,4.1653883 21.369626,3.7445623 19.405193,2.8753556 C 15.80217,1.2811182 13.224356,1.5241817 11.057467,1.8713897 C 9.4544161,2.1282671 8.4327017,3.4107521 7.629718,4.1889118 C 4.6705382,7.05126 4.2097461,9.6106249 4.5655476,12.822062 C 5.0068971,16.806955 4.2680743,21.015793 3.
3367686,23.309812 C 2.8604223,24.48505 2.9236112,26.011258 3.1365088,26.978548 C 3.5214743,28.723056 5.0173803,29.448829 3.1481744,29.208582 C -0.12595652,28.787761 -0.12403302,24.289333 0.99392257,21.109887 C 2.1361815,17.860811 1.50895,15.966631 1.7492717,13.57764 C 2.8163869,2.8893489 3.1267874,15.841548 3.0655428,16.408937 C 2.7077971,19.726701 2.2340211,20.608302 1.7385783,22.084704 C 0.6727661,25.260787 1.9314335,27.1104 2.2661583,27.366598 C 0.91096007,23.04949 3.2794126,21.655634 3.4660626,16.648877 C 3.5117529,15.433179 3.5311956,14.688893 3.3455178,13.046948 C 2.7748744,7.9799706 4.0814242,5.0574021 6.9725545,2.7097496 C 5.2188225,2.7266866 3.8724152,3.9950776 2.6825216,5.1477326 C 1.376944,6.4114188 0.16469114,9.2935264 -0.21249732,10.402898 C -0.77633582,12.06178 -1.2711526,10.046281 0.35036898,7.0776065 z M -1.1097781,28.513226 C -2.0128918,25.368594 -2.4649347,21.861701 -2.3900803,18.216487 C -2.3609163,16.765553 -2.2306501,15.171597 -1.7834679,13.305707 C -0.9
1243472,9.6699043 -0.68197602,11.586122 -0.74034592,12.032491 C -1.892347,20.8421 1.6144968,19.138912 -0.52455272,25.744091 C -1.2711526,25.202108 -0.48304042,21.510409 -1.656139,19.312946 C -1.3508469,21.734954 -2.0167804,25.74309 0.62937185,28.271403 C 1.3856931,28.994988 2.8681995,29.389244 0.72269676,29.208582 C -0.014181718,29.14648 -0.61787772,28.901835 -1.1097781,28.513226 z M 0.48744008,32.752173 C 0.31731635,32.398379 0.15496979,32.036116 -0.001544018,31.666325 C 1.0075324,31.547766 3.0567937,31.442381 4.827052,32.187607 C 5.8555711,32.620441 3.7965886,32.40967 2.0000826,32.51976 C 1.3565291,32.558339 0.82574314,32.712653 0.48744008,32.752173 z M 1.3409749,34.366831 C 1.1446035,34.029973 0.95600926,33.683706 0.77422,33.328971 C 5.2207668,32.537638 7.1728144,33.319562 10.628756,33.606549 C 14.190659,33.902946 18.050036,32.097277 19.263261,30.020616 C 19.886399,28.953587 20.061384,28.694827 20.186789,28.414427 C 20.295669,28.171663 20.367606,27.912904 20.662164,27.098
048 C 21.315439,25.292378 21.284331,27.753885 21.120039,28.360793 C 20.008889,32.4586 19.076611,31.735955 17.211084,33.542565 C 21.502088,32.007888 22.154392,28.937591 21.595413,24.661006 C 21.461259,23.63726 22.257437,20.869947 22.622961,19.880074 C 22.622961,19.880074 23.390948,17.397867 23.395808,12.997078 C 23.398724,10.126261 22.460614,5.7019479 18.621652,4.0505932 C 17.335517,3.4973188 15.549704,1.9767551 15.549704,1.9767551 C 16.345283,2.4005401 18.263906,2.6674072 20.120685,3.773956 C 20.191651,3.8162985 24.211429,6.0576243 24.612921,11.923462 C 24.802488,14.69548 24.823213,14.824989 24.636563,16.630658 C 24.636563,16.630658 24.522513,18.48748 23.87021,20.022157 C 23.555238,20.876533 23.217907,23.396378 23.404558,24.661006 C 23.836185,27.588279 22.063983,34.896582 14.845879,34.896582 C 11.657275,34.896582 9.7168927,34.487272 8.1906401,34.251096 C 6.8082637,34.036561 4.0143469,33.91612 1.3409749,34.366831 z M 3.9297711,37.852084 C 3.5603597,37.465356 3.2055303,37.0551
05 2.865283,36.622271 C 3.9113006,36.029477 4.8299684,36.020068 6.6935517,36.070879 C 8.8283608,36.070879 12.045156,36.916786 11.057467,37.195306 C 11.009832,37.207538 6.4806541,36.578988 3.9297711,37.852084 z M 14.733111,41.653493 C 14.310232,41.716536 13.88152,41.763583 13.44406,41.791811 C 11.361746,41.927308 9.4310844,41.515174 7.6851297,40.656094 C 8.5367202,40.538475 10.183517,40.482019 11.744767,40.7022 C 12.669268,40.832991 13.723062,41.288407 14.733111,41.653493 z M 20.761322,39.340655 C 19.332282,40.284421 17.738953,40.992951 16.005636,41.406965 C 14.734083,41.029648 11.984449,40.585503 12.005589,39.75495 C 12.021421,39.132891 15.493321,40.331468 17.489114,40.076472 C 18.855937,39.902398 19.892232,39.635171 20.761322,39.340655 z M 26.76134,32.003183 C 27.309624,31.472491 26.651489,34.623708 23.573708,35.937265 C 21.48945,36.675905 18.394173,38.324437 13.07076,36.917727 C 12.417485,36.744594 10.347808,36.196965 9.8870164,36.103812 C 6.2998369,35.382109 4.4372258,35.
597584 2.4064351,36.01254 C 2.1702062,35.685092 1.9417545,35.345412 1.7210798,34.995381 C 2.3753268,34.730976 4.0192075,34.628413 5.200352,34.626531 C 8.9615437,34.619945 14.003037,35.980548 15.996887,36.070879 C 20.670913,36.282591 23.680644,33.000582 24.053944,33.000582 C 24.986221,33.000582 23.179994,34.632178 22.678372,35.082889 C 22.251605,35.467734 24.361138,34.516441 26.76134,32.003183 z M 28.531599,25.021387 C 28.408138,27.148859 28.247735,30.101537 26.467755,31.577877 C 23.628148,33.933056 24.799572,32.277938 26.039044,30.634111 C 28.194268,27.774586 27.297959,21.332891 28.708527,20.0852 C 28.725053,21.437335 28.621035,23.498941 28.531599,25.021387 z"
+ style="fill:#a1a1a1;fill-opacity:1"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ sodipodi:nodetypes="cscssscccssssssscsssscsscsssccssscscssssscsssscssssssscsssscsssssscscsscssssssscsccssscsccccscccscssccssssssscsccsssccsscscsccscsssssssssssscsscssccsssccsscccsscccssssscscsscssccsssccccsccscscccsscccssccssssccsscc" /><path
+ sodipodi:type="arc"
+ style="opacity:0.24299999;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter84266)"
+ id="path84140"
+ sodipodi:cx="27.093658"
+ sodipodi:cy="38.810692"
+ sodipodi:rx="7.7301183"
+ sodipodi:ry="1.0405928"
+ d="M 34.823777,38.810692 A 7.7301183,1.0405928 0 1 1 19.36354,38.810692 A 7.7301183,1.0405928 0 1 1 34.823777,38.810692 z"
+ transform="matrix(1.1911672,0,0,2.1266149,-5.0625748,-41.775272)" /><g
+ id="Background">
+</g>
+<g
+ id="Guides">
+</g>
+
+<circle
+ clip-rule="evenodd"
+ cx="26.184"
+ cy="33.865002"
+ r="10"
+ id="circle2428"
+ sodipodi:cx="26.184"
+ sodipodi:cy="33.865002"
+ sodipodi:rx="10"
+ sodipodi:ry="10"
+ style="fill:url(#radialGradient8812);fill-opacity:1;fill-rule:evenodd;stroke:#a40000;stroke-width:1.31732059000000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(1.1263785,0,0,1.1511056,-2.2713556,-9.1997707)" /><path
+ sodipodi:type="arc"
+ style="opacity:0.5;fill:url(#linearGradient84277);fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3253"
+ sodipodi:cx="26.455547"
+ sodipodi:cy="27.394291"
+ sodipodi:rx="6.1445141"
+ sodipodi:ry="3.072257"
+ d="M 32.600061,27.394291 A 6.1445141,3.072257 0 1 1 20.311033,27.394291 A 6.1445141,3.072257 0 1 1 32.600061,27.394291 z"
+ transform="matrix(1.1246822,0,0,1.4387643,-2.5144268,-14.969086)" /><path
+ sodipodi:type="arc"
+ style="opacity:0.48;fill:none;fill-opacity:1;stroke:#e64837;stroke-width:1.21842730000000010;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path84102"
+ sodipodi:cx="28.185518"
+ sodipodi:cy="31.917336"
+ sodipodi:rx="10.027505"
+ sodipodi:ry="10.240856"
+ d="M 38.213023,31.917336 A 10.027505,10.240856 0 1 1 18.158013,31.917336 A 10.027505,10.240856 0 1 1 38.213023,31.917336 z"
+ transform="matrix(0.9962424,0,0,0.9957004,-0.8393988,-2.0505383)" /><g
+ id="g7978"
+ transform="translate(103.26268,8.6771365)">
+</g><g
+ style="display:none"
+ id="g7980"
+ display="none"
+ transform="translate(103.26268,8.6771365)">
+</g><path
+ id="path7993"
+ style="opacity:0.55;fill:#a40000;fill-opacity:1;fill-rule:evenodd;stroke:#a40000;stroke-width:1.14231765000000007;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 27.143847,37.652725 C 26.708227,37.652725 26.326837,37.518032 25.998788,37.249507 C 25.677852,36.974118 25.516939,36.592349 25.516939,36.10334 C 25.516939,35.676959 25.66985,35.315778 25.977452,35.018942 C 26.291276,34.7161 26.672666,34.565107 27.122511,34.56425 C 27.571466,34.56425 27.953745,34.7161 28.26757,35.018942 C 28.588505,35.31492 28.749418,35.676959 28.749418,36.10334 C 28.749418,36.585484 28.588505,36.963823 28.26757,37.239212 C 27.946633,37.515458 27.571466,37.652725 27.143847,37.652725 z M 25.956115,30.858079 L 25.613842,25.912229 C 25.549833,24.94794 25.517828,24.256464 25.517828,23.836088 C 25.517828,23.26472 25.670739,22.82118 25.978341,22.504613 C 26.292165,22.18118 26.702004,22.019893 27.208746,22.019035 C 27.82217,22.019035 28.232008,22.225791 28.439151,22.638445 C 28.645403,23.045095 28.749418,23.633622 28.749418,24.404025 C 28.749418,24.858717 28.724526,25.320272 28.674741,25.787834 L 28.214228,30.878669 C 28.164443,31.484354 28.056871,31.94934 2
7.893292,32.272773 C 27.728822,32.596205 27.457671,32.758349 27.079838,32.758349 C 26.694892,32.758349 26.427296,32.603067 26.277052,32.293362 C 26.126807,31.976794 26.020125,31.498939 25.956115,30.858079 z" /><path
+ id="path8814"
+ style="fill:#ffffff;fill-rule:evenodd"
+ d="M 27.143847,36.992527 C 26.708227,36.992527 26.326837,36.857834 25.998788,36.589309 C 25.677852,36.31392 25.516939,35.932151 25.516939,35.443142 C 25.516939,35.016761 25.66985,34.65558 25.977452,34.358744 C 26.291276,34.055902 26.672666,33.904909 27.122511,33.904052 C 27.571466,33.904052 27.953745,34.055902 28.26757,34.358744 C 28.588505,34.654722 28.749418,35.016761 28.749418,35.443142 C 28.749418,35.925286 28.588505,36.303625 28.26757,36.579014 C 27.946633,36.85526 27.571466,36.992527 27.143847,36.992527 z M 25.956115,30.197881 L 25.613842,25.252031 C 25.549833,24.287742 25.517828,23.596266 25.517828,23.17589 C 25.517828,22.604522 25.670739,22.160982 25.978341,21.844415 C 26.292165,21.520982 26.702004,21.359695 27.208746,21.358837 C 27.82217,21.358837 28.232008,21.565593 28.439151,21.978247 C 28.645403,22.384897 28.749418,22.973424 28.749418,23.743827 C 28.749418,24.198519 28.724526,24.660074 28.674741,25.127636 L 28.214228,30.218471 C 28.164443,30.824156 28.056871,3
1.289142 27.893292,31.612575 C 27.728822,31.936007 27.457671,32.098151 27.079838,32.098151 C 26.694892,32.098151 26.427296,31.942869 26.277052,31.633164 C 26.126807,31.316596 26.020125,30.838741 25.956115,30.197881 z" /><rect
+ style="fill:#666666;fill-opacity:0.75;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="rect9126"
+ width="1.8060035"
+ height="42.650608"
+ x="-26.038683"
+ y="-24.284592"
+ transform="matrix(-0.6420845,-0.7666339,0.7632254,-0.6461324,0,0)"
+ ry="0"
+ rx="0.018515259" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/print_ok.png b/panels/user-accounts/data/icons/print_ok.png
new file mode 100644
index 0000000..4dd615e
Binary files /dev/null and b/panels/user-accounts/data/icons/print_ok.png differ
diff --git a/panels/user-accounts/data/icons/print_ok.svg b/panels/user-accounts/data/icons/print_ok.svg
new file mode 100644
index 0000000..ba821ef
--- /dev/null
+++ b/panels/user-accounts/data/icons/print_ok.svg
@@ -0,0 +1,310 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 36.184 43.865"
+ enable-background="new 0 0 36.184 43.865"
+ xml:space="preserve"
+ id="svg2419"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="print_ok.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/print_ok.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"><metadata
+ id="metadata2435"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs2433"><linearGradient
+ inkscape:collect="always"
+ id="linearGradient84104"><stop
+ style="stop-color:#fffffc;stop-opacity:1;"
+ offset="0"
+ id="stop84106" /><stop
+ style="stop-color:#fffffc;stop-opacity:0;"
+ offset="1"
+ id="stop84108" /></linearGradient><linearGradient
+ id="linearGradient84076"><stop
+ style="stop-color:#73d216;stop-opacity:1;"
+ offset="0"
+ id="stop84078" /><stop
+ id="stop84090"
+ offset="0.31459025"
+ style="stop-color:#73d216;stop-opacity:1;" /><stop
+ style="stop-color:#4e9a06;stop-opacity:1;"
+ offset="1"
+ id="stop84080" /></linearGradient><linearGradient
+ id="linearGradient3531"><stop
+ id="stop3533"
+ offset="0"
+ style="stop-color:#9b9b9b;stop-opacity:1;" /><stop
+ id="stop3535"
+ offset="1"
+ style="stop-color:#414141;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient3483"><stop
+ id="stop3485"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" /><stop
+ id="stop3487"
+ offset="1"
+ style="stop-color:#787878;stop-opacity:1" /></linearGradient><linearGradient
+ id="linearGradient3263"><stop
+ id="stop3265"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop3267"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient3247"><stop
+ style="stop-color:#52a714;stop-opacity:1;"
+ offset="0"
+ id="stop3249" /><stop
+ style="stop-color:#398800;stop-opacity:1;"
+ offset="1"
+ id="stop3251" /></linearGradient><linearGradient
+ id="linearGradient3233"><stop
+ id="stop3235"
+ offset="0"
+ style="stop-color:#398800;stop-opacity:1;" /><stop
+ id="stop3237"
+ offset="1"
+ style="stop-color:#84c706;stop-opacity:1;" /></linearGradient><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 21.932501 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="36.183998 : 21.932501 : 1"
+ inkscape:persp3d-origin="18.091999 : 14.621667 : 1"
+ id="perspective2437" />
+
+
+
+
+
+ <filter
+ inkscape:collect="always"
+ id="filter3471"><feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.057808254"
+ id="feGaussianBlur3473" /></filter><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3233"
+ id="linearGradient3517"
+ gradientUnits="userSpaceOnUse"
+ x1="25.144751"
+ y1="43.865002"
+ x2="25.144751"
+ y2="23.838018" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3247"
+ id="linearGradient3519"
+ gradientUnits="userSpaceOnUse"
+ x1="30.691881"
+ y1="23.365002"
+ x2="30.691881"
+ y2="44.365963" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3263"
+ id="linearGradient3521"
+ gradientUnits="userSpaceOnUse"
+ x1="26.455547"
+ y1="24.322035"
+ x2="26.455547"
+ y2="30.466549" /><linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="55.692348"
+ x2="18.072493"
+ y1="29.205048"
+ x1="21.55229"
+ id="linearGradient5138"
+ xlink:href="#linearGradient5132"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient5132"
+ inkscape:collect="always"><stop
+ id="stop5134"
+ offset="0"
+ style="stop-color:white;stop-opacity:1;" /><stop
+ id="stop5136"
+ offset="1"
+ style="stop-color:white;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient1913"><stop
+ id="stop1915"
+ offset="0"
+ style="stop-color:#73d216;stop-opacity:1" /><stop
+ id="stop1917"
+ offset="1"
+ style="stop-color:#8ae234;stop-opacity:1" /></linearGradient><inkscape:perspective
+ id="perspective84036"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient84076"
+ id="radialGradient84088"
+ cx="26.183998"
+ cy="40.111427"
+ fx="26.183998"
+ fy="40.111427"
+ r="10.5"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.9228377,7.3282173e-8,-7.9001009e-8,0.8924238,2.0204218,4.4167191)" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3263"
+ id="linearGradient84092"
+ gradientUnits="userSpaceOnUse"
+ x1="26.455547"
+ y1="24.322035"
+ x2="26.455547"
+ y2="30.466549" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient84104"
+ id="linearGradient84110"
+ x1="28.185518"
+ y1="22.649143"
+ x2="27.596079"
+ y2="42.648415"
+ gradientUnits="userSpaceOnUse" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient84076"
+ id="radialGradient84134"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0862379,-2.5925308e-8,1.5464794e-8,0.8186283,-2.2580516,7.302016)"
+ cx="26.183998"
+ cy="39.098457"
+ fx="26.183998"
+ fy="39.098457"
+ r="10.5" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3263"
+ id="linearGradient84136"
+ gradientUnits="userSpaceOnUse"
+ x1="26.455547"
+ y1="24.322035"
+ x2="26.455547"
+ y2="30.466549" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient84104"
+ id="linearGradient84138"
+ gradientUnits="userSpaceOnUse"
+ x1="21.515692"
+ y1="23.09075"
+ x2="34.488232"
+ y2="40.661182" /><filter
+ inkscape:collect="always"
+ id="filter84266"
+ x="-0.07103052"
+ width="1.142061"
+ y="-0.5276553"
+ height="2.0553105"><feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.45756194"
+ id="feGaussianBlur84268" /></filter><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3263"
+ id="linearGradient84277"
+ gradientUnits="userSpaceOnUse"
+ x1="26.455547"
+ y1="24.322035"
+ x2="26.455547"
+ y2="30.466549" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient84104"
+ id="linearGradient84279"
+ gradientUnits="userSpaceOnUse"
+ x1="21.515692"
+ y1="23.09075"
+ x2="34.488232"
+ y2="40.661182" /></defs><sodipodi:namedview
+ inkscape:window-height="713"
+ inkscape:window-width="1222"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="4.399364"
+ inkscape:cx="39.372624"
+ inkscape:cy="7.0437262"
+ inkscape:window-x="15"
+ inkscape:window-y="165"
+ inkscape:current-layer="svg2419" />
+<path
+ sodipodi:type="arc"
+ style="opacity:0.24299999;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter84266)"
+ id="path84140"
+ sodipodi:cx="27.093658"
+ sodipodi:cy="38.810692"
+ sodipodi:rx="7.7301183"
+ sodipodi:ry="1.0405928"
+ d="M 34.823777,38.810692 A 7.7301183,1.0405928 0 1 1 19.36354,38.810692 A 7.7301183,1.0405928 0 1 1 34.823777,38.810692 z"
+ transform="matrix(1.1911672,0,0,2.1266149,-5.0625748,-41.775272)" /><g
+ id="Background">
+</g>
+<g
+ id="Guides">
+</g>
+<path
+ id="path2424"
+ d="M 11.4715,8.6587828 C 11.85841,8.5063492 18.052855,7.561643 17.533735,14.80408 C 16.823104,24.735732 10.185364,21.847978 8.4432981,28.076079 C 9.1393466,27.839903 9.3182195,27.169011 9.756653,26.617618 C 11.076813,24.955913 11.899239,24.128823 13.185374,23.57555 C 17.764131,21.605215 20.247937,13.221415 16.391477,9.350375 C 14.944939,7.8975596 12.256985,8.0358782 11.4715,8.6587828 z M -0.42938057,30.607215 C -0.63450107,30.057705 -0.82406747,29.493139 -0.99905177,28.917282 C 0.26764038,30.495244 2.394932,29.496061 3.7663553,29.835942 C 7.7655495,30.827068 10.188281,29.685092 12.483493,26.986468 C 14.524976,24.585181 15.716814,26.01071 17.678584,22.400312 C 18.1734,21.49042 19.513975,20.499607 20.036011,16.462963 C 20.321819,14.250806 21.717163,13.992047 21.54704,12.391502 C 21.286508,9.9394059 21.195769,9.5856116 20.169194,7.9304926 C 18.692521,5.5480253 16.429451,4.9093448 14.383024,4.903159 C 12.660627,4.8984543 11.830403,5.5932308 11.247122,5.820939 C 15.376525,4.82
00358 18.963747,6.2462048 19.964074,8.6587828 C 20.4725,9.8848305 20.731088,10.257444 20.821497,11.562532 C 20.964401,13.628842 20.293627,14.400415 20.249881,13.774689 C 20.066148,11.097706 19.138731,7.7893511 16.036647,6.7232629 C 14.08071,6.0514297 11.288738,6.5623616 9.6166659,7.8298117 C 7.5771256,9.7832092 6.4815291,12.601333 6.4815291,14.458754 C 6.4815291,18.022104 6.4552814,19.153118 5.840892,22.47841 C 5.582304,23.881356 4.6490541,25.341699 5.1885893,27.263105 C 5.9837959,26.96953 6.6973432,25.862981 6.9812066,25.096113 C 8.9731118,19.712038 10.278689,20.976666 12.329895,18.813438 C 12.817907,18.298742 13.655888,16.907087 13.757962,16.394273 C 14.004884,15.152229 14.615385,12.184495 12.543765,12.254125 C 10.819197,13.353146 11.543438,15.633991 10.22425,17.856498 C 12.257957,16.947548 12.124774,13.112265 12.871375,13.021934 C 13.519788,12.943836 13.479931,14.488864 13.338,15.31313 C 13.032749,17.084925 12.408638,18.086089 11.005847,19.10513 C 9.8869191,19.918105 8.84
67342,20.049837 7.8347414,21.757648 C 7.6442029,22.057809 6.2890464,25.330408 6.120867,25.639037 C 5.2721928,27.201003 5.3742671,25.548707 6.3075169,22.931003 C 6.3075169,22.931003 7.2174355,20.095002 7.0978629,16.9993 C 7.0434233,15.566244 7.1231384,9.904591 10.82989,7.8994415 C 12.425164,7.0365969 14.901193,6.4946138 16.606318,7.7611229 C 21.944313,11.725315 19.458563,21.448078 15.392121,24.198454 C 13.75699,25.305002 12.399889,25.373692 11.005847,27.501164 C 9.918999,29.160046 6.8363585,30.274122 4.6937724,29.305892 C 5.5511957,28.829775 6.8820488,28.787432 7.2650702,27.723226 C 10.071625,19.916223 15.516555,23.290444 16.169829,14.950867 C 16.401197,11.993484 14.864252,9.6797058 12.683753,9.6797058 C 10.452702,9.6797058 8.9526972,12.659672 8.6143939,15.425102 C 8.4520474,16.758419 8.3645551,18.513277 8.2576203,19.43446 C 10.04246,18.397541 8.4714901,11.146635 12.470855,10.731679 C 15.950127,10.371298 15.652653,14.948985 15.256023,16.945666 C 14.953689,18.468112 14.256667,
19.779785 11.256658,21.500771 C 9.676938,22.406898 8.629948,23.557671 7.8289087,25.441439 C 7.3593673,26.546106 6.3988976,28.436461 4.53337,28.617122 C 4.3195003,28.637822 3.9131477,26.558339 3.7867702,25.457436 C 3.6001201,23.832427 4.440045,22.749402 4.7190479,21.000189 C 5.0301312,19.057142 5.2478894,17.510232 5.2644158,14.182117 C 4.4799026,9.0737386 5.2780257,7.5635248 7.4089462,5.140597 C 9.9005288,2.306477 12.031512,2.0549452 13.495175,2.0414516 C 15.638858,2.0237012 16.885321,3.5353485 16.249545,3.412085 C 14.609553,3.0949872 11.616348,2.6753267 9.0440775,5.2789155 C 7.6092061,6.7298495 6.0265698,8.5966804 6.1947492,10.870939 C 6.8664947,7.6039854 10.320801,3.7360695 13.733969,3.7360695 C 17.563499,3.7360695 19.963101,5.1556521 21.106332,6.9999001 C 22.267062,8.8733168 22.748269,10.281909 22.748269,12.806458 C 22.748269,14.670467 21.716834,15.435453 21.249236,16.946607 C 21.069391,17.527169 20.450141,19.029855 20.177944,20.195682 C 19.084291,24.880637 18.295889,27.13
7018 14.257639,30.204492 C 12.498075,31.540631 11.0982,31.630961 10.165922,31.269639 C 10.173699,31.117206 12.353227,30.918668 13.710328,28.921987 C 14.988685,27.040101 16.636455,26.992113 17.383054,24.825122 C 16.609234,25.763242 15.405731,26.658079 14.400543,27.100321 C 14.11668,27.225467 13.604365,27.870953 13.407994,28.100544 C 11.354844,30.515944 9.699297,31.088038 7.3321475,31.358088 C 7.7083639,31.534985 8.097218,31.671422 8.5094033,31.724115 C 11.518163,32.108961 13.373968,32.147539 15.76056,30.039827 C 19.12901,27.065506 19.683127,25.185502 20.034068,22.951704 C 20.293627,21.297527 21.655941,17.421123 22.247619,15.852571 C 23.080867,13.643616 22.556758,18.089853 22.318585,18.673238 C 20.924543,22.100151 21.486437,26.72018 17.942032,30.365393 C 15.509749,32.86736 12.310453,33.796071 7.1221663,32.621775 C 5.7388178,32.30844 6.239509,31.841732 4.657845,31.358088 C 4.1834429,31.143553 1.6402954,30.997707 1.3486549,30.995825 C 0.70898981,30.992061 0.10626602,30.860329 -0
.42938057,30.607215 z M 4.2543673,5.1321285 C 2.8544925,6.7006803 3.1305788,7.9154375 2.2021896,9.2289942 C 0.37749172,11.810941 1.4380914,15.378055 1.0521537,17.983526 C 0.55247613,21.363393 0.50913089,20.094485 -0.0071428722,17.974697 C -0.76834557,14.849245 -0.21551087,13.71635 -0.090105372,12.391502 C 0.60497135,9.1141985 1.2125559,7.7404222 2.8554645,5.6731707 C 4.2115933,3.9672413 6.135449,3.0215936 4.2543673,5.1321285 z M 24.022738,36.434475 C 23.474454,37.067729 22.882424,37.657701 22.248591,38.195919 L 21.462134,38.491375 C 18.22784,39.695783 16.567433,39.805873 11.843827,39.067233 C 9.918999,38.766131 7.2913178,38.39634 5.4841183,39.262008 C 6.5702412,39.494461 10.381694,38.345449 11.56288,39.970538 C 11.446224,40.140849 9.1976753,39.867976 7.0823087,40.331861 C 6.5699934,40.042991 6.0751766,39.715542 5.5978581,39.351398 C 5.1390103,38.993839 4.8036236,38.704969 4.4789305,38.398222 C 6.9432934,37.015037 10.891135,37.492095 13.335083,37.804487 C 17.567761,38.346471
19.246638,38.16581 21.951118,37.173114 C 22.683136,36.904005 23.374324,36.654655 24.022738,36.434475 z M 26.080749,7.8825045 C 27.13746,9.9657525 28.24375,11.559709 28.24375,14.719395 C 28.24375,18.197241 25.745362,19.892699 25.346787,22.207418 C 24.973487,24.37441 24.914187,26.178198 24.585605,26.178198 C 24.11898,26.178198 23.970243,24.110005 24.041208,22.659071 C 24.113147,21.19026 24.300417,20.389257 24.641637,19.294941 C 25.02563,18.066069 25.608953,15.315973 25.490352,13.235548 C 25.33481,10.489877 25.576852,11.073882 26.289427,12.729942 C 26.756052,13.812967 26.247915,15.934534 26.289427,17.469211 C 27.316002,15.633392 27.121554,12.867259 26.336068,10.86211 C 26.073591,10.189336 25.713282,8.8290928 26.080749,7.8825045 z M 24.608294,9.2431681 C 24.472195,8.6390829 23.70969,6.477977 22.713252,5.1870035 C 21.219079,3.2533658 22.564494,3.6570102 23.635787,4.8313069 C 24.682776,5.9792572 25.458582,7.3442852 25.487746,8.6644279 C 25.547046,11.377166 25.108944,11.455325 24.6
08294,9.2431681 z M 28.310827,16.859099 C 28.588858,16.49966 28.861055,18.750394 28.357489,19.540787 C 27.013026,21.649439 27.496178,25.113051 26.626116,27.878481 C 25.240824,32.27833 20.835106,35.602682 15.985124,35.513292 C 19.529529,34.338995 22.700634,33.797953 24.16953,26.824625 C 24.298825,26.210189 25.161109,26.541401 24.3834,29.44421 C 25.240824,28.207811 25.985479,25.109287 26.098247,22.60826 C 26.173102,20.914563 27.715881,18.48693 28.310827,16.859099 z M 0.33666203,7.0620024 C 3.1023869,1.995966 8.6775827,0.52715408 13.200929,0.52715408 C 16.027898,0.52715408 18.494205,1.0117396 20.804971,2.3394101 C 21.160772,2.5991103 21.699335,2.9199718 21.989031,3.2097822 C 22.925198,4.1497842 21.355919,3.7289582 19.391486,2.8597515 C 15.788463,1.2655141 13.210649,1.5085776 11.04376,1.8557856 C 9.4407091,2.112663 8.4189947,3.395148 7.616011,4.1733077 C 4.6568312,7.0356559 4.1960391,9.5950208 4.5518406,12.806458 C 4.9931901,16.791351 4.2543673,21.000189 3.3230616,23.294208 C 2.
8467153,24.469446 2.9099042,25.995654 3.1228018,26.962944 C 3.5077673,28.707452 5.0036733,29.433225 3.1344674,29.192978 C -0.13966347,28.772157 -0.13773997,24.273729 0.98021562,21.094283 C 2.1224745,17.845207 1.495243,15.951027 1.7355647,13.562036 C 2.8026799,2.8737448 3.1130804,15.825944 3.0518358,16.393333 C 2.6940901,19.711097 2.2203141,20.592698 1.7248713,22.0691 C 0.65905915,25.245183 1.9177265,27.094796 2.2524513,27.350994 C 0.89725312,23.033886 3.2657056,21.64003 3.4523556,16.633273 C 3.4980459,15.417575 3.5174886,14.673289 3.3318108,13.031344 C 2.7611674,7.9643665 4.0677172,5.041798 6.9588475,2.6941455 C 5.2051155,2.7110825 3.8587082,3.9794735 2.6688146,5.1321285 C 1.363237,6.3958147 0.15098419,9.2779223 -0.22620427,10.387294 C -0.79004277,12.046176 -1.2848596,10.030677 0.33666203,7.0620024 z M -1.1234851,28.497622 C -2.0265988,25.35299 -2.4786417,21.846097 -2.4037873,18.200883 C -2.3746233,16.749949 -2.2443571,15.155993 -1.7971749,13.290103 C -0.92614167,9.6543002 -
0.69568297,11.570518 -0.75405287,12.016887 C -1.906054,20.826496 1.6007898,19.123308 -0.53825967,25.728487 C -1.2848596,25.186504 -0.49674737,21.494805 -1.669846,19.297342 C -1.3645539,21.71935 -2.0304874,25.727486 0.6156649,28.255799 C 1.3719861,28.979384 2.8544925,29.37364 0.70898981,29.192978 C -0.027888672,29.130876 -0.63158467,28.886231 -1.1234851,28.497622 z M 0.47373313,32.736569 C 0.3036094,32.382775 0.14126284,32.020512 -0.015250972,31.650721 C 0.99382545,31.532162 3.0430867,31.426777 4.813345,32.172003 C 5.8418641,32.604837 3.7828816,32.394066 1.9863756,32.504156 C 1.3428221,32.542735 0.81203619,32.697049 0.47373313,32.736569 z M 1.3272679,34.351227 C 1.1308965,34.014369 0.94230231,33.668102 0.76051305,33.313367 C 5.2070598,32.522034 7.1591074,33.303958 10.615049,33.590945 C 14.176952,33.887342 18.036329,32.081673 19.249554,30.005012 C 19.872692,28.937983 20.047677,28.679223 20.173082,28.398823 C 20.281962,28.156059 20.353899,27.8973 20.648457,27.082444 C 21.301732
,25.276774 21.270624,27.738281 21.106332,28.345189 C 19.995182,32.442996 19.062904,31.720351 17.197377,33.526961 C 21.488381,31.992284 22.140685,28.921987 21.581706,24.645402 C 21.447552,23.621656 22.24373,20.854343 22.609254,19.86447 C 22.609254,19.86447 23.377241,17.382263 23.382101,12.981474 C 23.385017,10.110657 22.446907,5.6863438 18.607945,4.0349891 C 17.32181,3.4817147 15.535997,1.961151 15.535997,1.961151 C 16.331576,2.384936 18.250199,2.6518031 20.106978,3.7583519 C 20.177944,3.8006944 24.197722,6.0420202 24.599214,11.907858 C 24.788781,14.679876 24.809506,14.809385 24.622856,16.615054 C 24.622856,16.615054 24.508806,18.471876 23.856503,20.006553 C 23.541531,20.860929 23.2042,23.380774 23.390851,24.645402 C 23.822478,27.572675 22.050276,34.880978 14.832172,34.880978 C 11.643568,34.880978 9.7031857,34.471668 8.1769331,34.235492 C 6.7945567,34.020957 4.0006399,33.900516 1.3272679,34.351227 z M 3.9160641,37.83648 C 3.5466527,37.449752 3.1918233,37.039501 2.851576,36.60
6667 C 3.8975936,36.013873 4.8162614,36.004464 6.6798447,36.055275 C 8.8146538,36.055275 12.031449,36.901182 11.04376,37.179702 C 10.996125,37.191934 6.4669471,36.563384 3.9160641,37.83648 z M 14.719404,41.637889 C 14.296525,41.700932 13.867813,41.747979 13.430353,41.776207 C 11.348039,41.911704 9.4173774,41.49957 7.6714227,40.64049 C 8.5230132,40.522871 10.16981,40.466415 11.73106,40.686596 C 12.655561,40.817387 13.709355,41.272803 14.719404,41.637889 z M 20.747615,39.325051 C 19.318575,40.268817 17.725246,40.977347 15.991929,41.391361 C 14.720376,41.014044 11.970742,40.569899 11.991882,39.739346 C 12.007714,39.117287 15.479614,40.315864 17.475407,40.060868 C 18.84223,39.886794 19.878525,39.619567 20.747615,39.325051 z M 26.747633,31.987579 C 27.295917,31.456887 26.637782,34.608104 23.560001,35.921661 C 21.475743,36.660301 18.380466,38.308833 13.057053,36.902123 C 12.403778,36.72899 10.334101,36.181361 9.8733094,36.088208 C 6.2861299,35.366505 4.4235188,35.58198 2.3927281,3
5.996936 C 2.1564992,35.669488 1.9280475,35.329808 1.7073728,34.979777 C 2.3616198,34.715372 4.0055005,34.612809 5.186645,34.610927 C 8.9478367,34.604341 13.98933,35.964944 15.98318,36.055275 C 20.657206,36.266987 23.666937,32.984978 24.040237,32.984978 C 24.972514,32.984978 23.166287,34.616574 22.664665,35.067285 C 22.237898,35.45213 24.347431,34.500837 26.747633,31.987579 z M 28.517892,25.005783 C 28.394431,27.133255 28.234028,30.085933 26.454048,31.562273 C 23.614441,33.917452 24.785865,32.262334 26.025337,30.618507 C 28.180561,27.758982 27.284252,21.317287 28.69482,20.069596 C 28.711346,21.421731 28.607328,23.483337 28.517892,25.005783 z"
+ style="fill:#a1a1a1;fill-opacity:1"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ sodipodi:nodetypes="cscssscccssssssscsssscsscsssccssscscssssscsssscssssssscsssscsssssscscsscssssssscsccssscsccccscccscssccssssssscsccsssccsscscsccscsssssssssssscsscssccsssccsscccsscccssssscscsscssccsssccccsccscscccsscccssccssssccsscc" />
+<circle
+ clip-rule="evenodd"
+ cx="26.184"
+ cy="33.865002"
+ r="10"
+ id="circle2428"
+ sodipodi:cx="26.184"
+ sodipodi:cy="33.865002"
+ sodipodi:rx="10"
+ sodipodi:ry="10"
+ style="fill:url(#radialGradient84134);fill-opacity:1;fill-rule:evenodd;stroke:#448c00;stroke-width:1.31732059;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(1.1263785,0,0,1.1511056,-2.2713556,-9.1997707)" /><path
+ sodipodi:type="arc"
+ style="opacity:0.50746268;fill:url(#linearGradient84277);fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3253"
+ sodipodi:cx="26.455547"
+ sodipodi:cy="27.394291"
+ sodipodi:rx="6.1445141"
+ sodipodi:ry="3.072257"
+ d="M 32.600061,27.394291 A 6.1445141,3.072257 0 1 1 20.311033,27.394291 A 6.1445141,3.072257 0 1 1 32.600061,27.394291 z"
+ transform="matrix(1.1111101,0,0,1.2758999,-2.2387648,-10.924499)" /><path
+ clip-rule="evenodd"
+ d="M 21.777021,33.575871 C 22.428021,34.222871 23.330021,35.596871 24.012021,36.209871 C 25.264021,34.898871 27.599021,31.912871 31.077021,29.499871 C 31.754021,29.029871 33.458021,29.462871 32.557021,30.487871 C 29.846021,33.287871 27.332021,36.692871 25.385021,39.387871 C 24.468021,40.656871 23.706021,39.994871 22.908021,38.978871 C 21.912021,37.682871 20.897021,36.071871 20.509021,35.011871 C 20.282021,34.392871 20.974021,32.785871 21.777021,33.575871 z"
+ id="path2430"
+ style="opacity:0.42786069;fill:#398800;fill-opacity:1;fill-rule:evenodd;stroke:#398800;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3471)"
+ transform="matrix(1.1263785,0,0,1.1511056,-2.2713556,-9.1997707)" /><path
+ clip-rule="evenodd"
+ d="M 21.969435,28.185201 C 22.702707,28.929966 23.718701,30.511585 24.486892,31.217213 C 25.897117,29.708113 28.527211,26.270911 32.444755,23.493294 C 33.207314,22.952275 35.126663,23.450703 34.111795,24.630586 C 31.058183,27.853682 28.226468,31.773196 26.033409,34.875426 C 25.00052,36.33618 24.142219,35.574148 23.243369,34.404624 C 22.121497,32.912791 20.978222,31.05836 20.541188,29.838188 C 20.2855,29.125654 21.064954,27.275827 21.969435,28.185201 z"
+ id="path3469"
+ style="fill:#ffffff;fill-rule:evenodd" /><path
+ sodipodi:type="arc"
+ style="opacity:0.36815945;fill:none;fill-opacity:1;stroke:url(#linearGradient84279);stroke-width:1.2184273;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path84102"
+ sodipodi:cx="28.185518"
+ sodipodi:cy="31.917336"
+ sodipodi:rx="10.027505"
+ sodipodi:ry="10.240856"
+ d="M 38.213023,31.917336 A 10.027505,10.240856 0 1 1 18.158013,31.917336 A 10.027505,10.240856 0 1 1 38.213023,31.917336 z"
+ transform="matrix(1.0116992,0,0,1.0189783,-1.4418411,-2.7101136)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/right-index-finger.png b/panels/user-accounts/data/icons/right-index-finger.png
new file mode 100644
index 0000000..4aaeaac
Binary files /dev/null and b/panels/user-accounts/data/icons/right-index-finger.png differ
diff --git a/panels/user-accounts/data/icons/right-index-finger.svg b/panels/user-accounts/data/icons/right-index-finger.svg
new file mode 100644
index 0000000..5a621a2
--- /dev/null
+++ b/panels/user-accounts/data/icons/right-index-finger.svg
@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 40.425 46.214"
+ enable-background="new 0 0 40.425 46.214"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="right-index-finger.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
+ id="metadata44"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs42"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 23.107 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="40.424999 : 23.107 : 1"
+ inkscape:persp3d-origin="20.2125 : 15.404667 : 1"
+ id="perspective46" />
+
+
+
+
+
+
+<radialGradient
+ r="8.341651"
+ fy="9.3411446"
+ fx="38.658855"
+ cy="9.3411446"
+ cx="38.658855"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2479"
+ xlink:href="#linearGradient2378"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient2378"><stop
+ id="stop2386"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ style="stop-color:#27dc16;stop-opacity:1;"
+ offset="1"
+ id="stop2382" /></linearGradient><linearGradient
+ id="linearGradient3702"><stop
+ id="stop3704"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" /><stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3710" /><stop
+ id="stop3706"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient6732"><stop
+ id="stop6734"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop6736"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient4585"><stop
+ id="stop4587"
+ offset="0"
+ style="stop-color:#9e9e9e;stop-opacity:1;" /><stop
+ id="stop4589"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective
+ id="perspective2516"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86956"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86964"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86966"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /></defs><sodipodi:namedview
+ inkscape:window-height="922"
+ inkscape:window-width="1302"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.122171"
+ inkscape:cx="15.672125"
+ inkscape:cy="30.299841"
+ inkscape:window-x="36"
+ inkscape:window-y="91"
+ inkscape:current-layer="svg2" />
+<g
+ id="g35"
+ style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(-1.1605241,0,0,1.3370602,44.823901,-0.7984997)">
+ <circle
+ style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:ry="3.829"
+ sodipodi:rx="3.829"
+ sodipodi:cy="5.5700002"
+ sodipodi:cx="26.49"
+ id="circle37"
+ r="3.829"
+ cy="5.5700002"
+ cx="26.49"
+ stroke-miterlimit="3.8637" />
+ <path
+ style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path39"
+ stroke-miterlimit="3.8637"
+ d="" />
+ </g><g
+ id="Background"
+ transform="matrix(-1,0,0,1,40.436754,0)">
+</g>
+<g
+ id="Guides"
+ display="none"
+ style="display:none">
+</g>
+<g
+ id="g7"
+ style="fill:#2f2f2f;fill-opacity:1"
+ transform="matrix(-1,0,0,1,40.436754,0)">
+ <path
+ style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd"
+ id="path9"
+ d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.1
32,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z"
+ clip-rule="evenodd" />
+ </g>
+<g
+ style="display:inline"
+ inkscape:label="Base"
+ id="layer1"
+ transform="matrix(-1,0,0,1,92.903401,2.6102791)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/right-little-finger.png b/panels/user-accounts/data/icons/right-little-finger.png
new file mode 100644
index 0000000..17946af
Binary files /dev/null and b/panels/user-accounts/data/icons/right-little-finger.png differ
diff --git a/panels/user-accounts/data/icons/right-little-finger.svg b/panels/user-accounts/data/icons/right-little-finger.svg
new file mode 100644
index 0000000..9fcec2a
--- /dev/null
+++ b/panels/user-accounts/data/icons/right-little-finger.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 40.425 46.214"
+ enable-background="new 0 0 40.425 46.214"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="right-pinky-finger.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-ring-finger.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"><metadata
+ id="metadata44"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs42"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 23.107 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="40.424999 : 23.107 : 1"
+ inkscape:persp3d-origin="20.2125 : 15.404667 : 1"
+ id="perspective46" />
+
+
+
+
+
+
+<radialGradient
+ r="8.341651"
+ fy="9.3411446"
+ fx="38.658855"
+ cy="9.3411446"
+ cx="38.658855"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2479"
+ xlink:href="#linearGradient2378"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient2378"><stop
+ id="stop2386"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ style="stop-color:#27dc16;stop-opacity:1;"
+ offset="1"
+ id="stop2382" /></linearGradient><linearGradient
+ id="linearGradient3702"><stop
+ id="stop3704"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" /><stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3710" /><stop
+ id="stop3706"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient6732"><stop
+ id="stop6734"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop6736"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient4585"><stop
+ id="stop4587"
+ offset="0"
+ style="stop-color:#9e9e9e;stop-opacity:1;" /><stop
+ id="stop4589"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective
+ id="perspective2516"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86956"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86964"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86966"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /></defs><sodipodi:namedview
+ inkscape:window-height="933"
+ inkscape:window-width="1054"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.122171"
+ inkscape:cx="22.316511"
+ inkscape:cy="30.299841"
+ inkscape:window-x="346"
+ inkscape:window-y="109"
+ inkscape:current-layer="svg2" />
+<g
+ id="g35"
+ style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(-1.1074589,0,0,1.2726911,65.968411,5.5330271)">
+ <circle
+ style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:ry="3.829"
+ sodipodi:rx="3.829"
+ sodipodi:cy="5.5700002"
+ sodipodi:cx="26.49"
+ id="circle37"
+ r="3.829"
+ cy="5.5700002"
+ cx="26.49"
+ stroke-miterlimit="3.8637" />
+ <path
+ style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path39"
+ stroke-miterlimit="3.8637"
+ d="" />
+ </g><g
+ id="Background"
+ transform="matrix(-1,0,0,1,40.436756,0)">
+</g>
+<g
+ id="Guides"
+ display="none"
+ style="display:none">
+</g>
+<g
+ id="g7"
+ style="fill:#2f2f2f;fill-opacity:1"
+ transform="matrix(-1,0,0,1,40.436756,0)">
+ <path
+ style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd"
+ id="path9"
+ d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.1
32,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z"
+ clip-rule="evenodd" />
+ </g>
+<g
+ style="display:inline"
+ inkscape:label="Base"
+ id="layer1"
+ transform="matrix(-1,0,0,1,92.903403,2.6102791)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/right-middle-finger.png b/panels/user-accounts/data/icons/right-middle-finger.png
new file mode 100644
index 0000000..71bd41b
Binary files /dev/null and b/panels/user-accounts/data/icons/right-middle-finger.png differ
diff --git a/panels/user-accounts/data/icons/right-middle-finger.svg b/panels/user-accounts/data/icons/right-middle-finger.svg
new file mode 100644
index 0000000..b33a654
--- /dev/null
+++ b/panels/user-accounts/data/icons/right-middle-finger.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 40.425 46.214"
+ enable-background="new 0 0 40.425 46.214"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="right-middle-finger.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-index-finger.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"><metadata
+ id="metadata44"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs42"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 23.107 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="40.424999 : 23.107 : 1"
+ inkscape:persp3d-origin="20.2125 : 15.404667 : 1"
+ id="perspective46" />
+
+
+
+
+
+
+<radialGradient
+ r="8.341651"
+ fy="9.3411446"
+ fx="38.658855"
+ cy="9.3411446"
+ cx="38.658855"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2479"
+ xlink:href="#linearGradient2378"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient2378"><stop
+ id="stop2386"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ style="stop-color:#27dc16;stop-opacity:1;"
+ offset="1"
+ id="stop2382" /></linearGradient><linearGradient
+ id="linearGradient3702"><stop
+ id="stop3704"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" /><stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3710" /><stop
+ id="stop3706"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient6732"><stop
+ id="stop6734"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop6736"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient4585"><stop
+ id="stop4587"
+ offset="0"
+ style="stop-color:#9e9e9e;stop-opacity:1;" /><stop
+ id="stop4589"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective
+ id="perspective2516"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86956"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86964"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86966"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /></defs><sodipodi:namedview
+ inkscape:window-height="933"
+ inkscape:window-width="1054"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.122171"
+ inkscape:cx="22.316511"
+ inkscape:cy="30.299841"
+ inkscape:window-x="362"
+ inkscape:window-y="121"
+ inkscape:current-layer="svg2" />
+<g
+ id="g35"
+ style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(-1.1824583,0,0,1.3363867,53.282364,-2.0066594)">
+ <circle
+ style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:ry="3.829"
+ sodipodi:rx="3.829"
+ sodipodi:cy="5.5700002"
+ sodipodi:cx="26.49"
+ id="circle37"
+ r="3.829"
+ cy="5.5700002"
+ cx="26.49"
+ stroke-miterlimit="3.8637" />
+ <path
+ style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path39"
+ stroke-miterlimit="3.8637"
+ d="" />
+ </g><g
+ id="Background"
+ transform="matrix(-1,0,0,1,40.436756,0)">
+</g>
+<g
+ id="Guides"
+ display="none"
+ style="display:none">
+</g>
+<g
+ id="g7"
+ style="fill:#2f2f2f;fill-opacity:1"
+ transform="matrix(-1,0,0,1,40.436756,0)">
+ <path
+ style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd"
+ id="path9"
+ d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.1
32,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z"
+ clip-rule="evenodd" />
+ </g>
+<g
+ style="display:inline"
+ inkscape:label="Base"
+ id="layer1"
+ transform="matrix(-1,0,0,1,92.903403,2.6102791)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/right-ring-finger.png b/panels/user-accounts/data/icons/right-ring-finger.png
new file mode 100644
index 0000000..aa73ae6
Binary files /dev/null and b/panels/user-accounts/data/icons/right-ring-finger.png differ
diff --git a/panels/user-accounts/data/icons/right-ring-finger.svg b/panels/user-accounts/data/icons/right-ring-finger.svg
new file mode 100644
index 0000000..9e264fe
--- /dev/null
+++ b/panels/user-accounts/data/icons/right-ring-finger.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 40.425 46.214"
+ enable-background="new 0 0 40.425 46.214"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="right-ring-finger.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-middle-finger.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"><metadata
+ id="metadata44"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs42"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 23.107 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="40.424999 : 23.107 : 1"
+ inkscape:persp3d-origin="20.2125 : 15.404667 : 1"
+ id="perspective46" />
+
+
+
+
+
+
+<radialGradient
+ r="8.341651"
+ fy="9.3411446"
+ fx="38.658855"
+ cy="9.3411446"
+ cx="38.658855"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2479"
+ xlink:href="#linearGradient2378"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient2378"><stop
+ id="stop2386"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ style="stop-color:#27dc16;stop-opacity:1;"
+ offset="1"
+ id="stop2382" /></linearGradient><linearGradient
+ id="linearGradient3702"><stop
+ id="stop3704"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" /><stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3710" /><stop
+ id="stop3706"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient6732"><stop
+ id="stop6734"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop6736"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient4585"><stop
+ id="stop4587"
+ offset="0"
+ style="stop-color:#9e9e9e;stop-opacity:1;" /><stop
+ id="stop4589"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective
+ id="perspective2516"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86956"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86964"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86966"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /></defs><sodipodi:namedview
+ inkscape:window-height="933"
+ inkscape:window-width="1054"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.122171"
+ inkscape:cx="22.316511"
+ inkscape:cy="30.299841"
+ inkscape:window-x="424"
+ inkscape:window-y="91"
+ inkscape:current-layer="svg2" />
+<g
+ id="g35"
+ style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(-1.1824583,0,0,1.3363867,61.073222,-0.7947482)">
+ <circle
+ style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:ry="3.829"
+ sodipodi:rx="3.829"
+ sodipodi:cy="5.5700002"
+ sodipodi:cx="26.49"
+ id="circle37"
+ r="3.829"
+ cy="5.5700002"
+ cx="26.49"
+ stroke-miterlimit="3.8637" />
+ <path
+ style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path39"
+ stroke-miterlimit="3.8637"
+ d="" />
+ </g><g
+ id="Background"
+ transform="matrix(-1,0,0,1,40.436756,0)">
+</g>
+<g
+ id="Guides"
+ display="none"
+ style="display:none">
+</g>
+<g
+ id="g7"
+ style="fill:#2f2f2f;fill-opacity:1"
+ transform="matrix(-1,0,0,1,40.436756,0)">
+ <path
+ style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd"
+ id="path9"
+ d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.1
32,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z"
+ clip-rule="evenodd" />
+ </g>
+<g
+ style="display:inline"
+ inkscape:label="Base"
+ id="layer1"
+ transform="matrix(-1,0,0,1,92.903403,2.6102791)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/right-thumb.png b/panels/user-accounts/data/icons/right-thumb.png
new file mode 100644
index 0000000..1c967d6
Binary files /dev/null and b/panels/user-accounts/data/icons/right-thumb.png differ
diff --git a/panels/user-accounts/data/icons/right-thumb.svg b/panels/user-accounts/data/icons/right-thumb.svg
new file mode 100644
index 0000000..0aa0f2e
--- /dev/null
+++ b/panels/user-accounts/data/icons/right-thumb.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0px"
+ y="0px"
+ width="48"
+ height="48"
+ viewBox="0 0 40.425 46.214"
+ enable-background="new 0 0 40.425 46.214"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="right-thumb.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/Users/mlanglie/Desktop/Fingerprint Enrollment Icons/Vector/left-pinky-finger.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"><metadata
+ id="metadata44"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs42"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 23.107 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="40.424999 : 23.107 : 1"
+ inkscape:persp3d-origin="20.2125 : 15.404667 : 1"
+ id="perspective46" />
+
+
+
+
+
+
+<radialGradient
+ r="8.341651"
+ fy="9.3411446"
+ fx="38.658855"
+ cy="9.3411446"
+ cx="38.658855"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2479"
+ xlink:href="#linearGradient2378"
+ inkscape:collect="always" /><linearGradient
+ id="linearGradient2378"><stop
+ id="stop2386"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ style="stop-color:#27dc16;stop-opacity:1;"
+ offset="1"
+ id="stop2382" /></linearGradient><linearGradient
+ id="linearGradient3702"><stop
+ id="stop3704"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" /><stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3710" /><stop
+ id="stop3706"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
+ id="linearGradient6732"><stop
+ id="stop6734"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" /><stop
+ id="stop6736"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:1;" /></linearGradient><linearGradient
+ id="linearGradient4585"><stop
+ id="stop4587"
+ offset="0"
+ style="stop-color:#9e9e9e;stop-opacity:1;" /><stop
+ id="stop4589"
+ offset="1"
+ style="stop-color:#dddddd;stop-opacity:0;" /></linearGradient><inkscape:perspective
+ id="perspective2516"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 24 : 1"
+ sodipodi:type="inkscape:persp3d" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86956"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86964"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2378"
+ id="radialGradient86966"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0059869,5.3782029e-7,-9.45767e-7,1.1767077,-0.1585918,-0.9842761)"
+ cx="26.49"
+ cy="5.5700002"
+ fx="26.49"
+ fy="5.5700002"
+ r="4.0552225" /></defs><sodipodi:namedview
+ inkscape:window-height="933"
+ inkscape:window-width="1054"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="11.122171"
+ inkscape:cx="22.316511"
+ inkscape:cy="30.299841"
+ inkscape:window-x="116"
+ inkscape:window-y="117"
+ inkscape:current-layer="svg2" />
+<g
+ id="g35"
+ style="fill:url(#radialGradient86956);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ transform="matrix(-1.1916623,0,0,1.4021101,35.910183,14.334323)">
+ <circle
+ style="fill:url(#radialGradient86964);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:ry="3.829"
+ sodipodi:rx="3.829"
+ sodipodi:cy="5.5700002"
+ sodipodi:cx="26.49"
+ id="circle37"
+ r="3.829"
+ cy="5.5700002"
+ cx="26.49"
+ stroke-miterlimit="3.8637" />
+ <path
+ style="fill:url(#radialGradient86966);fill-opacity:1;stroke:#31ae00;stroke-width:0.45244551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path39"
+ stroke-miterlimit="3.8637"
+ d="" />
+ </g><g
+ id="Background"
+ transform="matrix(-1,0,0,1,40.436756,0)">
+</g>
+<g
+ id="Guides"
+ display="none"
+ style="display:none">
+</g>
+<g
+ id="g7"
+ style="fill:#2f2f2f;fill-opacity:1"
+ transform="matrix(-1,0,0,1,40.436756,0)">
+ <path
+ style="fill:#2f2f2f;fill-opacity:1;fill-rule:evenodd"
+ id="path9"
+ d="M 20.569,13.286 C 20.843,15.459 20.791,17.834 20.82,18.542 C 20.865,18.917 21.913,19.241 22.094,18.877 C 22.301,17.514 22.586,14.847 22.92,13.455 C 23.372,9.917 23.886,8.247 24.072,7.257 C 24.58,4.567 25.014,3.12 26.762,3.12 C 28.384,3.12 28.623,5.167 28.153,8.165 C 28.025,9.379 28.036,12.24 27.433,14.894 C 27.165,16.691 26.962,21.278 27.139,24.981 C 27.189,26.045 27.669,27.315 28.242,27.945 C 29.462,27.773 30.379,24.377 31.988,22.847 C 32.809,21.464 34.883,19.299 36.951,19.299 C 39.243,19.299 39.292,21 38.345,22.262 C 37.187,23.806 36.525,24.528 36.079,25.802 C 35.633,27.076 33.758,29.778 33.471,31.209 C 32.902,34.054 32.471,35.105 29.284,37.661 C 27.783,38.865 27.302,39.637 26.855,40.401 C 26.347,41.273 26.409,44.721 26.409,46.212 L 10.64,46.212 C 10.64,46.212 10.767,43.587 10.513,42.058 C 10.461,41.746 10.105,39.613 9.176,38.231 C 8.317,36.954 5.902,32.306 5.902,26.836 C 5.902,24.026 4.39,21.718 3.854,19.533 C 3.591,17.833 2.638,15.423 2.57,14.709 C 1.684,11.559 2.1
32,10.118 3.741,10.118 C 5.132,10.118 5.528,11.927 6.093,13.622 C 6.258,14.52 7.505,16.934 7.788,18.233 C 8.038,19.088 8.897,21.556 9.437,21.727 C 9.75,21.784 10.151,21.523 10.291,21.043 C 10.52,20.26 9.818,18.945 9.539,15.26 C 9.087,12.343 9.17,10.139 9.116,8.949 C 8.653,5.424 9.133,3.598 10.693,3.598 C 12.176,3.598 12.568,4.455 13.02,7.936 C 13.189,9.518 13.754,12.095 14.037,14.864 C 14.213,16.364 14.639,18.601 14.98,19.17 C 15.285,19.503 15.919,19.198 15.9,18.848 C 15.894,16.377 15.732,14.977 15.958,12.999 C 15.845,10.287 16.219,7.823 16.219,6.467 C 16.219,3.494 16.502,1.473 18.358,1.473 C 20.333,1.473 20.287,3.776 20.287,6.806 C 20.399,7.545 20.456,10.087 20.569,13.286 z"
+ clip-rule="evenodd" />
+ </g>
+<g
+ style="display:inline"
+ inkscape:label="Base"
+ id="layer1"
+ transform="matrix(-1,0,0,1,92.903403,2.6102791)" /></svg>
\ No newline at end of file
diff --git a/panels/user-accounts/data/language-chooser.ui b/panels/user-accounts/data/language-chooser.ui
new file mode 100644
index 0000000..b61e24b
--- /dev/null
+++ b/panels/user-accounts/data/language-chooser.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<interface>
+ <object class="GtkDialog" id="dialog">
+ <property name="height_request">400</property>
+ <property name="border_width">5</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="type_hint">dialog</property>
+ <property name="title"> </property>
+ <property name="icon_name">system-users</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="content-area">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="top_padding">10</property>
+ <property name="bottom_padding">10</property>
+ <property name="left_padding">10</property>
+ <property name="right_padding">10</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="language-list">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="headers_clickable">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="action-area">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel-button">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ok-button">
+ <property name="label" translatable="yes">Select</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">cancel-button</action-widget>
+ <action-widget response="-5">ok-button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/panels/user-accounts/data/password-dialog.ui b/panels/user-accounts/data/password-dialog.ui
new file mode 100644
index 0000000..69e9d6f
--- /dev/null
+++ b/panels/user-accounts/data/password-dialog.ui
@@ -0,0 +1,531 @@
+<?xml version="1.0"?>
+<interface>
+ <!-- interface-requires gtk+ 2.12 -->
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkListStore" id="action-model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gint -->
+ <column type="gint"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Set a password now</col>
+ <col id="1">0</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Choose password at next login</col>
+ <col id="1">1</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Log in without a password</col>
+ <col id="1">2</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Disable this account</col>
+ <col id="1">3</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Enable this account</col>
+ <col id="1">4</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkDialog" id="dialog">
+ <property name="border_width">5</property>
+ <property name="title"> </property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="icon_name">system-users</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">16</property>
+ <child>
+ <object class="GtkTable" id="table4">
+ <property name="visible">True</property>
+ <property name="n_rows">8</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">10</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox14">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="password-normal-hint-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Hint:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">normal-hint-entry</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox15">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkEntry" id="normal-hint-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="password-normal-hint-description-label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes"><small>This hint may be displayed at the login screen. It will be visible to all users of this system. Do <b>not</b> include the password here.</small></property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="verify-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="password-normal-verify-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">C_onfirm password:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">verify-entry</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox17">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="password-normal-password-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_New password:</property>
+ <property name="use_underline">True</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label35">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox16">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkEntry" id="password-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="generate-again-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Choose a generated password</property>
+ <child>
+ <object class="GtkImage" id="generate-again-image">
+ <property name="visible">True</property>
+ <property name="stock">gtk-execute</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox13">
+ <property name="visible">True</property>
+ <property name="spacing">9</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">6</property>
+ <child>
+ <object class="UmStrengthBar" id="strength-indicator">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="strength-indicator-label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Fair</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="old-password-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Current _password:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">old-password-entry</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="old-password-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="action-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Action:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">action-combo</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="action-combo">
+ <property name="visible">True</property>
+ <property name="model">action-model</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="blablalabel23">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="user-icon">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Changing password for:</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="user-name">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.200000"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show-password-checkbutton">
+ <property name="label" translatable="yes">_Show password</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">edge</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <child>
+ <object class="GtkLabel" id="password-normal-strength-hints-label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes"><a href="http://wolfram.org/writing/howto/password.html">How to choose a strong password</a></property>
+ <property name="use_markup">True</property>
+ <property name="track_visited_links">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="ok-button">
+ <property name="label" translatable="yes">Ch_ange</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel-button">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkSizeGroup" id="sizegroup"/>
+</interface>
diff --git a/panels/user-accounts/data/photo-dialog.ui b/panels/user-accounts/data/photo-dialog.ui
new file mode 100644
index 0000000..5bc10d8
--- /dev/null
+++ b/panels/user-accounts/data/photo-dialog.ui
@@ -0,0 +1,304 @@
+<?xml version="1.0"?>
+<interface>
+ <object class="GtkAdjustment" id="browse-scale-adjustment">
+ <property name="lower">0</property>
+ <property name="upper">100</property>
+ <property name="page_size">0</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <property name="value">50</property>
+ </object>
+ <object class="GtkDialog" id="dialog">
+ <property name="border_width">5</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="title"> </property>
+ <property name="icon_name">system-users</property>
+ <property name="modal">True</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox7">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox17">
+ <property name="visible">True</property>
+ <property name="border_width">10</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkVBox" id="vbox21">
+ <property name="visible">True</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkHBox" id="user-photo-dialog-user-box">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkImage" id="user-icon">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="blablabox">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="blabla">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Changing photo for:</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="user-name">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.2"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label52">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox33">
+ <property name="visible">True</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="label53">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Choose a picture that will be shown at the login screen for this account.</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">True</property>
+ <property name="width_chars">28</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox22">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkRadioButton" id="gallery-radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Gallery</property>
+ <property name="draw-indicator">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="browse-radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Browse for more pictures</property>
+ <property name="draw-indicator">False</property>
+ <property name="group">gallery-radiobutton</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="photo-radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Take a photograph</property>
+ <property name="draw-indicator">False</property>
+ <property name="group">gallery-radiobutton</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">False</property>
+ <child>
+ <object class="GtkVBox" id="vbox19">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <object class="GtkIconView" id="gallery">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label54">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Gallery</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox1234">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <child>
+ <object class="GtkDrawingArea" id="browse-drawing-area">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="fill">True</property>
+ <property name="expand">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1234">
+ <property name="visible">False</property>
+ <property name="homogeneous">False</property>
+ <child>
+ <object class="GtkImage" id="browse-scale-small">
+ <property name="visible">True</property>
+ <property name="icon_name">avatar-default</property>
+ <property name="pixel_size">10</property>
+ </object>
+ <packing>
+ <property name="fill">False</property>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="browse-scale">
+ <property name="visible">True</property>
+ <property name="draw-value">False</property>
+ <property name="adjustment">browse-scale-adjustment</property>
+ </object>
+ <packing>
+ <property name="fill">True</property>
+ <property name="expand">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="browse-scale-big">
+ <property name="visible">True</property>
+ <property name="icon_name">avatar-default</property>
+ <property name="pixel_size">20</property>
+ </object>
+ <packing>
+ <property name="fill">False</property>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="fill">True</property>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label55">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Browse</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkDrawingArea" id="photo">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label56">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Photograph</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area7">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="cancel-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Cancel</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="ok-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Select</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/panels/user-accounts/data/user-accounts-dialog.ui b/panels/user-accounts/data/user-accounts-dialog.ui
new file mode 100644
index 0000000..e6e4f8c
--- /dev/null
+++ b/panels/user-accounts/data/user-accounts-dialog.ui
@@ -0,0 +1,726 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkListStore" id="shortname-model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="account-type-model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gint -->
+ <column type="gint"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" context="Account type">Standard</col>
+ <col id="1">0</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="Account type">Administrator</col>
+ <col id="1">1</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="Account type">Supervised</col>
+ <col id="1">2</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="language-model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkImage" id="plus">
+ <property name="pixel_size">12</property>
+ <property name="icon_name">list-add</property>
+ </object>
+ <object class="GtkImage" id="minus">
+ <property name="pixel_size">12</property>
+ <property name="icon_name">list-remove</property>
+ </object>
+ <object class="GtkWindow" id="user-account-window">
+ <property name="border_width">12</property>
+ <property name="title" translatable="yes">Account Information</property>
+ <property name="icon_name">system-users</property>
+ <child>
+ <object class="GtkVBox" id="user-account-main-vbox">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkNotebook" id="top-level-notebook">
+ <property name="visible">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">True</property>
+ <property name="border_width">5</property>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <property name="border_width">12</property>
+ <child>
+ <object class="GtkVBox" id="userlist-vbox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="list-treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="add-delete-buttonbox">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkButton" id="add-user-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Create a user</property>
+ <property name="image">plus</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="delete-user-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Delete the selected user</property>
+ <property name="image">minus</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="main-user-vbox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">8</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkHBox" id="hbox20">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="button11">
+ <property name="label" translatable="yes">Open</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox4">
+ <child>
+ <object class="GtkButton" id="button10">
+ <property name="label" translatable="yes">Open</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="account-parental-controls-label1">
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Address Book Card:</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="UmEditableEntry" id="account-location-entry">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="account-fingerprint-notebook">
+ <property name="visible">True</property>
+ <property name="show_tabs">False</property>
+ <property name="show_border">False</property>
+ <child>
+ <object class="GtkLabel" id="account-fingerprint-value-label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="account-fingerprint-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="relief">none</property>
+ <child>
+ <object class="GtkLabel" id="account-fingerprint-button-label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="UmEditableCombo" id="account-type-combo">
+ <property name="visible">True</property>
+ <property name="model">account-type-model</property>
+ <property name="text-column">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="account-type-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Account type:</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox10">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="UmEditableEntry" id="full-name-entry">
+ <property name="visible">True</property>
+ <property name="scale">1.2</property>
+ <property name="weight">700</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="password-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Password:</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="UmEditableButton" id="account-password-button">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="email-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">E-mail address:</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="UmEditableEntry" id="account-email-entry">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="language-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Language:</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="location-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Location:</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="account-fingerprint-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Fingerprint Login:</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="account-parental-controls-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Restrictions:</property>
+ <attributes>
+ <attribute name="foreground" value="#555555555555"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox5">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="user-icon-nonbutton">
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <object class="GtkImage" id="user-icon-image">
+ <property name="visible">True</property>
+ <property name="icon_name">avatar-default</property>
+ <property name="icon-size">6</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="user-icon-button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="relief">none</property>
+ <child>
+ <object class="GtkImage" id="user-icon-image2">
+ <property name="visible">True</property>
+ <property name="icon_name">avatar-default</property>
+ <property name="icon-size">6</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="UmEditableCombo" id="account-language-combo">
+ <property name="visible">True</property>
+ <property name="model">language-model</property>
+ <property name="text-column">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="accounts-tab-label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Accounts</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkVBox" id="main-login-window-vbox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <property name="border_width">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="dm-automatic-login-label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Automatic Login:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="dm-automatic-login-combobox">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="dm-show-user-list-checkbutton">
+ <property name="label" translatable="yes">Show list of users</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="dm-show-power-buttons-checkbutton">
+ <property name="label" translatable="yes">Show Shutdown, Suspend and Restart actions</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="dm-show-password-hints-checkbutton">
+ <property name="label" translatable="yes">Show password hints</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">A guest account will allow anyone to temporarily log in to this computer without a password. For security, remote logins to this account are not allowed.
+
+<b>When the guest user logs out, all files and data associated with the account will be deleted.</b></property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="dm-allow-guest-login-checkbutton">
+ <property name="label" translatable="yes">Allow guests to log in to this computer</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkAlignment" id="lockbutton-alignment">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">1</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="login-options-tab-label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Login Options</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkSizeGroup" id="user-icon-sizegroup">
+ <property name="mode">both</property>
+ <widgets>
+ <widget name="user-icon-button"/>
+ <widget name="user-icon-nonbutton"/>
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup" id="label-width-sizegroup">
+ <widgets>
+ <widget name="account-fingerprint-label"/>
+ <widget name="location-label"/>
+ <widget name="language-label"/>
+ <widget name="email-label"/>
+ <widget name="password-label"/>
+ <widget name="account-type-label"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/panels/user-accounts/fingerprint-strings.h b/panels/user-accounts/fingerprint-strings.h
new file mode 100644
index 0000000..6fb7cb3
--- /dev/null
+++ b/panels/user-accounts/fingerprint-strings.h
@@ -0,0 +1,111 @@
+/*
+ * Helper functions to translate statuses and actions to strings
+ * Copyright (C) 2008 Bastien Nocera <hadess hadess net>
+ *
+ * Experimental code. This will be moved out of fprintd into it's own
+ * package once the system has matured.
+ *
+ * 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.
+ */
+
+struct {
+ const char *dbus_name;
+ const char *place_str;
+ const char *swipe_str;
+} fingers[11] = {
+ { "left-thumb", N_("Place your left thumb on %s"), N_("Swipe your left thumb on %s") },
+ { "left-index-finger", N_("Place your left index finger on %s"), N_("Swipe your left index finger on %s") },
+ { "left-middle-finger", N_("Place your left middle finger on %s"), N_("Swipe your left middle finger on %s") },
+ { "left-ring-finger", N_("Place your left ring finger on %s"), N_("Swipe your left ring finger on %s") },
+ { "left-little-finger", N_("Place your left little finger on %s"), N_("Swipe your left little finger on %s") },
+ { "right-thumb", N_("Place your right thumb on %s"), N_("Swipe your right thumb on %s") },
+ { "right-index-finger", N_("Place your right index finger on %s"), N_("Swipe your right index finger on %s") },
+ { "right-middle-finger", N_("Place your right middle finger on %s"), N_("Swipe your right middle finger on %s") },
+ { "right-ring-finger", N_("Place your right ring finger on %s"), N_("Swipe your right ring finger on %s") },
+ { "right-little-finger", N_("Place your right little finger on %s"), N_("Swipe your right little finger on %s") },
+ { NULL, NULL, NULL }
+};
+
+static const char *finger_str_to_msg(const char *finger_name, gboolean is_swipe)
+{
+ int i;
+
+ if (finger_name == NULL)
+ return NULL;
+
+ for (i = 0; fingers[i].dbus_name != NULL; i++) {
+ if (g_str_equal (fingers[i].dbus_name, finger_name)) {
+ if (is_swipe == FALSE)
+ return fingers[i].place_str;
+ else
+ return fingers[i].swipe_str;
+ }
+ }
+
+ return NULL;
+}
+
+/* Cases not handled:
+ * verify-no-match
+ * verify-match
+ * verify-unknown-error
+ */
+static const char *verify_result_str_to_msg(const char *result, gboolean is_swipe)
+{
+ if (result == NULL)
+ return NULL;
+
+ if (strcmp (result, "verify-retry-scan") == 0) {
+ if (is_swipe == FALSE)
+ return N_("Place your finger on the reader again");
+ else
+ return N_("Swipe your finger again");
+ }
+ if (strcmp (result, "verify-swipe-too-short") == 0)
+ return N_("Swipe was too short; try again");
+ if (strcmp (result, "verify-finger-not-centered") == 0)
+ return N_("Your finger was not centered; try swiping your finger again");
+ if (strcmp (result, "verify-remove-and-retry") == 0)
+ return N_("Remove your finger and try swiping it again");
+
+ return NULL;
+}
+
+/* Cases not handled:
+ * enroll-completed
+ * enroll-failed
+ * enroll-unknown-error
+ */
+static const char *enroll_result_str_to_msg(const char *result, gboolean is_swipe)
+{
+ if (result == NULL)
+ return NULL;
+
+ if (strcmp (result, "enroll-retry-scan") == 0 || strcmp (result, "enroll-stage-passed") == 0) {
+ if (is_swipe == FALSE)
+ return N_("Place your finger on the reader again");
+ else
+ return N_("Swipe your finger again");
+ }
+ if (strcmp (result, "enroll-swipe-too-short") == 0)
+ return N_("Swipe was too short, try again");
+ if (strcmp (result, "enroll-finger-not-centered") == 0)
+ return N_("Your finger was not centered, try swiping your finger again");
+ if (strcmp (result, "enroll-remove-and-retry") == 0)
+ return N_("Remove your finger, and try swiping your finger again");
+
+ return NULL;
+}
+
diff --git a/panels/user-accounts/fprintd-marshal.list b/panels/user-accounts/fprintd-marshal.list
new file mode 100644
index 0000000..c4effb6
--- /dev/null
+++ b/panels/user-accounts/fprintd-marshal.list
@@ -0,0 +1 @@
+VOID:STRING,BOOLEAN
diff --git a/panels/user-accounts/gdm-languages.c b/panels/user-accounts/gdm-languages.c
new file mode 100644
index 0000000..2c99e15
--- /dev/null
+++ b/panels/user-accounts/gdm-languages.c
@@ -0,0 +1,1003 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2008 Red Hat, Inc,
+ * 2007 William Jon McCann <mccann jhu edu>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by : William Jon McCann <mccann jhu edu>
+ * Ray Strode <rstrode redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include "gdm-languages.h"
+
+#include <langinfo.h>
+#ifndef __LC_LAST
+#define __LC_LAST 13
+#endif
+#include "locarchive.h"
+
+#define ALIASES_FILE LIBLOCALEDIR "/locale.alias"
+#define ARCHIVE_FILE LIBLOCALEDIR "/locale-archive"
+#define ISO_CODES_DATADIR ISO_CODES_PREFIX "/share/xml/iso-codes"
+#define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
+
+typedef struct _GdmLocale {
+ char *id;
+ char *name;
+ char *language_code;
+ char *territory_code;
+ char *codeset;
+ char *modifier;
+} GdmLocale;
+
+static GHashTable *gdm_languages_map;
+static GHashTable *gdm_territories_map;
+static GHashTable *gdm_available_locales_map;
+
+static void
+gdm_locale_free (GdmLocale *locale)
+{
+ if (locale == NULL) {
+ return;
+ }
+
+ g_free (locale->id);
+ g_free (locale->name);
+ g_free (locale->codeset);
+ g_free (locale->modifier);
+ g_free (locale);
+}
+
+static char *
+normalize_codeset (const char *codeset)
+{
+ char *normalized_codeset;
+ const char *p;
+ char *q;
+
+ normalized_codeset = g_strdup (codeset);
+
+ if (codeset != NULL) {
+ for (p = codeset, q = normalized_codeset;
+ *p != '\0'; p++) {
+
+ if (*p == '-' || *p == '_') {
+ continue;
+ }
+
+ *q = g_ascii_tolower (*p);
+ q++;
+ }
+ *q = '\0';
+ }
+
+ return normalized_codeset;
+}
+
+/*
+ * According to http://en.wikipedia.org/wiki/Locale
+ * locale names are of the form:
+ * [language[_territory][ codeset][ modifier]]
+ */
+gboolean
+gdm_parse_language_name (const char *name,
+ char **language_codep,
+ char **territory_codep,
+ char **codesetp,
+ char **modifierp)
+{
+ GRegex *re;
+ GMatchInfo *match_info;
+ gboolean res;
+ GError *error;
+
+ error = NULL;
+ re = g_regex_new ("^(?P<language>[^_ [:space:]]+)"
+ "(_(?P<territory>[[:upper:]]+))?"
+ "(\\.(?P<codeset>[-_0-9a-zA-Z]+))?"
+ "(@(?P<modifier>[[:ascii:]]+))?$",
+ 0, 0, &error);
+ if (re == NULL) {
+ g_critical ("%s", error->message);
+ return FALSE;
+ }
+
+ if (!g_regex_match (re, name, 0, &match_info) ||
+ g_match_info_is_partial_match (match_info)) {
+ g_match_info_free (match_info);
+ g_regex_unref (re);
+ g_warning ("locale %s isn't valid\n", name);
+ return FALSE;
+ }
+
+ res = g_match_info_matches (match_info);
+ if (! res) {
+ g_match_info_free (match_info);
+ g_regex_unref (re);
+ g_warning ("Unable to parse locale: %s", name);
+ return FALSE;
+ }
+
+ if (language_codep != NULL) {
+ *language_codep = g_match_info_fetch_named (match_info, "language");
+ }
+
+ if (territory_codep != NULL) {
+ *territory_codep = g_match_info_fetch_named (match_info, "territory");
+
+ if (*territory_codep != NULL &&
+ *territory_codep[0] == '\0') {
+ g_free (*territory_codep);
+ *territory_codep = NULL;
+ }
+ }
+
+ if (codesetp != NULL) {
+ *codesetp = g_match_info_fetch_named (match_info, "codeset");
+
+ if (*codesetp != NULL &&
+ *codesetp[0] == '\0') {
+ g_free (*codesetp);
+ *codesetp = NULL;
+ }
+
+ if (*codesetp != NULL) {
+ char *codeset;
+
+ codeset = normalize_codeset (*codesetp);
+ g_free (*codesetp);
+ *codesetp = codeset;
+ }
+ }
+
+ if (modifierp != NULL) {
+ *modifierp = g_match_info_fetch_named (match_info, "modifier");
+
+ if (*modifierp != NULL &&
+ *modifierp[0] == '\0') {
+ g_free (*modifierp);
+ *modifierp = NULL;
+ }
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (re);
+
+ return TRUE;
+}
+
+static char *
+construct_language_name (const char *language,
+ const char *territory,
+ const char *codeset,
+ const char *modifier)
+{
+ char *name;
+
+ g_assert (language[0] != 0);
+ g_assert (territory == NULL || territory[0] != 0);
+ g_assert (codeset == NULL || codeset[0] != 0);
+ g_assert (modifier == NULL || modifier[0] != 0);
+
+ name = g_strdup_printf ("%s%s%s%s%s%s%s",
+ language,
+ territory != NULL? "_" : "",
+ territory != NULL? territory : "",
+ codeset != NULL? "." : "",
+ codeset != NULL? codeset : "",
+ modifier != NULL? "@" : "",
+ modifier != NULL? modifier : "");
+
+ return name;
+}
+
+static void
+make_codeset_canonical_for_locale (const char *name,
+ char **codeset)
+{
+ char *old_locale;
+
+ old_locale = setlocale (LC_CTYPE, NULL);
+
+ if (setlocale (LC_CTYPE, name) == NULL) {
+ return;
+ }
+
+ g_free (*codeset);
+ *codeset = g_strdup (nl_langinfo (CODESET));
+
+ setlocale (LC_CTYPE, old_locale);
+}
+
+char *
+gdm_normalize_language_name (const char *name)
+{
+ char *normalized_name;
+ char *language_code;
+ char *territory_code;
+ char *codeset;
+ char *modifier;
+
+ if (name[0] == '\0') {
+ return NULL;
+ }
+
+ gdm_parse_language_name (name,
+ &language_code,
+ &territory_code,
+ &codeset, &modifier);
+
+ if (codeset != NULL) {
+ make_codeset_canonical_for_locale (name, &codeset);
+ }
+
+ normalized_name = construct_language_name (language_code,
+ territory_code,
+ codeset, modifier);
+ g_free (language_code);
+ g_free (territory_code);
+ g_free (codeset);
+ g_free (modifier);
+
+ return normalized_name;
+}
+
+static gboolean
+language_name_is_valid (const char *language_name)
+{
+ char *old_locale;
+ gboolean is_valid;
+
+ old_locale = g_strdup (setlocale (LC_MESSAGES, NULL));
+ is_valid = setlocale (LC_MESSAGES, language_name) != NULL;
+ setlocale (LC_MESSAGES, old_locale);
+ g_free (old_locale);
+
+ return is_valid;
+}
+
+static gboolean
+language_name_is_utf8 (const char *language_name)
+{
+ char *old_locale;
+ char *codeset;
+ gboolean is_utf8;
+
+ old_locale = g_strdup (setlocale (LC_CTYPE, NULL));
+
+ if (setlocale (LC_CTYPE, language_name) == NULL) {
+ g_free (old_locale);
+ return FALSE;
+ }
+
+ codeset = normalize_codeset (nl_langinfo (CODESET));
+
+ is_utf8 = strcmp (codeset, "utf8") == 0;
+ g_free (codeset);
+
+ setlocale (LC_CTYPE, old_locale);
+ g_free (old_locale);
+
+ return is_utf8;
+}
+
+static gboolean
+language_name_has_translations (const char *language_name)
+{
+ GDir *dir;
+ char *path;
+ const char *name;
+ gboolean has_translations;
+
+ path = g_build_filename (GNOMELOCALEDIR, language_name, "LC_MESSAGES", NULL);
+
+ has_translations = FALSE;
+ dir = g_dir_open (path, 0, NULL);
+ g_free (path);
+
+ if (dir == NULL) {
+ goto out;
+ }
+
+ do {
+ name = g_dir_read_name (dir);
+
+ if (name == NULL) {
+ break;
+ }
+
+ if (g_str_has_suffix (name, ".mo")) {
+ has_translations = TRUE;
+ break;
+ }
+ } while (name != NULL);
+ g_dir_close (dir);
+
+out:
+ return has_translations;
+}
+
+static gboolean
+add_locale (const char *language_name)
+{
+ GdmLocale *locale;
+ GdmLocale *old_locale;
+ char *name;
+
+ if (language_name_is_utf8 (language_name)) {
+ name = g_strdup (language_name);
+ } else {
+ name = g_strdup_printf ("%s.utf8", language_name);
+
+ if (!language_name_is_utf8 (name)) {
+ g_free (name);
+ return FALSE;
+ }
+ }
+
+ if (!language_name_is_valid (name)) {
+ g_free (name);
+ return FALSE;
+ }
+
+
+ locale = g_new0 (GdmLocale, 1);
+ gdm_parse_language_name (name,
+ &locale->language_code,
+ &locale->territory_code,
+ &locale->codeset,
+ &locale->modifier);
+ g_free (name);
+ name = NULL;
+
+ locale->id = construct_language_name (locale->language_code, locale->territory_code,
+ NULL, locale->modifier);
+ locale->name = construct_language_name (locale->language_code, locale->territory_code,
+ locale->codeset, locale->modifier);
+
+ if (!language_name_has_translations (locale->name) &&
+ !language_name_has_translations (locale->id) &&
+ !language_name_has_translations (locale->language_code)) {
+ gdm_locale_free (locale);
+ return FALSE;
+ }
+
+ old_locale = g_hash_table_lookup (gdm_available_locales_map, locale->id);
+ if (old_locale != NULL) {
+ if (strlen (old_locale->name) > strlen (locale->name)) {
+ gdm_locale_free (locale);
+ return FALSE;
+ }
+ }
+
+ g_hash_table_insert (gdm_available_locales_map, g_strdup (locale->id), locale);
+
+ return TRUE;
+}
+
+struct nameent
+{
+ char *name;
+ uint32_t locrec_offset;
+};
+
+static gboolean
+collect_locales_from_archive (void)
+{
+ GMappedFile *mapped;
+ GError *error;
+ char *addr;
+ struct locarhead *head;
+ struct namehashent *namehashtab;
+ struct nameent *names;
+ uint32_t used;
+ uint32_t cnt;
+ gsize len;
+ gboolean locales_collected;
+
+ error = NULL;
+ mapped = g_mapped_file_new (ARCHIVE_FILE, FALSE, &error);
+ if (mapped == NULL) {
+ g_warning ("Mapping failed for %s: %s", ARCHIVE_FILE, error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ locales_collected = FALSE;
+
+ addr = g_mapped_file_get_contents (mapped);
+ len = g_mapped_file_get_length (mapped);
+
+ head = (struct locarhead *) addr;
+ if (head->namehash_offset + head->namehash_size > len
+ || head->string_offset + head->string_size > len
+ || head->locrectab_offset + head->locrectab_size > len
+ || head->sumhash_offset + head->sumhash_size > len) {
+ goto out;
+ }
+
+ namehashtab = (struct namehashent *) (addr + head->namehash_offset);
+
+ names = (struct nameent *) g_new0 (struct nameent, head->namehash_used);
+ for (cnt = used = 0; cnt < head->namehash_size; ++cnt) {
+ if (namehashtab[cnt].locrec_offset != 0) {
+ names[used].name = addr + namehashtab[cnt].name_offset;
+ names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
+ }
+ }
+
+ for (cnt = 0; cnt < used; ++cnt) {
+ add_locale (names[cnt].name);
+ }
+
+ g_free (names);
+
+ locales_collected = TRUE;
+ out:
+
+ g_mapped_file_unref (mapped);
+ return locales_collected;
+}
+
+static int
+select_dirs (const struct dirent *dirent)
+{
+ int result = 0;
+
+ if (strcmp (dirent->d_name, ".") != 0 && strcmp (dirent->d_name, "..") != 0) {
+ mode_t mode = 0;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK) {
+ mode = DTTOIF (dirent->d_type);
+ } else
+#endif
+ {
+ struct stat st;
+ char *path;
+
+ path = g_build_filename (LIBLOCALEDIR, dirent->d_name, NULL);
+ if (g_stat (path, &st) == 0) {
+ mode = st.st_mode;
+ }
+ g_free (path);
+ }
+
+ result = S_ISDIR (mode);
+ }
+
+ return result;
+}
+
+static void
+collect_locales_from_directory (void)
+{
+ struct dirent **dirents;
+ int ndirents;
+ int cnt;
+
+ ndirents = scandir (LIBLOCALEDIR, &dirents, select_dirs, alphasort);
+
+ for (cnt = 0; cnt < ndirents; ++cnt) {
+ add_locale (dirents[cnt]->d_name);
+ }
+
+ if (ndirents > 0) {
+ free (dirents);
+ }
+}
+
+static void
+collect_locales (void)
+{
+
+ if (gdm_available_locales_map == NULL) {
+ gdm_available_locales_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gdm_locale_free);
+ }
+
+ if (collect_locales_from_archive ()) {
+ return;
+ } else {
+ g_warning ("Could not read list of available locales from libc, "
+ "guessing possible locales from available translations, "
+ "but list may be incomplete!");
+
+ collect_locales_from_directory ();
+ }
+}
+
+static gboolean
+is_fallback_language (const char *code)
+{
+ const char *fallback_language_names[] = { "C", "POSIX", NULL };
+ int i;
+
+ for (i = 0; fallback_language_names[i] != NULL; i++) {
+ if (strcmp (code, fallback_language_names[i]) == 0) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static const char *
+get_language (const char *code)
+{
+ const char *name;
+ int len;
+
+ g_assert (code != NULL);
+
+ if (is_fallback_language (code)) {
+ return "Unspecified";
+ }
+
+ len = strlen (code);
+ if (len != 2 && len != 3) {
+ return NULL;
+ }
+
+ name = (const char *) g_hash_table_lookup (gdm_languages_map, code);
+
+ return name;
+}
+
+static char *
+get_first_item_in_semicolon_list (const char *list)
+{
+ char **items;
+ char *item;
+
+ /* Some entries in iso codes have multiple values, separated
+ * by semicolons. Not really sure which one to pick, so
+ * we just arbitrarily pick the first one.
+ */
+ items = g_strsplit (list, "; ", 2);
+
+ item = g_strdup (items[0]);
+ g_strfreev (items);
+
+ return item;
+}
+
+static char *
+get_translated_language (const char *code,
+ const char *locale)
+{
+ const char *language;
+ char *name;
+
+ language = get_language (code);
+
+ name = NULL;
+ if (language != NULL) {
+ const char *translated_name;
+ char *old_locale;
+
+ if (locale != NULL) {
+ old_locale = g_strdup (setlocale (LC_MESSAGES, NULL));
+ setlocale (LC_MESSAGES, locale);
+ }
+
+ if (is_fallback_language (code)) {
+ name = _("Unspecified");
+ } else {
+ translated_name = dgettext ("iso_639", language);
+ name = get_first_item_in_semicolon_list (translated_name);
+ }
+
+ if (locale != NULL) {
+ setlocale (LC_MESSAGES, old_locale);
+ g_free (old_locale);
+ }
+ }
+
+ return name;
+}
+
+static const char *
+get_territory (const char *code)
+{
+ const char *name;
+ int len;
+
+ g_assert (code != NULL);
+
+ len = strlen (code);
+ if (len != 2 && len != 3) {
+ return NULL;
+ }
+
+ name = (const char *) g_hash_table_lookup (gdm_territories_map, code);
+
+ return name;
+}
+
+static const char *
+get_translated_territory (const char *code,
+ const char *locale)
+{
+ const char *territory;
+ char *name;
+
+ territory = get_territory (code);
+
+ name = NULL;
+ if (territory != NULL) {
+ const char *translated_territory;
+ char *old_locale;
+
+ if (locale != NULL) {
+ old_locale = g_strdup (setlocale (LC_MESSAGES, NULL));
+ setlocale (LC_MESSAGES, locale);
+ }
+
+ translated_territory = dgettext ("iso_3166", territory);
+ name = get_first_item_in_semicolon_list (translated_territory);
+
+ if (locale != NULL) {
+ setlocale (LC_MESSAGES, old_locale);
+ g_free (old_locale);
+ }
+ }
+
+ return name;
+}
+
+static void
+languages_parse_start_tag (GMarkupParseContext *ctx,
+ const char *element_name,
+ const char **attr_names,
+ const char **attr_values,
+ gpointer user_data,
+ GError **error)
+{
+ const char *ccode_longB;
+ const char *ccode_longT;
+ const char *ccode;
+ const char *lang_name;
+
+ if (! g_str_equal (element_name, "iso_639_entry") || attr_names == NULL || attr_values == NULL) {
+ return;
+ }
+
+ ccode = NULL;
+ ccode_longB = NULL;
+ ccode_longT = NULL;
+ lang_name = NULL;
+
+ while (*attr_names && *attr_values) {
+ if (g_str_equal (*attr_names, "iso_639_1_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 2) {
+ return;
+ }
+ ccode = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "iso_639_2B_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 3) {
+ return;
+ }
+ ccode_longB = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "iso_639_2T_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 3) {
+ return;
+ }
+ ccode_longT = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "name")) {
+ lang_name = *attr_values;
+ }
+
+ ++attr_names;
+ ++attr_values;
+ }
+
+ if (lang_name == NULL) {
+ return;
+ }
+
+ if (ccode != NULL) {
+ g_hash_table_insert (gdm_languages_map,
+ g_strdup (ccode),
+ g_strdup (lang_name));
+ }
+ if (ccode_longB != NULL) {
+ g_hash_table_insert (gdm_languages_map,
+ g_strdup (ccode_longB),
+ g_strdup (lang_name));
+ }
+ if (ccode_longT != NULL) {
+ g_hash_table_insert (gdm_languages_map,
+ g_strdup (ccode_longT),
+ g_strdup (lang_name));
+ }
+}
+
+static void
+territories_parse_start_tag (GMarkupParseContext *ctx,
+ const char *element_name,
+ const char **attr_names,
+ const char **attr_values,
+ gpointer user_data,
+ GError **error)
+{
+ const char *acode_2;
+ const char *acode_3;
+ const char *ncode;
+ const char *territory_common_name;
+ const char *territory_name;
+
+ if (! g_str_equal (element_name, "iso_3166_entry") || attr_names == NULL || attr_values == NULL) {
+ return;
+ }
+
+ acode_2 = NULL;
+ acode_3 = NULL;
+ ncode = NULL;
+ territory_common_name = NULL;
+ territory_name = NULL;
+
+ while (*attr_names && *attr_values) {
+ if (g_str_equal (*attr_names, "alpha_2_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 2) {
+ return;
+ }
+ acode_2 = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "alpha_3_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 3) {
+ return;
+ }
+ acode_3 = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "numeric_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 3) {
+ return;
+ }
+ ncode = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "common_name")) {
+ /* skip if empty */
+ if (**attr_values) {
+ territory_common_name = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "name")) {
+ territory_name = *attr_values;
+ }
+
+ ++attr_names;
+ ++attr_values;
+ }
+
+ if (territory_common_name != NULL) {
+ territory_name = territory_common_name;
+ }
+
+ if (territory_name == NULL) {
+ return;
+ }
+
+ if (acode_2 != NULL) {
+ g_hash_table_insert (gdm_territories_map,
+ g_strdup (acode_2),
+ g_strdup (territory_name));
+ }
+ if (acode_3 != NULL) {
+ g_hash_table_insert (gdm_territories_map,
+ g_strdup (acode_3),
+ g_strdup (territory_name));
+ }
+ if (ncode != NULL) {
+ g_hash_table_insert (gdm_territories_map,
+ g_strdup (ncode),
+ g_strdup (territory_name));
+ }
+}
+
+static void
+languages_init (void)
+{
+ GError *error;
+ gboolean res;
+ char *buf;
+ gsize buf_len;
+
+ bindtextdomain ("iso_639", ISO_CODES_LOCALESDIR);
+ bind_textdomain_codeset ("iso_639", "UTF-8");
+
+ gdm_languages_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ error = NULL;
+ res = g_file_get_contents (ISO_CODES_DATADIR "/iso_639.xml",
+ &buf,
+ &buf_len,
+ &error);
+ if (res) {
+ GMarkupParseContext *ctx;
+ GMarkupParser parser = { languages_parse_start_tag, NULL, NULL, NULL, NULL };
+
+ ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
+
+ error = NULL;
+ res = g_markup_parse_context_parse (ctx, buf, buf_len, &error);
+
+ if (! res) {
+ g_warning ("Failed to parse '%s': %s\n",
+ ISO_CODES_DATADIR "/iso_639.xml",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_markup_parse_context_free (ctx);
+ g_free (buf);
+ } else {
+ g_warning ("Failed to load '%s': %s\n",
+ ISO_CODES_DATADIR "/iso_639.xml",
+ error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+territories_init (void)
+{
+ GError *error;
+ gboolean res;
+ char *buf;
+ gsize buf_len;
+
+ bindtextdomain ("iso_3166", ISO_CODES_LOCALESDIR);
+ bind_textdomain_codeset ("iso_3166", "UTF-8");
+
+ gdm_territories_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ error = NULL;
+ res = g_file_get_contents (ISO_CODES_DATADIR "/iso_3166.xml",
+ &buf,
+ &buf_len,
+ &error);
+ if (res) {
+ GMarkupParseContext *ctx;
+ GMarkupParser parser = { territories_parse_start_tag, NULL, NULL, NULL, NULL };
+
+ ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
+
+ error = NULL;
+ res = g_markup_parse_context_parse (ctx, buf, buf_len, &error);
+
+ if (! res) {
+ g_warning ("Failed to parse '%s': %s\n",
+ ISO_CODES_DATADIR "/iso_3166.xml",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_markup_parse_context_free (ctx);
+ g_free (buf);
+ } else {
+ g_warning ("Failed to load '%s': %s\n",
+ ISO_CODES_DATADIR "/iso_3166.xml",
+ error->message);
+ g_error_free (error);
+ }
+}
+
+char *
+gdm_get_language_from_name (const char *name,
+ const char *locale)
+{
+ char *full_language;
+ char *language_code;
+ char *territory_code;
+ const char *language;
+ const char *territory;
+
+ if (gdm_languages_map == NULL) {
+ languages_init ();
+ }
+
+ if (gdm_territories_map == NULL) {
+ territories_init ();
+ }
+
+ language_code = NULL;
+ territory_code = NULL;
+ full_language = NULL;
+
+ gdm_parse_language_name (name, &language_code, &territory_code,
+ NULL, NULL);
+
+ if (language_code == NULL) {
+ goto out;
+ }
+
+ language = get_translated_language (language_code, locale);
+
+ if (territory_code != NULL) {
+ territory = get_translated_territory (territory_code, locale);
+ } else {
+ territory = NULL;
+ }
+
+ if (territory != NULL) {
+ full_language = g_strdup_printf ("%s (%s)",
+ language ? language : "",
+ territory ? territory : "");
+ } else {
+ full_language = g_strdup (language);
+ }
+
+out:
+ g_free (language_code);
+ g_free (territory_code);
+
+ return full_language;
+}
+
+char **
+gdm_get_all_language_names (void)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ GPtrArray *array;
+
+ if (gdm_available_locales_map == NULL) {
+ collect_locales ();
+ }
+
+ array = g_ptr_array_new ();
+ g_hash_table_iter_init (&iter, gdm_available_locales_map);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ GdmLocale *locale;
+
+ locale = (GdmLocale *) value;
+
+ g_ptr_array_add (array, g_strdup (locale->name));
+ }
+ g_ptr_array_add (array, NULL);
+
+ return (char **) g_ptr_array_free (array, FALSE);
+}
diff --git a/panels/user-accounts/gdm-languages.h b/panels/user-accounts/gdm-languages.h
new file mode 100644
index 0000000..5b4560f
--- /dev/null
+++ b/panels/user-accounts/gdm-languages.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2008 Red Hat, Inc.
+ * Copyright 2007 William Jon McCann <mccann jhu edu>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Ray Strode
+ * William Jon McCann
+ */
+
+#ifndef __GDM_LANGUAGES_H
+#define __GDM_LANGUAGES_H
+
+G_BEGIN_DECLS
+
+char * gdm_get_language_from_name (const char *name,
+ const char *locale);
+char ** gdm_get_all_language_names (void);
+gboolean gdm_parse_language_name (const char *name,
+ char **language_codep,
+ char **territory_codep,
+ char **codesetp,
+ char **modifierp);
+char * gdm_normalize_language_name (const char *name);
+
+G_END_DECLS
+
+#endif /* __GDM_LANGUAGE_CHOOSER_WIDGET_H */
diff --git a/panels/user-accounts/locarchive.h b/panels/user-accounts/locarchive.h
new file mode 100644
index 0000000..f933f4d
--- /dev/null
+++ b/panels/user-accounts/locarchive.h
@@ -0,0 +1,97 @@
+/* Definitions for locale archive handling.
+ Copyright (C) 2002 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA. */
+
+#ifndef _LOCARCHIVE_H
+#define _LOCARCHIVE_H 1
+
+#include <stdint.h>
+
+#define AR_MAGIC 0xde020109
+
+struct locarhead
+{
+ uint32_t magic;
+ /* Serial number. */
+ uint32_t serial;
+ /* Name hash table. */
+ uint32_t namehash_offset;
+ uint32_t namehash_used;
+ uint32_t namehash_size;
+ /* String table. */
+ uint32_t string_offset;
+ uint32_t string_used;
+ uint32_t string_size;
+ /* Table with locale records. */
+ uint32_t locrectab_offset;
+ uint32_t locrectab_used;
+ uint32_t locrectab_size;
+ /* MD5 sum hash table. */
+ uint32_t sumhash_offset;
+ uint32_t sumhash_used;
+ uint32_t sumhash_size;
+};
+
+
+struct namehashent
+{
+ /* Hash value of the name. */
+ uint32_t hashval;
+ /* Offset of the name in the string table. */
+ uint32_t name_offset;
+ /* Offset of the locale record. */
+ uint32_t locrec_offset;
+};
+
+
+struct sumhashent
+{
+ /* MD5 sum. */
+ char sum[16];
+ /* Offset of the file in the archive. */
+ uint32_t file_offset;
+};
+
+struct locrecent
+{
+ uint32_t refs; /* # of namehashent records that point here */
+ struct
+ {
+ uint32_t offset;
+ uint32_t len;
+ } record[__LC_LAST];
+};
+
+
+struct locarhandle
+{
+ int fd;
+ void *addr;
+ size_t len;
+};
+
+
+/* In memory data for the locales with their checksums. */
+typedef struct locale_category_data
+{
+ off_t size;
+ void *addr;
+ char sum[16];
+} locale_data_t[__LC_LAST];
+
+#endif /* locarchive.h */
diff --git a/panels/user-accounts/run-passwd.c b/panels/user-accounts/run-passwd.c
new file mode 100644
index 0000000..6f7cfab
--- /dev/null
+++ b/panels/user-accounts/run-passwd.c
@@ -0,0 +1,772 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* run-passwd.c: this file is part of users-admin, a gnome-system-tools frontend
+ * for user administration.
+ *
+ * Copyright (C) 2002 Diego Gonzalez
+ * Copyright (C) 2006 Johannes H. Jensen
+ * Copyright (C) 2010 Milan Bouchet-Valat
+ *
+ * Written by: Diego Gonzalez <diego pemas net>
+ * Modified by: Johannes H. Jensen <joh deworks net>,
+ * Milan Bouchet-Valat <nalimilan club fr>.
+ *
+ * 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, 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Most of this code originally comes from gnome-about-me-password.c,
+ * from gnome-control-center.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#if __sun
+#include <sys/types.h>
+#include <signal.h>
+#endif
+
+#include "run-passwd.h"
+
+/* Passwd states */
+typedef enum {
+ PASSWD_STATE_NONE, /* Passwd is not asking for anything */
+ PASSWD_STATE_AUTH, /* Passwd is asking for our current password */
+ PASSWD_STATE_NEW, /* Passwd is asking for our new password */
+ PASSWD_STATE_RETYPE, /* Passwd is asking for our retyped new password */
+ PASSWD_STATE_DONE, /* Passwd succeeded but has not yet exited */
+ PASSWD_STATE_ERR /* Passwd reported an error but has not yet exited */
+} PasswdState;
+
+struct PasswdHandler {
+ const char *current_password;
+ const char *new_password;
+
+ /* Communication with the passwd program */
+ GPid backend_pid;
+
+ GIOChannel *backend_stdin;
+ GIOChannel *backend_stdout;
+
+ GQueue *backend_stdin_queue; /* Write queue to backend_stdin */
+
+ /* GMainLoop IDs */
+ guint backend_child_watch_id; /* g_child_watch_add (PID) */
+ guint backend_stdout_watch_id; /* g_io_add_watch (stdout) */
+
+ /* State of the passwd program */
+ PasswdState backend_state;
+ gboolean changing_password;
+
+ PasswdCallback auth_cb;
+ gpointer auth_cb_data;
+
+ PasswdCallback chpasswd_cb;
+ gpointer chpasswd_cb_data;
+};
+
+/* Buffer size for backend output */
+#define BUFSIZE 64
+
+
+static GQuark
+passwd_error_quark (void)
+{
+ static GQuark q = 0;
+
+ if (q == 0) {
+ q = g_quark_from_static_string("passwd_error");
+ }
+
+ return q;
+}
+
+/* Error handling */
+#define PASSWD_ERROR (passwd_error_quark ())
+
+
+static void
+stop_passwd (PasswdHandler *passwd_handler);
+
+static void
+free_passwd_resources (PasswdHandler *passwd_handler);
+
+static gboolean
+io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler);
+
+
+/*
+ * Spawning and closing of backend {{
+ */
+
+/* Child watcher */
+static void
+child_watch_cb (GPid pid, gint status, PasswdHandler *passwd_handler)
+{
+ if (WIFEXITED (status)) {
+ if (WEXITSTATUS (status) >= 255) {
+ g_warning ("Child exited unexpectedly");
+ }
+ if (WEXITSTATUS (status) == 0) {
+ if (passwd_handler->backend_state == PASSWD_STATE_RETYPE) {
+ passwd_handler->backend_state = PASSWD_STATE_DONE;
+ if (passwd_handler->chpasswd_cb)
+ passwd_handler->chpasswd_cb (passwd_handler,
+ NULL,
+ passwd_handler->auth_cb_data);
+ }
+ }
+ }
+
+ free_passwd_resources (passwd_handler);
+}
+
+static void
+ignore_sigpipe (gpointer data)
+{
+ signal (SIGPIPE, SIG_IGN);
+}
+
+/* Spawn passwd backend
+ * Returns: TRUE on success, FALSE otherwise and sets error appropriately */
+static gboolean
+spawn_passwd (PasswdHandler *passwd_handler, GError **error)
+{
+ gchar *argv[2];
+ gchar *envp[1];
+ gint my_stdin, my_stdout, my_stderr;
+
+ argv[0] = "/usr/bin/passwd"; /* Is it safe to rely on a hard-coded path? */
+ argv[1] = NULL;
+
+ envp[0] = NULL; /* If we pass an empty array as the environment,
+ * will the childs environment be empty, and the
+ * locales set to the C default? From the manual:
+ * "If envp is NULL, the child inherits its
+ * parent'senvironment."
+ * If I'm wrong here, we somehow have to set
+ * the locales here.
+ */
+
+ if (!g_spawn_async_with_pipes (NULL, /* Working directory */
+ argv, /* Argument vector */
+ envp, /* Environment */
+ G_SPAWN_DO_NOT_REAP_CHILD, /* Flags */
+ ignore_sigpipe, /* Child setup */
+ NULL, /* Data to child setup */
+ &passwd_handler->backend_pid, /* PID */
+ &my_stdin, /* Stdin */
+ &my_stdout, /* Stdout */
+ &my_stderr, /* Stderr */
+ error)) { /* GError */
+
+ /* An error occured */
+ free_passwd_resources (passwd_handler);
+
+ return FALSE;
+ }
+
+ /* 2>&1 */
+ if (dup2 (my_stderr, my_stdout) == -1) {
+ /* Failed! */
+ g_set_error_literal (error,
+ PASSWD_ERROR,
+ PASSWD_ERROR_BACKEND,
+ strerror (errno));
+
+ /* Clean up */
+ stop_passwd (passwd_handler);
+
+ return FALSE;
+ }
+
+ /* Open IO Channels */
+ passwd_handler->backend_stdin = g_io_channel_unix_new (my_stdin);
+ passwd_handler->backend_stdout = g_io_channel_unix_new (my_stdout);
+
+ /* Set raw encoding */
+ /* Set nonblocking mode */
+ if (g_io_channel_set_encoding (passwd_handler->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL ||
+ g_io_channel_set_encoding (passwd_handler->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL ||
+ g_io_channel_set_flags (passwd_handler->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ||
+ g_io_channel_set_flags (passwd_handler->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) {
+
+ /* Clean up */
+ stop_passwd (passwd_handler);
+ return FALSE;
+ }
+
+ /* Turn off buffering */
+ g_io_channel_set_buffered (passwd_handler->backend_stdin, FALSE);
+ g_io_channel_set_buffered (passwd_handler->backend_stdout, FALSE);
+
+ /* Add IO Channel watcher */
+ passwd_handler->backend_stdout_watch_id = g_io_add_watch (passwd_handler->backend_stdout,
+ G_IO_IN | G_IO_PRI,
+ (GIOFunc) io_watch_stdout, passwd_handler);
+
+ /* Add child watcher */
+ passwd_handler->backend_child_watch_id = g_child_watch_add (passwd_handler->backend_pid, (GChildWatchFunc) child_watch_cb, passwd_handler);
+
+ /* Success! */
+
+ return TRUE;
+}
+
+/* Stop passwd backend */
+static void
+stop_passwd (PasswdHandler *passwd_handler)
+{
+ /* This is the standard way of returning from the dialog with passwd.
+ * If we return this way we can safely kill passwd as it has completed
+ * its task.
+ */
+
+ if (passwd_handler->backend_pid != -1) {
+ kill (passwd_handler->backend_pid, 9);
+ }
+
+ /* We must run free_passwd_resources here and not let our child
+ * watcher do it, since it will access invalid memory after the
+ * dialog has been closed and cleaned up.
+ *
+ * If we had more than a single thread we'd need to remove
+ * the child watch before trying to kill the child.
+ */
+ free_passwd_resources (passwd_handler);
+}
+
+/* Clean up passwd resources */
+static void
+free_passwd_resources (PasswdHandler *passwd_handler)
+{
+ GError *error = NULL;
+
+ /* Remove the child watcher */
+ if (passwd_handler->backend_child_watch_id != 0) {
+
+ g_source_remove (passwd_handler->backend_child_watch_id);
+
+ passwd_handler->backend_child_watch_id = 0;
+ }
+
+
+ /* Close IO channels (internal file descriptors are automatically closed) */
+ if (passwd_handler->backend_stdin != NULL) {
+
+ if (g_io_channel_shutdown (passwd_handler->backend_stdin, TRUE, &error) != G_IO_STATUS_NORMAL) {
+ g_warning ("Could not shutdown backend_stdin IO channel: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ g_io_channel_unref (passwd_handler->backend_stdin);
+ passwd_handler->backend_stdin = NULL;
+ }
+
+ if (passwd_handler->backend_stdout != NULL) {
+
+ if (g_io_channel_shutdown (passwd_handler->backend_stdout, TRUE, &error) != G_IO_STATUS_NORMAL) {
+ g_warning ("Could not shutdown backend_stdout IO channel: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ g_io_channel_unref (passwd_handler->backend_stdout);
+
+ passwd_handler->backend_stdout = NULL;
+ }
+
+ /* Remove IO watcher */
+ if (passwd_handler->backend_stdout_watch_id != 0) {
+
+ g_source_remove (passwd_handler->backend_stdout_watch_id);
+
+ passwd_handler->backend_stdout_watch_id = 0;
+ }
+
+ /* Close PID */
+ if (passwd_handler->backend_pid != -1) {
+
+ g_spawn_close_pid (passwd_handler->backend_pid);
+
+ passwd_handler->backend_pid = -1;
+ }
+
+ /* Clear backend state */
+ passwd_handler->backend_state = PASSWD_STATE_NONE;
+}
+
+/*
+ * }} Spawning and closing of backend
+ */
+
+/*
+ * Backend communication code {{
+ */
+
+/* Write the first element of queue through channel */
+static void
+io_queue_pop (GQueue *queue, GIOChannel *channel)
+{
+ gchar *buf;
+ gsize bytes_written;
+ GError *error = NULL;
+
+ buf = g_queue_pop_head (queue);
+
+ if (buf != NULL) {
+
+ if (g_io_channel_write_chars (channel, buf, -1, &bytes_written, &error) != G_IO_STATUS_NORMAL) {
+ g_warning ("Could not write queue element \"%s\" to channel: %s", buf, error->message);
+ g_error_free (error);
+ }
+
+ /* Ensure passwords are cleared from memory */
+ memset (buf, 0, strlen (buf));
+ g_free (buf);
+ }
+}
+
+/* Goes through the argument list, checking if one of them occurs in str
+ * Returns: TRUE as soon as an element is found to match, FALSE otherwise */
+static gboolean
+is_string_complete (gchar *str, ...)
+{
+ va_list ap;
+ gchar *arg;
+
+ if (strlen (str) == 0) {
+ return FALSE;
+ }
+
+ va_start (ap, str);
+
+ while ((arg = va_arg (ap, char *)) != NULL) {
+ if (strstr (str, arg) != NULL) {
+ va_end (ap);
+ return TRUE;
+ }
+ }
+
+ va_end (ap);
+
+ return FALSE;
+}
+
+/*
+ * IO watcher for stdout, called whenever there is data to read from the backend.
+ * This is where most of the actual IO handling happens.
+ */
+static gboolean
+io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler)
+{
+ static GString *str = NULL; /* Persistent buffer */
+
+ gchar buf[BUFSIZE]; /* Temporary buffer */
+ gsize bytes_read;
+ GError *gio_error = NULL; /* Error returned by functions */
+ GError *error = NULL; /* Error sent to callbacks */
+
+ gboolean reinit = FALSE;
+
+ /* Initialize buffer */
+ if (str == NULL) {
+ str = g_string_new ("");
+ }
+
+ if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &gio_error)
+ != G_IO_STATUS_NORMAL) {
+ g_warning ("IO Channel read error: %s", gio_error->message);
+ g_error_free (gio_error);
+
+ return TRUE;
+ }
+
+ str = g_string_append_len (str, buf, bytes_read);
+
+ /* In which state is the backend? */
+ switch (passwd_handler->backend_state) {
+ case PASSWD_STATE_AUTH:
+ /* Passwd is asking for our current password */
+
+ if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) {
+
+ if (strstr (str->str, "assword: ") != NULL) {
+ /* Authentication successful */
+
+ passwd_handler->backend_state = PASSWD_STATE_NEW;
+
+ /* Trigger callback to update authentication status */
+ if (passwd_handler->auth_cb)
+ passwd_handler->auth_cb (passwd_handler,
+ NULL,
+ passwd_handler->auth_cb_data);
+
+ } else {
+ /* Authentication failed */
+
+ error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
+ _("Authentication failed"));
+
+ passwd_handler->changing_password = FALSE;
+
+ /* This error can happen both while authenticating or while changing password:
+ * if chpasswd_cb is set, this means we're already changing password */
+ if (passwd_handler->chpasswd_cb)
+ passwd_handler->chpasswd_cb (passwd_handler,
+ error,
+ passwd_handler->auth_cb_data);
+ else if (passwd_handler->auth_cb)
+ passwd_handler->auth_cb (passwd_handler,
+ error,
+ passwd_handler->auth_cb_data);
+
+ g_error_free (error);
+ }
+
+ reinit = TRUE;
+ }
+ break;
+ case PASSWD_STATE_NEW:
+ /* Passwd is asking for our new password */
+
+ if (is_string_complete (str->str, "assword: ", NULL)) {
+ /* Advance to next state */
+ passwd_handler->backend_state = PASSWD_STATE_RETYPE;
+
+ /* Pop retyped password from queue and into IO channel */
+ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+
+ reinit = TRUE;
+ }
+ break;
+ case PASSWD_STATE_RETYPE:
+ /* Passwd is asking for our retyped new password */
+
+ if (is_string_complete (str->str,
+ "successfully",
+ "short",
+ "longer",
+ "palindrome",
+ "dictionary",
+ "simple",
+ "simplistic",
+ "similar",
+ "case",
+ "different",
+ "wrapped",
+ "recovered",
+ "recent",
+ "unchanged",
+ "match",
+ "1 numeric or special",
+ "failure",
+ "DIFFERENT",
+ "BAD PASSWORD",
+ NULL)) {
+
+ if (strstr (str->str, "successfully") != NULL) {
+ /* Hooray! */
+
+ passwd_handler->backend_state = PASSWD_STATE_DONE;
+ /* Trigger callback to update status */
+ if (passwd_handler->chpasswd_cb)
+ passwd_handler->chpasswd_cb (passwd_handler,
+ NULL,
+ passwd_handler->chpasswd_cb_data);
+ }
+ else {
+ /* Ohnoes! */
+
+ if (strstr (str->str, "recovered") != NULL) {
+ /* What does this indicate?
+ * "Authentication information cannot be recovered?" from libpam? */
+ error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
+ str->str);
+ } else if (strstr (str->str, "short") != NULL ||
+ strstr (str->str, "longer") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password is too short"));
+ } else if (strstr (str->str, "palindrome") != NULL ||
+ strstr (str->str, "simple") != NULL ||
+ strstr (str->str, "simplistic") != NULL ||
+ strstr (str->str, "dictionary") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password is too simple"));
+ } else if (strstr (str->str, "similar") != NULL ||
+ strstr (str->str, "different") != NULL ||
+ strstr (str->str, "case") != NULL ||
+ strstr (str->str, "wrapped") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The old and new passwords are too similar"));
+ } else if (strstr (str->str, "recent") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password has already been used recently."));
+ } else if (strstr (str->str, "1 numeric or special") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password must contain numeric or special characters"));
+ } else if (strstr (str->str, "unchanged") != NULL ||
+ strstr (str->str, "match") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The old and new passwords are the same"));
+ } else if (strstr (str->str, "failure") != NULL) {
+ /* Authentication failure */
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
+ _("Your password has been changed since you initially authenticated!"));
+ }
+ else if (strstr (str->str, "DIFFERENT")) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password does not contain enough different characters"));
+ }
+ else {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
+ _("Unknown error"));
+ }
+
+ /* At this point, passwd might have exited, in which case
+ * child_watch_cb should clean up for us and remove this watcher.
+ * On some error conditions though, passwd just re-prompts us
+ * for our new password. */
+ passwd_handler->backend_state = PASSWD_STATE_ERR;
+
+ passwd_handler->changing_password = FALSE;
+
+ /* Trigger callback to update status */
+ if (passwd_handler->chpasswd_cb)
+ passwd_handler->chpasswd_cb (passwd_handler,
+ error,
+ passwd_handler->chpasswd_cb_data);
+
+ g_error_free (error);
+
+ }
+
+ reinit = TRUE;
+
+ /* child_watch_cb should clean up for us now */
+ }
+ break;
+ case PASSWD_STATE_NONE:
+ /* Passwd is not asking for anything yet */
+ if (is_string_complete (str->str, "assword: ", NULL)) {
+
+ /* If the user does not have a password set,
+ * passwd will immediately ask for the new password,
+ * so skip the AUTH phase */
+ if (is_string_complete (str->str, "new", "New", NULL)) {
+ gchar *pw;
+
+ passwd_handler->backend_state = PASSWD_STATE_NEW;
+
+ /* since passwd didn't ask for our old password
+ * in this case, simply remove it from the queue */
+ pw = g_queue_pop_head (passwd_handler->backend_stdin_queue);
+ g_free (pw);
+
+ /* Pop the IO queue, i.e. send new password */
+ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+
+ } else {
+
+ passwd_handler->backend_state = PASSWD_STATE_AUTH;
+
+ /* Pop the IO queue, i.e. send current password */
+ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+ }
+
+ reinit = TRUE;
+ }
+ break;
+ default:
+ /* Passwd has returned an error */
+ reinit = TRUE;
+ break;
+ }
+
+ if (reinit) {
+ g_string_free (str, TRUE);
+ str = NULL;
+ }
+
+ /* Continue calling us */
+ return TRUE;
+}
+
+/*
+ * }} Backend communication code
+ */
+
+/* Adds the current password to the IO queue */
+static void
+authenticate (PasswdHandler *passwd_handler)
+{
+ gchar *s;
+
+ s = g_strdup_printf ("%s\n", passwd_handler->current_password);
+
+ g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
+}
+
+/* Adds the new password twice to the IO queue */
+static void
+update_password (PasswdHandler *passwd_handler)
+{
+ gchar *s;
+
+ s = g_strdup_printf ("%s\n", passwd_handler->new_password);
+
+ g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
+ /* We need to allocate new space because io_queue_pop() g_free()s
+ * every element of the queue after it's done */
+ g_queue_push_tail (passwd_handler->backend_stdin_queue, g_strdup (s));
+}
+
+
+PasswdHandler *
+passwd_init (void)
+{
+ PasswdHandler *passwd_handler;
+
+ passwd_handler = g_new0 (PasswdHandler, 1);
+
+ /* Initialize backend_pid. -1 means the backend is not running */
+ passwd_handler->backend_pid = -1;
+
+ /* Initialize IO Channels */
+ passwd_handler->backend_stdin = NULL;
+ passwd_handler->backend_stdout = NULL;
+
+ /* Initialize write queue */
+ passwd_handler->backend_stdin_queue = g_queue_new ();
+
+ /* Initialize watchers */
+ passwd_handler->backend_child_watch_id = 0;
+ passwd_handler->backend_stdout_watch_id = 0;
+
+ /* Initialize backend state */
+ passwd_handler->backend_state = PASSWD_STATE_NONE;
+ passwd_handler->changing_password = FALSE;
+
+ return passwd_handler;
+}
+
+void
+passwd_destroy (PasswdHandler *passwd_handler)
+{
+ g_queue_free (passwd_handler->backend_stdin_queue);
+ stop_passwd (passwd_handler);
+ g_free (passwd_handler);
+}
+
+void
+passwd_authenticate (PasswdHandler *passwd_handler,
+ const char *current_password,
+ PasswdCallback cb,
+ const gpointer user_data)
+{
+ GError *error = NULL;
+
+ /* Don't stop if we've already started chaging password */
+ if (passwd_handler->changing_password)
+ return;
+
+ /* Clear data from possible previous attempts to change password */
+ passwd_handler->new_password = NULL;
+ passwd_handler->chpasswd_cb = NULL;
+ passwd_handler->chpasswd_cb_data = NULL;
+ g_queue_foreach (passwd_handler->backend_stdin_queue, (GFunc) g_free, NULL);
+ g_queue_clear (passwd_handler->backend_stdin_queue);
+
+ passwd_handler->current_password = current_password;
+ passwd_handler->auth_cb = cb;
+ passwd_handler->auth_cb_data = user_data;
+
+ /* Spawn backend */
+ stop_passwd (passwd_handler);
+
+ if (!spawn_passwd (passwd_handler, &error)) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+
+ return;
+ }
+
+ authenticate (passwd_handler);
+
+ /* Our IO watcher should now handle the rest */
+}
+
+gboolean
+passwd_change_password (PasswdHandler *passwd_handler,
+ const char *new_password,
+ PasswdCallback cb,
+ const gpointer user_data)
+{
+ GError *error = NULL;
+
+ passwd_handler->changing_password = TRUE;
+
+ passwd_handler->new_password = new_password;
+ passwd_handler->chpasswd_cb = cb;
+ passwd_handler->chpasswd_cb_data = user_data;
+
+ /* Stop passwd if an error occured and it is still running */
+ if (passwd_handler->backend_state == PASSWD_STATE_ERR) {
+
+ /* Stop passwd, free resources */
+ stop_passwd (passwd_handler);
+ }
+
+ /* Check that the backend is still running, or that an error
+ * has occured but it has not yet exited */
+ if (passwd_handler->backend_pid == -1) {
+ /* If it is not, re-run authentication */
+
+ /* Spawn backend */
+ stop_passwd (passwd_handler);
+
+ if (!spawn_passwd (passwd_handler, &error)) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+
+ return FALSE;
+ }
+
+ /* Add current and new passwords to queue */
+ authenticate (passwd_handler);
+ update_password (passwd_handler);
+ } else {
+ /* Only add new passwords to queue */
+ update_password (passwd_handler);
+ }
+
+ /* Pop new password through the backend.
+ * If user has no password, popping the queue would output current
+ * password, while 'passwd' is waiting for the new one. So wait for
+ * io_watch_stdout() to remove current password from the queue,
+ * and output the new one for us.
+ */
+ if (passwd_handler->current_password)
+ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+
+ /* Our IO watcher should now handle the rest */
+
+ return TRUE;
+}
diff --git a/panels/user-accounts/run-passwd.h b/panels/user-accounts/run-passwd.h
new file mode 100644
index 0000000..2b07185
--- /dev/null
+++ b/panels/user-accounts/run-passwd.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* run-passwd.h: this file is part of users-admin, a gnome-system-tools frontend
+ * for user administration.
+ *
+ * Copyright (C) 2010 Milan Bouchet-Valat
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: Milan Bouchet-Valat <nalimilan club fr>
+ */
+
+#ifndef _RUN_PASSWD_H
+#define _RUN_PASSWD_H
+
+struct PasswdHandler;
+
+typedef struct PasswdHandler PasswdHandler;
+
+typedef void (*PasswdCallback) (PasswdHandler *passwd_handler, GError *error, const gpointer user_data);
+
+/* Error codes */
+typedef enum {
+ PASSWD_ERROR_REJECTED, /* New password is not secure enough */
+ PASSWD_ERROR_AUTH_FAILED, /* Wrong old password, or PAM failure */
+ PASSWD_ERROR_REAUTH_FAILED, /* Password has changed since first authentication */
+ PASSWD_ERROR_BACKEND, /* Backend error */
+ PASSWD_ERROR_UNKNOWN /* General error */
+} PasswdError;
+
+
+PasswdHandler *passwd_init (void);
+
+void passwd_destroy (PasswdHandler *passwd_handler);
+
+void passwd_authenticate (PasswdHandler *passwd_handler,
+ const char *current_password,
+ PasswdCallback cb,
+ gpointer user_data);
+
+gboolean passwd_change_password (PasswdHandler *passwd_handler,
+ const char *new_password,
+ PasswdCallback cb,
+ const gpointer user_data);
+
+#endif /* _RUN_PASSWD_H */
+
diff --git a/panels/user-accounts/um-account-dialog.c b/panels/user-accounts/um-account-dialog.c
new file mode 100644
index 0000000..93e10b5
--- /dev/null
+++ b/panels/user-accounts/um-account-dialog.c
@@ -0,0 +1,493 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <utmp.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "um-account-dialog.h"
+#include "um-user-manager.h"
+#include "um-utils.h"
+
+#define MAXNAMELEN (UT_NAMESIZE - 1)
+
+struct _UmAccountDialog {
+ GtkWidget *dialog;
+ GtkWidget *username_combo;
+ GtkWidget *name_entry;
+ GtkWidget *account_type_combo;
+ GtkWidget *ok_button;
+
+ gboolean valid_name;
+ gboolean valid_username;
+
+ UserCreatedCallback user_created_callback;
+ gpointer user_created_data;
+};
+
+static void
+cancel_account_dialog (GtkButton *button,
+ UmAccountDialog *um)
+{
+ gtk_widget_hide (um->dialog);
+}
+
+static void
+create_user_done (UmUserManager *manager,
+ GAsyncResult *res,
+ UmAccountDialog *um)
+{
+ UmUser *user;
+ GError *error;
+
+ error = NULL;
+ if (!um_user_manager_create_user_finish (manager, res, &user, &error)) {
+
+ if (!g_error_matches (error, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_PERMISSION_DENIED)) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (gtk_window_get_transient_for (GTK_WINDOW (um->dialog)),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("Failed to create user"));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", error->message);
+
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+ gtk_window_present (GTK_WINDOW (dialog));
+ }
+ g_error_free (error);
+ }
+ else {
+ um->user_created_callback (user, um->user_created_data);
+ }
+}
+
+static void
+accept_account_dialog (GtkButton *button,
+ UmAccountDialog *um)
+{
+ UmUserManager *manager;
+ const gchar *username;
+ const gchar *name;
+ gint account_type;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ name = gtk_entry_get_text (GTK_ENTRY (um->name_entry));
+ username = gtk_combo_box_get_active_text (GTK_COMBO_BOX (um->username_combo));
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->account_type_combo));
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (um->account_type_combo), &iter);
+ gtk_tree_model_get (model, &iter, 1, &account_type, -1);
+
+ manager = um_user_manager_ref_default ();
+ um_user_manager_create_user (manager,
+ username,
+ name,
+ account_type,
+ (GAsyncReadyCallback)create_user_done,
+ um,
+ NULL);
+ g_object_unref (manager);
+
+ gtk_widget_hide (um->dialog);
+}
+
+static gboolean
+is_username_used (const gchar *username)
+{
+ struct passwd *pwent;
+
+ pwent = getpwnam (username);
+
+ return pwent != NULL;
+}
+
+static void
+username_changed (GtkComboBox *combo,
+ UmAccountDialog *um)
+{
+ gboolean in_use;
+ gboolean empty;
+ gboolean valid;
+ gboolean toolong;
+ const gchar *username;
+ const gchar *c;
+ gchar *tip;
+ GtkWidget *entry;
+
+ username = gtk_combo_box_get_active_text (combo);
+
+ in_use = is_username_used (username);
+ empty = username[0] == 0;
+ toolong = strlen (username) > MAXNAMELEN;
+ valid = TRUE;
+
+ if (!in_use && !empty && !toolong) {
+ /* First char must be a letter, and it must only composed
+ * of ASCII letters, digits, and a '.', '-', '_'
+ */
+ for (c = username; *c; c++) {
+ if (! ((*c >= 'a' && *c <= 'z') ||
+ (*c >= 'A' && *c <= 'Z') ||
+ (*c >= '0' && *c <= '9') ||
+ (*c == '_') || (*c == '.') ||
+ (*c == '-' && c != username)))
+ valid = FALSE;
+ }
+ }
+
+ um->valid_username = !empty && !in_use && !toolong && valid;
+ gtk_widget_set_sensitive (um->ok_button, um->valid_name && um->valid_username);
+
+ entry = gtk_bin_get_child (GTK_BIN (combo));
+
+ if (!empty && (in_use || toolong || !valid)) {
+ if (in_use) {
+ tip = g_strdup_printf (_("A user with the username '%s' already exists"),
+ username);
+ }
+ else if (toolong) {
+ tip = g_strdup_printf (_("The username is too long"));
+ }
+ else if (username[0] == '-') {
+ tip = g_strdup (_("The username cannot start with a '-'"));
+ }
+ else {
+ tip = g_strdup (_("The username must consist of:\n"
+ " \xe2\x9e\xa3 letters from the English alphabet\n"
+ " \xe2\x9e\xa3 digits\n"
+ " \xe2\x9e\xa3 any of the characters '.', '-' and '_'"));
+ }
+
+ set_entry_validation_error (GTK_ENTRY (entry), tip);
+
+ g_free (tip);
+ }
+ else {
+ clear_entry_validation_error (GTK_ENTRY (entry));
+ }
+}
+
+static void
+name_changed (GtkEntry *name_entry,
+ GParamSpec *pspec,
+ UmAccountDialog *um)
+{
+ GtkWidget *entry;
+ GtkTreeModel *model;
+ gboolean in_use;
+ const char *name;
+ char *lc_name, *ascii_name, *stripped_name;
+ char **words1;
+ char **words2 = NULL;
+ char **w1, **w2;
+ char *c;
+ char *unicode_fallback = "?";
+ GString *first_word, *last_word;
+ GString *item1, *item2, *item3, *item4;
+ int len;
+ int nwords1, nwords2, i;
+ GHashTable *items;
+
+ entry = gtk_bin_get_child (GTK_BIN (um->username_combo));
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->username_combo));
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+
+ name = gtk_entry_get_text (GTK_ENTRY (name_entry));
+
+ um->valid_name = (strlen (name) > 0);
+ gtk_widget_set_sensitive (um->ok_button, um->valid_name && um->valid_username);
+
+ if (!um->valid_name) {
+ gtk_entry_set_text (GTK_ENTRY (entry), "");
+ return;
+ }
+
+ ascii_name = g_convert_with_fallback (name, -1, "ASCII//TRANSLIT", "UTF-8",
+ unicode_fallback, NULL, NULL, NULL);
+
+ lc_name = g_ascii_strdown (ascii_name, -1);
+
+ /* remove all non ASCII alphanumeric chars from the name,
+ * apart from the few allowed symbols
+ */
+ stripped_name = g_strnfill (strlen (lc_name) + 1, '\0');
+ i = 0;
+ for (c = lc_name; *c; c++) {
+ if (!(g_ascii_isdigit (*c) || g_ascii_islower (*c) ||
+ *c == ' ' || *c == '-' || *c == '.' || *c == '_' ||
+ /* used to track invalid words, removed below */
+ *c == '?') )
+ continue;
+
+ stripped_name[i] = *c;
+ i++;
+ }
+
+ if (strlen (stripped_name) == 0) {
+ g_free (ascii_name);
+ g_free (lc_name);
+ g_free (stripped_name);
+ return;
+ }
+
+ /* we split name on spaces, and then on dashes, so that we can treat
+ * words linked with dashes the same way, i.e. both fully shown, or
+ * both abbreviated
+ */
+ words1 = g_strsplit_set (stripped_name, " ", -1);
+ len = g_strv_length (words1);
+
+ g_free (ascii_name);
+ g_free (lc_name);
+ g_free (stripped_name);
+
+ /* Concatenate the whole first word with the first letter of each
+ * word (item1), and the last word with the first letter of each
+ * word (item2). item3 and item4 are symmetrical respectively to
+ * item1 and item2.
+ *
+ * Constant 5 is the max reasonable number of words we may get when
+ * splitting on dashes, since we can't guess it at this point,
+ * and reallocating would be too bad.
+ */
+ item1 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5);
+ item3 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5);
+
+ item2 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5);
+ item4 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5);
+
+ /* again, guess at the max size of names */
+ first_word = g_string_sized_new (20);
+ last_word = g_string_sized_new (20);
+
+ nwords1 = 0;
+ nwords2 = 0;
+ for (w1 = words1; *w1; w1++) {
+ if (strlen (*w1) == 0)
+ continue;
+
+ /* skip words with string '?', most likely resulting
+ * from failed transliteration to ASCII
+ */
+ if (strstr (*w1, unicode_fallback) != NULL)
+ continue;
+
+ nwords1++; /* count real words, excluding empty string */
+
+ words2 = g_strsplit_set (*w1, "-", -1);
+ /* reset last word if a new non-empty word has been found */
+ if (strlen (*words2) > 0)
+ last_word = g_string_set_size (last_word, 0);
+
+ for (w2 = words2; *w2; w2++) {
+ if (strlen (*w2) == 0)
+ continue;
+
+ nwords2++;
+
+ /* part of the first "toplevel" real word */
+ if (nwords1 == 1) {
+ item1 = g_string_append (item1, *w2);
+ first_word = g_string_append (first_word, *w2);
+ }
+ else {
+ item1 = g_string_append_unichar (item1,
+ g_utf8_get_char (*w2));
+ item3 = g_string_append_unichar (item3,
+ g_utf8_get_char (*w2));
+ }
+
+ /* not part of the last "toplevel" word */
+ if (w1 != words1 + len - 1) {
+ item2 = g_string_append_unichar (item2,
+ g_utf8_get_char (*w2));
+ item4 = g_string_append_unichar (item4,
+ g_utf8_get_char (*w2));
+ }
+
+ /* always save current word so that we have it if last one reveals empty */
+ last_word = g_string_append (last_word, *w2);
+ }
+
+ g_strfreev (words2);
+ }
+ item2 = g_string_append (item2, last_word->str);
+ item3 = g_string_append (item3, first_word->str);
+ item4 = g_string_prepend (item4, last_word->str);
+
+ items = g_hash_table_new (g_str_hash, g_str_equal);
+
+ in_use = is_username_used (item1->str);
+ if (nwords2 > 0 && !in_use && !g_ascii_isdigit (item1->str[0])) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), item1->str);
+ g_hash_table_insert (items, item1->str, item1->str);
+ }
+
+ /* if there's only one word, would be the same as item1 */
+ if (nwords2 > 1) {
+ /* add other items */
+ in_use = is_username_used (item2->str);
+ if (!in_use && !g_ascii_isdigit (item2->str[0]) &&
+ !g_hash_table_lookup (items, item2->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), item2->str);
+ g_hash_table_insert (items, item2->str, item2->str);
+ }
+
+ in_use = is_username_used (item3->str);
+ if (!in_use && !g_ascii_isdigit (item3->str[0]) &&
+ !g_hash_table_lookup (items, item3->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), item3->str);
+ g_hash_table_insert (items, item3->str, item3->str);
+ }
+
+ in_use = is_username_used (item4->str);
+ if (!in_use && !g_ascii_isdigit (item4->str[0]) &&
+ !g_hash_table_lookup (items, item4->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), item4->str);
+ g_hash_table_insert (items, item4->str, item4->str);
+ }
+
+ /* add the last word */
+ in_use = is_username_used (last_word->str);
+ if (!in_use && !g_ascii_isdigit (last_word->str[0]) &&
+ !g_hash_table_lookup (items, last_word->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), last_word->str);
+ g_hash_table_insert (items, last_word->str, last_word->str);
+ }
+
+ /* ...and the first one */
+ in_use = is_username_used (first_word->str);
+ if (!in_use && !g_ascii_isdigit (first_word->str[0]) &&
+ !g_hash_table_lookup (items, first_word->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), first_word->str);
+ g_hash_table_insert (items, first_word->str, first_word->str);
+ }
+ }
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (um->username_combo), 0);
+ g_hash_table_destroy (items);
+ g_strfreev (words1);
+ g_string_free (first_word, TRUE);
+ g_string_free (last_word, TRUE);
+ g_string_free (item1, TRUE);
+ g_string_free (item2, TRUE);
+ g_string_free (item3, TRUE);
+ g_string_free (item4, TRUE);
+}
+
+UmAccountDialog *
+um_account_dialog_new (void)
+{
+ GtkBuilder *builder;
+ GtkWidget *widget;
+ UmAccountDialog *um;
+ const gchar *filename;
+ GError *error = NULL;
+
+ builder = gtk_builder_new ();
+
+ filename = UIDIR "/account-dialog.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ filename = "../data/account-dialog.ui";
+ if (!gtk_builder_add_from_file (builder, filename, &error)) {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ um = g_new0 (UmAccountDialog, 1);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dialog");
+ g_signal_connect (widget, "delete-event",
+ G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+ um->dialog = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "cancel-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (cancel_account_dialog), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (accept_account_dialog), um);
+ gtk_widget_grab_default (widget);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "username-combo");
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (username_changed), um);
+ um->username_combo = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "name-entry");
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (name_changed), um);
+ um->name_entry = widget;
+
+ um->ok_button = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "account-type-combo");
+ um->account_type_combo = widget;
+
+ return um;
+}
+
+void
+um_account_dialog_free (UmAccountDialog *um)
+{
+ gtk_widget_destroy (um->dialog);
+ g_free (um);
+}
+
+void
+um_account_dialog_show (UmAccountDialog *um,
+ GtkWindow *parent,
+ UserCreatedCallback user_created_callback,
+ gpointer user_created_data)
+{
+ GtkTreeModel *model;
+
+ gtk_entry_set_text (GTK_ENTRY (um->name_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (um->username_combo))), "");
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->username_combo));
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+ gtk_combo_box_set_active (GTK_COMBO_BOX (um->account_type_combo), 0);
+
+ gtk_window_set_transient_for (GTK_WINDOW (um->dialog), parent);
+ gtk_window_present (GTK_WINDOW (um->dialog));
+ gtk_widget_grab_focus (um->name_entry);
+
+ um->valid_name = um->valid_username = TRUE;
+
+ um->user_created_callback = user_created_callback;
+ um->user_created_data = user_created_data;
+}
+
+
diff --git a/panels/user-accounts/um-account-dialog.h b/panels/user-accounts/um-account-dialog.h
new file mode 100644
index 0000000..e3b2ebc
--- /dev/null
+++ b/panels/user-accounts/um-account-dialog.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __UM_ACCOUNT_DIALOG_H__
+#define __UM_ACCOUNT_DIALOG_H__
+
+#include <gtk/gtk.h>
+#include "um-user.h"
+
+G_BEGIN_DECLS
+
+typedef struct _UmAccountDialog UmAccountDialog;
+
+typedef void (*UserCreatedCallback) (UmUser *user, gpointer data);
+
+UmAccountDialog *um_account_dialog_new (void);
+void um_account_dialog_free (UmAccountDialog *dialog);
+void um_account_dialog_show (UmAccountDialog *dialog,
+ GtkWindow *parent,
+ UserCreatedCallback user_created,
+ gpointer data);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-account-type.c b/panels/user-accounts/um-account-type.c
new file mode 100644
index 0000000..55064a8
--- /dev/null
+++ b/panels/user-accounts/um-account-type.c
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+
+#include "um-account-type.h"
+
+const gchar *
+um_account_type_get_name (UmAccountType account_type)
+{
+ switch (account_type) {
+ case UM_ACCOUNT_TYPE_STANDARD:
+ return C_("Account type", "Standard");
+ case UM_ACCOUNT_TYPE_ADMINISTRATOR:
+ return C_("Account type", "Administrator");
+ case UM_ACCOUNT_TYPE_SUPERVISED:
+ return C_("Account type", "Supervised");
+ default:
+ g_assert_not_reached ();
+ }
+}
diff --git a/panels/user-accounts/um-account-type.h b/panels/user-accounts/um-account-type.h
new file mode 100644
index 0000000..1e42e00
--- /dev/null
+++ b/panels/user-accounts/um-account-type.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __UM_ACCOUNT_TYPE__
+#define __UM_ACCOUNT_TYPE__
+
+G_BEGIN_DECLS
+
+typedef enum {
+ UM_ACCOUNT_TYPE_STANDARD,
+ UM_ACCOUNT_TYPE_ADMINISTRATOR,
+ UM_ACCOUNT_TYPE_SUPERVISED
+} UmAccountType;
+
+const gchar *um_account_type_get_name (UmAccountType account_type);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-crop-area.c b/panels/user-accounts/um-crop-area.c
new file mode 100644
index 0000000..4b6bacb
--- /dev/null
+++ b/panels/user-accounts/um-crop-area.c
@@ -0,0 +1,817 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "um-crop-area.h"
+
+struct _UmCropAreaPrivate {
+ GdkPixbuf *browse_pixbuf;
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *color_shifted;
+ gdouble scale;
+ GdkRectangle image;
+ GdkCursorType current_cursor;
+ GdkRectangle crop;
+ gint active_region;
+ gint last_press_x;
+ gint last_press_y;
+ gint base_width;
+ gint base_height;
+ gdouble aspect;
+};
+
+G_DEFINE_TYPE (UmCropArea, um_crop_area, GTK_TYPE_DRAWING_AREA);
+
+static inline guchar
+shift_color_byte (guchar b,
+ int shift)
+{
+ return CLAMP(b + shift, 0, 255);
+}
+
+static void
+shift_colors (GdkPixbuf *pixbuf,
+ gint red,
+ gint green,
+ gint blue,
+ gint alpha)
+{
+ gint x, y, offset, y_offset, rowstride, width, height;
+ guchar *pixels;
+ gint channels;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+ for (y = 0; y < height; y++) {
+ y_offset = y * rowstride;
+ for (x = 0; x < width; x++) {
+ offset = y_offset + x * channels;
+ if (red != 0)
+ pixels[offset] = shift_color_byte (pixels[offset], red);
+ if (green != 0)
+ pixels[offset + 1] = shift_color_byte (pixels[offset + 1], green);
+ if (blue != 0)
+ pixels[offset + 2] = shift_color_byte (pixels[offset + 2], blue);
+ if (alpha != 0 && channels >= 4)
+ pixels[offset + 3] = shift_color_byte (pixels[offset + 3], blue);
+ }
+ }
+}
+
+static void
+update_pixbufs (UmCropArea *area)
+{
+ gint width;
+ gint height;
+ GtkAllocation allocation;
+ gdouble scale;
+ GdkColor *color;
+ guint32 pixel;
+ gint dest_x, dest_y, dest_width, dest_height;
+ GtkWidget *widget;
+ GtkStyle *style;
+
+ widget = GTK_WIDGET (area);
+ gtk_widget_get_allocation (widget, &allocation);
+ style = gtk_widget_get_style (widget);
+
+ if (area->priv->pixbuf == NULL ||
+ gdk_pixbuf_get_width (area->priv->pixbuf) != allocation.width ||
+ gdk_pixbuf_get_height (area->priv->pixbuf) != allocation.height) {
+ if (area->priv->pixbuf != NULL)
+ g_object_unref (area->priv->pixbuf);
+ area->priv->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ gdk_pixbuf_get_has_alpha (area->priv->browse_pixbuf),
+ 8,
+ allocation.width, allocation.height);
+
+ color = &style->bg[gtk_widget_get_state (widget)];
+ pixel = ((color->red & 0xff00) << 16) |
+ ((color->green & 0xff00) << 8) |
+ (color->blue & 0xff00);
+ gdk_pixbuf_fill (area->priv->pixbuf, pixel);
+
+ width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+ height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+
+ scale = allocation.height / (gdouble)height;
+ if (scale * width > allocation.width)
+ scale = allocation.width / (gdouble)width;
+
+ dest_width = width * scale;
+ dest_height = height * scale;
+ dest_x = (allocation.width - dest_width) / 2;
+ dest_y = (allocation.height - dest_height) / 2,
+
+ gdk_pixbuf_scale (area->priv->browse_pixbuf,
+ area->priv->pixbuf,
+ dest_x, dest_y,
+ dest_width, dest_height,
+ dest_x, dest_y,
+ scale, scale,
+ GDK_INTERP_BILINEAR);
+
+ if (area->priv->color_shifted)
+ g_object_unref (area->priv->color_shifted);
+ area->priv->color_shifted = gdk_pixbuf_copy (area->priv->pixbuf);
+ shift_colors (area->priv->color_shifted, -32, -32, -32, 0);
+
+ if (area->priv->scale == 0.0) {
+ area->priv->crop.width = 2 * area->priv->base_width / scale;
+ area->priv->crop.height = 2 * area->priv->base_height / scale;
+ area->priv->crop.x = (gdk_pixbuf_get_width (area->priv->browse_pixbuf) - area->priv->crop.width) / 2;
+ area->priv->crop.y = (gdk_pixbuf_get_height (area->priv->browse_pixbuf) - area->priv->crop.height) / 2;
+ }
+
+ area->priv->scale = scale;
+ area->priv->image.x = dest_x;
+ area->priv->image.y = dest_y;
+ area->priv->image.width = dest_width;
+ area->priv->image.height = dest_height;
+ }
+}
+
+static void
+crop_to_widget (UmCropArea *area,
+ GdkRectangle *crop)
+{
+ crop->x = area->priv->image.x + area->priv->crop.x * area->priv->scale;
+ crop->y = area->priv->image.y + area->priv->crop.y * area->priv->scale;
+ crop->width = area->priv->crop.width * area->priv->scale;
+ crop->height = area->priv->crop.height * area->priv->scale;
+}
+
+typedef enum {
+ OUTSIDE,
+ INSIDE,
+ TOP,
+ TOP_LEFT,
+ TOP_RIGHT,
+ BOTTOM,
+ BOTTOM_LEFT,
+ BOTTOM_RIGHT,
+ LEFT,
+ RIGHT
+} Location;
+
+static gboolean
+um_crop_area_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GdkRectangle area;
+ GdkRectangle crop;
+ gint width, height;
+ UmCropArea *uarea = UM_CROP_AREA (widget);
+
+ if (uarea->priv->browse_pixbuf == NULL)
+ return FALSE;
+
+ update_pixbufs (uarea);
+
+ width = gdk_pixbuf_get_width (uarea->priv->pixbuf);
+ height = gdk_pixbuf_get_height (uarea->priv->pixbuf);
+ crop_to_widget (uarea, &crop);
+
+ gdk_cairo_set_source_pixbuf (cr, uarea->priv->color_shifted, 0, 0);
+ cairo_rectangle (cr, 0, 0, width, crop.y);
+ cairo_rectangle (cr, 0, crop.y, crop.x, crop.height);
+ cairo_rectangle (cr, crop.x + crop.width, crop.y, width - crop.x - crop.width, crop.height);
+ cairo_rectangle (cr, 0, crop.y + crop.height, width, height - crop.y - crop.height);
+ cairo_fill (cr);
+
+ gdk_cairo_set_source_pixbuf (cr, uarea->priv->pixbuf, 0, 0);
+ cairo_rectangle (cr, crop.x, crop.y, crop.width, crop.height);
+ cairo_fill (cr);
+
+ if (uarea->priv->active_region != OUTSIDE) {
+ gint x1, x2, y1, y2;
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_set_line_width (cr, 1.0);
+ x1 = crop.x + crop.width / 3.0;
+ x2 = crop.x + 2 * crop.width / 3.0;
+ y1 = crop.y + crop.height / 3.0;
+ y2 = crop.y + 2 * crop.height / 3.0;
+
+ cairo_move_to (cr, x1 + 0.5, crop.y);
+ cairo_line_to (cr, x1 + 0.5, crop.y + crop.height);
+
+ cairo_move_to (cr, x2 + 0.5, crop.y);
+ cairo_line_to (cr, x2 + 0.5, crop.y + crop.height);
+
+ cairo_move_to (cr, crop.x, y1 + 0.5);
+ cairo_line_to (cr, crop.x + crop.width, y1 + 0.5);
+
+ cairo_move_to (cr, crop.x, y2 + 0.5);
+ cairo_line_to (cr, crop.x + crop.width, y2 + 0.5);
+ cairo_stroke (cr);
+ }
+
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_set_line_width (cr, 1.0);
+ cairo_rectangle (cr,
+ crop.x + 0.5,
+ crop.y + 0.5,
+ crop.width - 1.0,
+ crop.height - 1.0);
+ cairo_stroke (cr);
+
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_set_line_width (cr, 2.0);
+ cairo_rectangle (cr,
+ crop.x + 2.0,
+ crop.y + 2.0,
+ crop.width - 4.0,
+ crop.height - 4.0);
+ cairo_stroke (cr);
+
+ return FALSE;
+}
+
+typedef enum {
+ BELOW,
+ LOWER,
+ BETWEEN,
+ UPPER,
+ ABOVE
+} Range;
+
+static Range
+find_range (gint x,
+ gint min,
+ gint max)
+{
+ gint tolerance = 12;
+
+ if (x < min - tolerance)
+ return BELOW;
+ if (x <= min + tolerance)
+ return LOWER;
+ if (x < max - tolerance)
+ return BETWEEN;
+ if (x <= max + tolerance)
+ return UPPER;
+ return ABOVE;
+}
+
+static Location
+find_location (GdkRectangle *rect,
+ gint x,
+ gint y)
+{
+ Range x_range, y_range;
+ Location location[5][5] = {
+ { OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE },
+ { OUTSIDE, TOP_LEFT, TOP, TOP_RIGHT, OUTSIDE },
+ { OUTSIDE, LEFT, INSIDE, RIGHT, OUTSIDE },
+ { OUTSIDE, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT, OUTSIDE },
+ { OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE }
+ };
+
+ x_range = find_range (x, rect->x, rect->x + rect->width);
+ y_range = find_range (y, rect->y, rect->y + rect->height);
+
+ return location[y_range][x_range];
+}
+
+static void
+update_cursor (UmCropArea *area,
+ gint x,
+ gint y)
+{
+ gint cursor_type;
+ GdkRectangle crop;
+ gint region;
+
+ region = area->priv->active_region;
+ if (region == OUTSIDE) {
+ crop_to_widget (area, &crop);
+ region = find_location (&crop, x, y);
+ }
+
+ switch (region) {
+ case OUTSIDE:
+ cursor_type = GDK_LEFT_PTR;
+ break;
+ case TOP_LEFT:
+ cursor_type = GDK_TOP_LEFT_CORNER;
+ break;
+ case TOP:
+ cursor_type = GDK_TOP_SIDE;
+ break;
+ case TOP_RIGHT:
+ cursor_type = GDK_TOP_RIGHT_CORNER;
+ break;
+ case LEFT:
+ cursor_type = GDK_LEFT_SIDE;
+ break;
+ case INSIDE:
+ cursor_type = GDK_FLEUR;
+ break;
+ case RIGHT:
+ cursor_type = GDK_RIGHT_SIDE;
+ break;
+ case BOTTOM_LEFT:
+ cursor_type = GDK_BOTTOM_LEFT_CORNER;
+ break;
+ case BOTTOM:
+ cursor_type = GDK_BOTTOM_SIDE;
+ break;
+ case BOTTOM_RIGHT:
+ cursor_type = GDK_BOTTOM_RIGHT_CORNER;
+ break;
+ }
+
+ if (cursor_type != area->priv->current_cursor) {
+ GdkCursor *cursor = gdk_cursor_new (cursor_type);
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (area)), cursor);
+ gdk_cursor_unref (cursor);
+ area->priv->current_cursor = cursor_type;
+ }
+}
+
+static int
+eval_radial_line (gdouble center_x, gdouble center_y,
+ gdouble bounds_x, gdouble bounds_y,
+ gdouble user_x)
+{
+ gdouble decision_slope;
+ gdouble decision_intercept;
+
+ decision_slope = (bounds_y - center_y) / (bounds_x - center_x);
+ decision_intercept = bounds_y = -(decision_slope * bounds_x);
+
+ return (int) (decision_slope * user_x + decision_intercept);
+}
+
+static gboolean
+um_crop_area_motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ UmCropArea *area = UM_CROP_AREA (widget);
+ gint x, y;
+ gint delta_x, delta_y;
+ gint width, height;
+ gint adj_width, adj_height;
+ gint pb_width, pb_height;
+ GdkRectangle damage;
+ gint left, right, top, bottom;
+ gdouble new_width, new_height;
+ gdouble center_x, center_y;
+ gint min_width, min_height;
+
+ if (area->priv->browse_pixbuf == NULL)
+ return FALSE;
+
+ update_cursor (area, event->x, event->y);
+
+ crop_to_widget (area, &damage);
+ gtk_widget_queue_draw_area (widget,
+ damage.x - 1, damage.y - 1,
+ damage.width + 2, damage.height + 2);
+
+ pb_width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+ pb_height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+
+ x = (event->x - area->priv->image.x) / area->priv->scale;
+ y = (event->y - area->priv->image.y) / area->priv->scale;
+
+ delta_x = x - area->priv->last_press_x;
+ delta_y = y - area->priv->last_press_y;
+ area->priv->last_press_x = x;
+ area->priv->last_press_y = y;
+
+ left = area->priv->crop.x;
+ right = area->priv->crop.x + area->priv->crop.width - 1;
+ top = area->priv->crop.y;
+ bottom = area->priv->crop.y + area->priv->crop.height - 1;
+
+ center_x = (left + right) / 2.0;
+ center_y = (top + bottom) / 2.0;
+
+ switch (area->priv->active_region) {
+ case INSIDE:
+ width = right - left + 1;
+ height = bottom - top + 1;
+
+ left += delta_x;
+ right += delta_x;
+ top += delta_y;
+ bottom += delta_y;
+
+ if (left < 0)
+ left = 0;
+ if (top < 0)
+ top = 0;
+ if (right > pb_width)
+ right = pb_width;
+ if (bottom > pb_height)
+ bottom = pb_height;
+
+ adj_width = right - left + 1;
+ adj_height = bottom - top + 1;
+ if (adj_width != width) {
+ if (delta_x < 0)
+ right = left + width - 1;
+ else
+ left = right - width + 1;
+ }
+ if (adj_height != height) {
+ if (delta_y < 0)
+ bottom = top + height - 1;
+ else
+ top = bottom - height + 1;
+ }
+
+ break;
+
+ case TOP_LEFT:
+ if (area->priv->aspect < 0) {
+ top = y;
+ left = x;
+ }
+ else if (y < eval_radial_line (center_x, center_y, left, top, x)) {
+ top = y;
+ new_width = (bottom - top) * area->priv->aspect;
+ left = right - new_width;
+ }
+ else {
+ left = x;
+ new_height = (right - left) / area->priv->aspect;
+ top = bottom - new_height;
+ }
+ break;
+
+ case TOP:
+ top = y;
+ if (area->priv->aspect > 0) {
+ new_width = (bottom - top) * area->priv->aspect;
+ right = left + new_width;
+ }
+ break;
+
+ case TOP_RIGHT:
+ if (area->priv->aspect < 0) {
+ top = y;
+ right = x;
+ }
+ else if (y < eval_radial_line (center_x, center_y, right, top, x)) {
+ top = y;
+ new_width = (bottom - top) * area->priv->aspect;
+ right = left + new_width;
+ }
+ else {
+ right = x;
+ new_height = (right - left) / area->priv->aspect;
+ top = bottom - new_height;
+ }
+ break;
+
+ case LEFT:
+ left = x;
+ if (area->priv->aspect > 0) {
+ new_height = (right - left) / area->priv->aspect;
+ bottom = top + new_height;
+ }
+ break;
+
+ case BOTTOM_LEFT:
+ if (area->priv->aspect < 0) {
+ bottom = y;
+ left = x;
+ }
+ else if (y < eval_radial_line (center_x, center_y, left, bottom, x)) {
+ left = x;
+ new_height = (right - left) / area->priv->aspect;
+ bottom = top + new_height;
+ }
+ else {
+ bottom = y;
+ new_width = (bottom - top) * area->priv->aspect;
+ left = right - new_width;
+ }
+ break;
+
+ case RIGHT:
+ right = x;
+ if (area->priv->aspect > 0) {
+ new_height = (right - left) / area->priv->aspect;
+ bottom = top + new_height;
+ }
+ break;
+
+ case BOTTOM_RIGHT:
+ if (area->priv->aspect < 0) {
+ bottom = y;
+ right = x;
+ }
+ else if (y < eval_radial_line (center_x, center_y, right, bottom, x)) {
+ right = x;
+ new_height = (right - left) / area->priv->aspect;
+ bottom = top + new_height;
+ }
+ else {
+ bottom = y;
+ new_width = (bottom - top) * area->priv->aspect;
+ right = left + new_width;
+ }
+ break;
+
+ case BOTTOM:
+ bottom = y;
+ if (area->priv->aspect > 0) {
+ new_width = (bottom - top) * area->priv->aspect;
+ right= left + new_width;
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ min_width = area->priv->base_width / area->priv->scale;
+ min_height = area->priv->base_height / area->priv->scale;
+
+ width = right - left + 1;
+ height = bottom - top + 1;
+ if (area->priv->aspect < 0) {
+ if (left < 0)
+ left = 0;
+ if (top < 0)
+ top = 0;
+ if (right > pb_width)
+ right = pb_width;
+ if (bottom > pb_height)
+ bottom = pb_height;
+
+ width = right - left + 1;
+ height = bottom - top + 1;
+
+ switch (area->priv->active_region) {
+ case LEFT:
+ case TOP_LEFT:
+ case BOTTOM_LEFT:
+ if (width < min_width)
+ left = right - min_width;
+ break;
+ case RIGHT:
+ case TOP_RIGHT:
+ case BOTTOM_RIGHT:
+ if (width < min_width)
+ right = left + min_width;
+ break;
+
+ default: ;
+ }
+
+ switch (area->priv->active_region) {
+ case TOP:
+ case TOP_LEFT:
+ case TOP_RIGHT:
+ if (height < min_height)
+ top = bottom - min_height;
+ break;
+ case BOTTOM:
+ case BOTTOM_LEFT:
+ case BOTTOM_RIGHT:
+ if (height < min_height)
+ bottom = top + min_height;
+ break;
+
+ default: ;
+ }
+ }
+ else {
+ if (left < 0 || top < 0 ||
+ right > pb_width || bottom > pb_height ||
+ width < min_width || height < min_height) {
+ left = area->priv->crop.x;
+ right = area->priv->crop.x + area->priv->crop.width - 1;
+ top = area->priv->crop.y;
+ bottom = area->priv->crop.y + area->priv->crop.height - 1;
+ }
+ }
+
+ area->priv->crop.x = left;
+ area->priv->crop.y = top;
+ area->priv->crop.width = right - left + 1;
+ area->priv->crop.height = bottom - top + 1;
+
+ crop_to_widget (area, &damage);
+ gtk_widget_queue_draw_area (widget,
+ damage.x - 1, damage.y - 1,
+ damage.width + 2, damage.height + 2);
+
+ return FALSE;
+}
+
+static gboolean
+um_crop_area_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ UmCropArea *area = UM_CROP_AREA (widget);
+ GdkRectangle crop;
+
+ if (area->priv->browse_pixbuf == NULL)
+ return FALSE;
+
+ crop_to_widget (area, &crop);
+
+ area->priv->last_press_x = (event->x - area->priv->image.x) / area->priv->scale;
+ area->priv->last_press_y = (event->y - area->priv->image.y) / area->priv->scale;
+ area->priv->active_region = find_location (&crop, event->x, event->y);
+
+ gtk_widget_queue_draw_area (widget,
+ crop.x - 1, crop.y - 1,
+ crop.width + 2, crop.height + 2);
+
+ return FALSE;
+}
+
+static gboolean
+um_crop_area_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ UmCropArea *area = UM_CROP_AREA (widget);
+ GdkRectangle crop;
+
+ if (area->priv->browse_pixbuf == NULL)
+ return FALSE;
+
+ crop_to_widget (area, &crop);
+
+ area->priv->last_press_x = -1;
+ area->priv->last_press_y = -1;
+ area->priv->active_region = OUTSIDE;
+
+ gtk_widget_queue_draw_area (widget,
+ crop.x - 1, crop.y - 1,
+ crop.width + 2, crop.height + 2);
+
+ return FALSE;
+}
+
+static void
+um_crop_area_finalize (GObject *object)
+{
+ UmCropArea *area = UM_CROP_AREA (object);
+
+ if (area->priv->browse_pixbuf) {
+ g_object_unref (area->priv->browse_pixbuf);
+ area->priv->browse_pixbuf = NULL;
+ }
+ if (area->priv->pixbuf) {
+ g_object_unref (area->priv->pixbuf);
+ area->priv->pixbuf = NULL;
+ }
+ if (area->priv->color_shifted) {
+ g_object_unref (area->priv->color_shifted);
+ area->priv->color_shifted = NULL;
+ }
+}
+
+static void
+um_crop_area_class_init (UmCropAreaClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = um_crop_area_finalize;
+ widget_class->draw = um_crop_area_draw;
+ widget_class->button_press_event = um_crop_area_button_press_event;
+ widget_class->button_release_event = um_crop_area_button_release_event;
+ widget_class->motion_notify_event = um_crop_area_motion_notify_event;
+
+ g_type_class_add_private (klass, sizeof (UmCropAreaPrivate));
+}
+
+static void
+um_crop_area_init (UmCropArea *area)
+{
+ area->priv = (G_TYPE_INSTANCE_GET_PRIVATE ((area), UM_TYPE_CROP_AREA,
+ UmCropAreaPrivate));
+
+ gtk_widget_add_events (GTK_WIDGET (area), GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK);
+
+ area->priv->scale = 0.0;
+ area->priv->image.x = 0;
+ area->priv->image.y = 0;
+ area->priv->image.width = 0;
+ area->priv->image.height = 0;
+ area->priv->active_region = OUTSIDE;
+ area->priv->base_width = 48;
+ area->priv->base_height = 48;
+ area->priv->aspect = 1;
+}
+
+GtkWidget *
+um_crop_area_new (void)
+{
+ return g_object_new (UM_TYPE_CROP_AREA, NULL);
+}
+
+GdkPixbuf *
+um_crop_area_get_picture (UmCropArea *area)
+{
+ gint width, height;
+
+ width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+ height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+ width = MIN (area->priv->crop.width, width - area->priv->crop.x);
+ height = MIN (area->priv->crop.height, height - area->priv->crop.y);
+
+ return gdk_pixbuf_new_subpixbuf (area->priv->browse_pixbuf,
+ area->priv->crop.x,
+ area->priv->crop.y,
+ width, height);
+}
+
+void
+um_crop_area_set_picture (UmCropArea *area,
+ GdkPixbuf *pixbuf)
+{
+ int width;
+ int height;
+
+ if (area->priv->browse_pixbuf) {
+ g_object_unref (area->priv->browse_pixbuf);
+ area->priv->browse_pixbuf = NULL;
+ }
+ if (pixbuf) {
+ area->priv->browse_pixbuf = g_object_ref (pixbuf);
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ } else {
+ width = 0;
+ height = 0;
+ }
+
+ area->priv->crop.width = 2 * area->priv->base_width;
+ area->priv->crop.height = 2 * area->priv->base_height;
+ area->priv->crop.x = (width - area->priv->crop.width) / 2;
+ area->priv->crop.y = (height - area->priv->crop.height) / 2;
+
+ area->priv->scale = 0.0;
+ area->priv->image.x = 0;
+ area->priv->image.y = 0;
+ area->priv->image.width = 0;
+ area->priv->image.height = 0;
+
+ gtk_widget_queue_draw (GTK_WIDGET (area));
+}
+
+void
+um_crop_area_set_min_size (UmCropArea *area,
+ gint width,
+ gint height)
+{
+ area->priv->base_width = width;
+ area->priv->base_height = height;
+
+ if (area->priv->aspect > 0) {
+ area->priv->aspect = area->priv->base_width / (gdouble)area->priv->base_height;
+ }
+}
+
+void
+um_crop_area_set_constrain_aspect (UmCropArea *area,
+ gboolean constrain)
+{
+ if (constrain) {
+ area->priv->aspect = area->priv->base_width / (gdouble)area->priv->base_height;
+ }
+ else {
+ area->priv->aspect = -1;
+ }
+}
+
diff --git a/panels/user-accounts/um-crop-area.h b/panels/user-accounts/um-crop-area.h
new file mode 100644
index 0000000..8992957
--- /dev/null
+++ b/panels/user-accounts/um-crop-area.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2009 Bastien Nocera <hadess hadess net>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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/>.
+ */
+
+#ifndef _UM_CROP_AREA_H_
+#define _UM_CROP_AREA_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_CROP_AREA (um_crop_area_get_type ())
+#define UM_CROP_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_CROP_AREA, \
+ UmCropArea))
+#define UM_CROP_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_CROP_AREA, \
+ UmCropAreaClass))
+#define UM_IS_CROP_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_CROP_AREA))
+#define UM_IS_CROP_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_CROP_AREA))
+#define UM_CROP_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_CROP_AREA, \
+ UmCropAreaClass))
+
+typedef struct _UmCropAreaClass UmCropAreaClass;
+typedef struct _UmCropArea UmCropArea;
+typedef struct _UmCropAreaPrivate UmCropAreaPrivate;
+
+struct _UmCropAreaClass {
+ GtkDrawingAreaClass parent_class;
+};
+
+struct _UmCropArea {
+ GtkDrawingArea parent_instance;
+ UmCropAreaPrivate *priv;
+};
+
+GType um_crop_area_get_type (void) G_GNUC_CONST;
+
+GtkWidget *um_crop_area_new (void);
+GdkPixbuf *um_crop_area_get_picture (UmCropArea *area);
+void um_crop_area_set_picture (UmCropArea *area,
+ GdkPixbuf *pixbuf);
+void um_crop_area_set_min_size (UmCropArea *area,
+ gint width,
+ gint height);
+void um_crop_area_set_constrain_aspect (UmCropArea *area,
+ gboolean constrain);
+
+G_END_DECLS
+
+#endif /* _UM_CROP_AREA_H_ */
diff --git a/panels/user-accounts/um-editable-button.c b/panels/user-accounts/um-editable-button.c
new file mode 100644
index 0000000..3212459
--- /dev/null
+++ b/panels/user-accounts/um-editable-button.c
@@ -0,0 +1,403 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include <gdk/gdkkeysyms.h>
+#include "um-editable-button.h"
+
+#define EMPTY_TEXT "\xe2\x80\x94"
+
+struct _UmEditableButtonPrivate {
+ GtkNotebook *notebook;
+ GtkLabel *label;
+ GtkButton *button;
+
+ gchar *text;
+ gboolean editable;
+ gint weight;
+ gboolean weight_set;
+ gdouble scale;
+ gboolean scale_set;
+};
+
+#define UM_EDITABLE_BUTTON_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), UM_TYPE_EDITABLE_BUTTON, UmEditableButtonPrivate))
+
+enum {
+ PROP_0,
+ PROP_TEXT,
+ PROP_EDITABLE,
+ PROP_SCALE,
+ PROP_SCALE_SET,
+ PROP_WEIGHT,
+ PROP_WEIGHT_SET
+};
+
+enum {
+ START_EDITING,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (UmEditableButton, um_editable_button, GTK_TYPE_ALIGNMENT);
+
+void
+um_editable_button_set_text (UmEditableButton *button,
+ const gchar *text)
+{
+ UmEditableButtonPrivate *priv;
+ gchar *tmp;
+ GtkWidget *label;
+
+ priv = button->priv;
+
+ tmp = g_strdup (text);
+ g_free (priv->text);
+ priv->text = tmp;
+
+ if (tmp == NULL || tmp[0] == '\0')
+ tmp = EMPTY_TEXT;
+
+ gtk_label_set_text (priv->label, tmp);
+ label = gtk_bin_get_child (GTK_BIN (priv->button));
+ gtk_label_set_text (GTK_LABEL (label), tmp);
+
+ g_object_notify (G_OBJECT (button), "text");
+}
+
+const gchar *
+um_editable_button_get_text (UmEditableButton *button)
+{
+ return button->priv->text;
+}
+
+void
+um_editable_button_set_editable (UmEditableButton *button,
+ gboolean editable)
+{
+ UmEditableButtonPrivate *priv;
+
+ priv = button->priv;
+
+ if (priv->editable != editable) {
+ priv->editable = editable;
+
+ gtk_notebook_set_current_page (priv->notebook, editable ? 1 : 0);
+
+ g_object_notify (G_OBJECT (button), "editable");
+ }
+}
+
+gboolean
+um_editable_button_get_editable (UmEditableButton *button)
+{
+ return button->priv->editable;
+}
+
+static void
+update_fonts (UmEditableButton *button)
+{
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+ GtkWidget *label;
+
+ UmEditableButtonPrivate *priv = button->priv;
+
+ attrs = pango_attr_list_new ();
+ if (priv->scale_set) {
+ attr = pango_attr_scale_new (priv->scale);
+ pango_attr_list_insert (attrs, attr);
+ }
+ if (priv->weight_set) {
+ attr = pango_attr_weight_new (priv->weight);
+ pango_attr_list_insert (attrs, attr);
+ }
+
+ gtk_label_set_attributes (priv->label, attrs);
+
+ label = gtk_bin_get_child (GTK_BIN (priv->button));
+ gtk_label_set_attributes (GTK_LABEL (label), attrs);
+
+ pango_attr_list_unref (attrs);
+}
+
+void
+um_editable_button_set_weight (UmEditableButton *button,
+ gint weight)
+{
+ UmEditableButtonPrivate *priv = button->priv;
+
+ if (priv->weight == weight && priv->weight_set)
+ return;
+
+ priv->weight = weight;
+ priv->weight_set = TRUE;
+
+ update_fonts (button);
+
+ g_object_notify (G_OBJECT (button), "weight");
+ g_object_notify (G_OBJECT (button), "weight-set");
+}
+
+gint
+um_editable_button_get_weight (UmEditableButton *button)
+{
+ return button->priv->weight;
+}
+
+void
+um_editable_button_set_scale (UmEditableButton *button,
+ gdouble scale)
+{
+ UmEditableButtonPrivate *priv = button->priv;
+
+ if (priv->scale == scale && priv->scale_set)
+ return;
+
+ priv->scale = scale;
+ priv->scale_set = TRUE;
+
+ update_fonts (button);
+
+ g_object_notify (G_OBJECT (button), "scale");
+ g_object_notify (G_OBJECT (button), "scale-set");
+}
+
+gdouble
+um_editable_button_get_scale (UmEditableButton *button)
+{
+ return button->priv->scale;
+}
+
+static void
+um_editable_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableButton *button = UM_EDITABLE_BUTTON (object);
+
+ switch (prop_id) {
+ case PROP_TEXT:
+ um_editable_button_set_text (button, g_value_get_string (value));
+ break;
+ case PROP_EDITABLE:
+ um_editable_button_set_editable (button, g_value_get_boolean (value));
+ break;
+ case PROP_WEIGHT:
+ um_editable_button_set_weight (button, g_value_get_int (value));
+ break;
+ case PROP_WEIGHT_SET:
+ button->priv->weight_set = g_value_get_boolean (value);
+ break;
+ case PROP_SCALE:
+ um_editable_button_set_scale (button, g_value_get_double (value));
+ break;
+ case PROP_SCALE_SET:
+ button->priv->scale_set = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableButton *button = UM_EDITABLE_BUTTON (object);
+
+ switch (prop_id) {
+ case PROP_TEXT:
+ g_value_set_string (value,
+ um_editable_button_get_text (button));
+ break;
+ case PROP_EDITABLE:
+ g_value_set_boolean (value,
+ um_editable_button_get_editable (button));
+ break;
+ case PROP_WEIGHT:
+ g_value_set_int (value,
+ um_editable_button_get_weight (button));
+ break;
+ case PROP_WEIGHT_SET:
+ g_value_set_boolean (value,
+ button->priv->weight_set);
+ break;
+ case PROP_SCALE:
+ g_value_set_double (value,
+ um_editable_button_get_scale (button));
+ break;
+ case PROP_SCALE_SET:
+ g_value_set_boolean (value,
+ button->priv->scale_set);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_button_finalize (GObject *object)
+{
+ UmEditableButton *button = (UmEditableButton*)object;
+
+ g_free (button->priv->text);
+
+ G_OBJECT_CLASS (um_editable_button_parent_class)->finalize (object);
+}
+
+static void
+um_editable_button_class_init (UmEditableButtonClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->set_property = um_editable_button_set_property;
+ object_class->get_property = um_editable_button_get_property;
+ object_class->finalize = um_editable_button_finalize;
+
+ signals[START_EDITING] =
+ g_signal_new ("start-editing",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmEditableButtonClass, start_editing),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_property (object_class, PROP_TEXT,
+ g_param_spec_string ("text",
+ "Text", "The text of the button",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_EDITABLE,
+ g_param_spec_boolean ("editable",
+ "Editable", "Whether the text can be edited",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WEIGHT,
+ g_param_spec_int ("weight",
+ "Font Weight", "The font weight to use",
+ 0, G_MAXINT, PANGO_WEIGHT_NORMAL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WEIGHT_SET,
+ g_param_spec_boolean ("weight-set",
+ "Font Weight Set", "Whether a font weight is set",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SCALE,
+ g_param_spec_double ("scale",
+ "Font Scale", "The font scale to use",
+ 0.0, G_MAXDOUBLE, 1.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SCALE_SET,
+ g_param_spec_boolean ("scale-set",
+ "Font Scale Set", "Whether a font scale is set",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (class, sizeof (UmEditableButtonPrivate));
+}
+
+static void
+start_editing (UmEditableButton *button)
+{
+ g_signal_emit (button, signals[START_EDITING], 0);
+}
+
+static void
+button_clicked (GtkWidget *widget,
+ UmEditableButton *button)
+{
+ start_editing (button);
+}
+
+static void
+update_button_padding (GtkWidget *widget,
+ GtkAllocation *allocation,
+ UmEditableButton *button)
+{
+ UmEditableButtonPrivate *priv = button->priv;
+ GtkAllocation parent_allocation;
+ gint offset;
+ gint pad;
+
+ gtk_widget_get_allocation (gtk_widget_get_parent (widget), &parent_allocation);
+
+ offset = allocation->x - parent_allocation.x;
+
+ gtk_misc_get_padding (GTK_MISC (priv->label), &pad, NULL);
+ if (offset != pad)
+ gtk_misc_set_padding (GTK_MISC (priv->label), offset, 0);
+}
+
+static void
+um_editable_button_init (UmEditableButton *button)
+{
+ UmEditableButtonPrivate *priv;
+
+ priv = button->priv = UM_EDITABLE_BUTTON_GET_PRIVATE (button);
+
+ priv->weight = PANGO_WEIGHT_NORMAL;
+ priv->weight_set = FALSE;
+ priv->scale = 1.0;
+ priv->scale_set = FALSE;
+
+ priv->notebook = (GtkNotebook*)gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (priv->notebook, FALSE);
+ gtk_notebook_set_show_border (priv->notebook, FALSE);
+
+ priv->label = (GtkLabel*)gtk_label_new (EMPTY_TEXT);
+ gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->label, NULL);
+
+ priv->button = (GtkButton*)gtk_button_new_with_label (EMPTY_TEXT);
+ gtk_widget_set_receives_default ((GtkWidget*)priv->button, TRUE);
+ gtk_button_set_relief (priv->button, GTK_RELIEF_NONE);
+ gtk_button_set_alignment (priv->button, 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->button, NULL);
+ g_signal_connect (priv->button, "clicked", G_CALLBACK (button_clicked), button);
+ g_signal_connect (gtk_bin_get_child (GTK_BIN (priv->button)), "size-allocate", G_CALLBACK (update_button_padding), button);
+
+ gtk_container_add (GTK_CONTAINER (button), (GtkWidget*)priv->notebook);
+
+ gtk_widget_show ((GtkWidget*)priv->notebook);
+ gtk_widget_show ((GtkWidget*)priv->label);
+ gtk_widget_show ((GtkWidget*)priv->button);
+
+ gtk_notebook_set_current_page (priv->notebook, 0);
+}
+
+GtkWidget *
+um_editable_button_new (void)
+{
+ return (GtkWidget *) g_object_new (UM_TYPE_EDITABLE_BUTTON, NULL);
+}
diff --git a/panels/user-accounts/um-editable-button.h b/panels/user-accounts/um-editable-button.h
new file mode 100644
index 0000000..36af622
--- /dev/null
+++ b/panels/user-accounts/um-editable-button.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef _UM_EDITABLE_BUTTON_H
+#define _UM_EDITABLE_BUTTON_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_EDITABLE_BUTTON um_editable_button_get_type()
+
+#define UM_EDITABLE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_EDITABLE_BUTTON, UmEditableButton))
+#define UM_EDITABLE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_EDITABLE_BUTTON, UmEditableButtonClass))
+#define UM_IS_EDITABLE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_EDITABLE_BUTTON))
+#define UM_IS_EDITABLE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_EDITABLE_BUTTON))
+#define UM_EDITABLE_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_EDITABLE_BUTTON, UmEditableButtonClass))
+
+typedef struct _UmEditableButton UmEditableButton;
+typedef struct _UmEditableButtonClass UmEditableButtonClass;
+typedef struct _UmEditableButtonPrivate UmEditableButtonPrivate;
+
+struct _UmEditableButton
+{
+ GtkAlignment parent;
+
+ UmEditableButtonPrivate *priv;
+};
+
+struct _UmEditableButtonClass
+{
+ GtkAlignmentClass parent_class;
+
+ void (* start_editing) (UmEditableButton *button);
+};
+
+GType um_editable_button_get_type (void) G_GNUC_CONST;
+GtkWidget *um_editable_button_new (void);
+void um_editable_button_set_text (UmEditableButton *button,
+ const gchar *text);
+const gchar *um_editable_button_get_text (UmEditableButton *button);
+void um_editable_button_set_editable (UmEditableButton *button,
+ gboolean editable);
+gboolean um_editable_button_get_editable (UmEditableButton *button);
+void um_editable_button_set_weight (UmEditableButton *button,
+ gint weight);
+gint um_editable_button_get_weight (UmEditableButton *button);
+void um_editable_button_set_scale (UmEditableButton *button,
+ gdouble scale);
+gdouble um_editable_button_get_scale (UmEditableButton *button);
+
+G_END_DECLS
+
+#endif /* _UM_EDITABLE_BUTTON_H_ */
diff --git a/panels/user-accounts/um-editable-combo.c b/panels/user-accounts/um-editable-combo.c
new file mode 100644
index 0000000..586bcdb
--- /dev/null
+++ b/panels/user-accounts/um-editable-combo.c
@@ -0,0 +1,439 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include <gdk/gdkkeysyms.h>
+#include "um-editable-combo.h"
+
+#define EMPTY_TEXT "\xe2\x80\x94"
+
+struct _UmEditableComboPrivate {
+ GtkNotebook *notebook;
+ GtkLabel *label;
+ GtkButton *button;
+ GtkComboBox *combo;
+ GtkWidget *toplevel;
+
+ gint active;
+ gint editable;
+ gint text_column;
+};
+
+#define UM_EDITABLE_COMBO_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), UM_TYPE_EDITABLE_COMBO, UmEditableComboPrivate))
+
+enum {
+ PROP_0,
+ PROP_EDITABLE,
+ PROP_MODEL,
+ PROP_TEXT_COLUMN
+};
+
+enum {
+ EDITING_DONE,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (UmEditableCombo, um_editable_combo, GTK_TYPE_ALIGNMENT);
+
+void
+um_editable_combo_set_editable (UmEditableCombo *combo,
+ gboolean editable)
+{
+ UmEditableComboPrivate *priv;
+
+ priv = combo->priv;
+
+ if (priv->editable != editable) {
+ priv->editable = editable;
+
+ gtk_notebook_set_current_page (priv->notebook, editable ? 1 : 0);
+
+ g_object_notify (G_OBJECT (combo), "editable");
+ }
+}
+
+gboolean
+um_editable_combo_get_editable (UmEditableCombo *combo)
+{
+ return combo->priv->editable;
+}
+
+void
+um_editable_combo_set_model (UmEditableCombo *combo,
+ GtkTreeModel *model)
+{
+ gtk_combo_box_set_model (combo->priv->combo, model);
+
+ g_object_notify (G_OBJECT (combo), "model");
+}
+
+GtkTreeModel *
+um_editable_combo_get_model (UmEditableCombo *combo)
+{
+ return gtk_combo_box_get_model (combo->priv->combo);
+}
+
+void
+um_editable_combo_set_text_column (UmEditableCombo *combo,
+ gint text_column)
+{
+ UmEditableComboPrivate *priv = combo->priv;
+ GList *cells;
+
+ if (priv->text_column == text_column)
+ return;
+
+ priv->text_column = text_column;
+
+ cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->combo));
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (priv->combo),
+ cells->data,
+ "text", text_column,
+ NULL);
+ g_list_free (cells);
+
+ g_object_notify (G_OBJECT (combo), "text-column");
+}
+
+gint
+um_editable_combo_get_text_column (UmEditableCombo *combo)
+{
+ return combo->priv->text_column;
+}
+
+void
+um_editable_combo_set_active (UmEditableCombo *combo,
+ gint active)
+{
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ if (active == -1)
+ um_editable_combo_set_active_iter (combo, NULL);
+ else {
+ model = gtk_combo_box_get_model (combo->priv->combo);
+ path = gtk_tree_path_new_from_indices (active, -1);
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_path_free (path);
+ um_editable_combo_set_active_iter (combo, &iter);
+ }
+}
+
+void
+um_editable_combo_set_active_iter (UmEditableCombo *combo,
+ GtkTreeIter *iter)
+{
+ UmEditableComboPrivate *priv = combo->priv;
+ GtkWidget *label;
+ gchar *text;
+ GtkTreeModel *model;
+
+ gtk_combo_box_set_active_iter (priv->combo, iter);
+ priv->active = gtk_combo_box_get_active (priv->combo);
+
+ if (priv->text_column == -1)
+ return;
+
+ if (iter) {
+ model = gtk_combo_box_get_model (priv->combo);
+ gtk_tree_model_get (model, iter, priv->text_column, &text, -1);
+ }
+ else {
+ text = g_strdup (EMPTY_TEXT);
+ }
+
+ gtk_label_set_text (priv->label, text);
+ label = gtk_bin_get_child ((GtkBin*)priv->button);
+ gtk_label_set_text (GTK_LABEL (label), text);
+
+ g_free (text);
+}
+
+gboolean
+um_editable_combo_get_active_iter (UmEditableCombo *combo,
+ GtkTreeIter *iter)
+{
+ return gtk_combo_box_get_active_iter (combo->priv->combo, iter);
+}
+
+gint
+um_editable_combo_get_active (UmEditableCombo *combo)
+{
+ return combo->priv->active;
+}
+
+static void
+um_editable_combo_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableCombo *combo = UM_EDITABLE_COMBO (object);
+
+ switch (prop_id) {
+ case PROP_EDITABLE:
+ um_editable_combo_set_editable (combo, g_value_get_boolean (value));
+ break;
+ case PROP_MODEL:
+ um_editable_combo_set_model (combo, g_value_get_object (value));
+ break;
+ case PROP_TEXT_COLUMN:
+ um_editable_combo_set_text_column (combo, g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_combo_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableCombo *combo = UM_EDITABLE_COMBO (object);
+
+ switch (prop_id) {
+ case PROP_EDITABLE:
+ g_value_set_boolean (value,
+ um_editable_combo_get_editable (combo));
+ break;
+ case PROP_MODEL:
+ g_value_set_object (value,
+ um_editable_combo_get_model (combo));
+ break;
+ case PROP_TEXT_COLUMN:
+ g_value_set_int (value,
+ um_editable_combo_get_text_column (combo));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_combo_class_init (UmEditableComboClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->set_property = um_editable_combo_set_property;
+ object_class->get_property = um_editable_combo_get_property;
+
+ signals[EDITING_DONE] =
+ g_signal_new ("editing-done",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmEditableComboClass, editing_done),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_property (object_class, PROP_MODEL,
+ g_param_spec_object ("model",
+ "Model", "The options to present in the combobox",
+ GTK_TYPE_TREE_MODEL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_TEXT_COLUMN,
+ g_param_spec_int ("text-column",
+ "Text Column", "The model column that contains the displayable text",
+ -1, G_MAXINT, -1,
+ G_PARAM_READWRITE));
+
+
+ g_object_class_install_property (object_class, PROP_EDITABLE,
+ g_param_spec_boolean ("editable",
+ "Editable", "Whether the text can be edited",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (class, sizeof (UmEditableComboPrivate));
+}
+
+static void
+start_editing (UmEditableCombo *combo)
+{
+ gtk_notebook_set_current_page (combo->priv->notebook, 2);
+}
+
+static void
+stop_editing (UmEditableCombo *combo)
+{
+ um_editable_combo_set_active (combo,
+ gtk_combo_box_get_active (combo->priv->combo));
+ gtk_notebook_set_current_page (combo->priv->notebook, 1);
+
+ g_signal_emit (combo, signals[EDITING_DONE], 0);
+}
+
+static void
+cancel_editing (UmEditableCombo *combo)
+{
+ gtk_combo_box_set_active (combo->priv->combo,
+ um_editable_combo_get_active (combo));
+ gtk_notebook_set_current_page (combo->priv->notebook, 1);
+}
+
+static void
+button_clicked (GtkWidget *widget,
+ UmEditableCombo *combo)
+{
+ if (combo->priv->editable)
+ start_editing (combo);
+}
+
+static void
+combo_changed (GtkWidget *widget,
+ UmEditableCombo *combo)
+{
+ if (combo->priv->editable)
+ stop_editing (combo);
+}
+
+static gboolean
+combo_key_press (GtkWidget *widget,
+ GdkEventKey *event,
+ UmEditableCombo *combo)
+{
+ if (event->keyval == GDK_KEY_Escape) {
+ cancel_editing (combo);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+focus_moved (GtkWindow *window,
+ GtkWidget *widget,
+ UmEditableCombo *combo)
+{
+ if (gtk_notebook_get_current_page (combo->priv->notebook) == 2 &&
+ (!widget || !gtk_widget_is_ancestor (widget, (GtkWidget *)combo)))
+ stop_editing (combo);
+}
+
+static void
+combo_hierarchy_changed (GtkWidget *widget,
+ GtkWidget *previous_toplevel,
+ UmEditableCombo *combo)
+{
+ UmEditableComboPrivate *priv;
+ GtkWidget *toplevel;
+
+ priv = combo->priv;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (priv->toplevel != toplevel) {
+ if (priv->toplevel)
+ g_signal_handlers_disconnect_by_func (priv->toplevel,
+ focus_moved, combo);
+
+ if (GTK_IS_WINDOW (toplevel))
+ priv->toplevel = toplevel;
+ else
+ priv->toplevel = NULL;
+
+ if (priv->toplevel)
+ g_signal_connect (priv->toplevel, "set-focus",
+ G_CALLBACK (focus_moved), combo);
+ }
+}
+
+static void
+update_button_padding (GtkWidget *widget,
+ GtkAllocation *allocation,
+ UmEditableCombo *combo)
+{
+ UmEditableComboPrivate *priv = combo->priv;
+ GtkAllocation parent_allocation;
+ gint offset;
+ gint pad;
+
+ gtk_widget_get_allocation (gtk_widget_get_parent (widget), &parent_allocation);
+
+ offset = allocation->x - parent_allocation.x;
+
+ gtk_misc_get_padding (GTK_MISC (priv->label), &pad, NULL);
+ if (offset != pad)
+ gtk_misc_set_padding (GTK_MISC (priv->label), offset, 0);
+}
+
+static void
+um_editable_combo_init (UmEditableCombo *combo)
+{
+ UmEditableComboPrivate *priv;
+ GtkCellRenderer *cell;
+
+ priv = combo->priv = UM_EDITABLE_COMBO_GET_PRIVATE (combo);
+
+ priv->active = -1;
+ priv->text_column = -1;
+
+ priv->notebook = (GtkNotebook*)gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (priv->notebook, FALSE);
+ gtk_notebook_set_show_border (priv->notebook, FALSE);
+
+ priv->label = (GtkLabel*)gtk_label_new ("");
+ gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->label, NULL);
+
+ priv->button = (GtkButton*)gtk_button_new_with_label ("");
+ gtk_widget_set_receives_default ((GtkWidget*)priv->button, TRUE);
+ gtk_button_set_relief (priv->button, GTK_RELIEF_NONE);
+ gtk_button_set_alignment (priv->button, 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->button, NULL);
+ g_signal_connect (priv->button, "clicked", G_CALLBACK (button_clicked), combo);
+
+ priv->combo = (GtkComboBox*)gtk_combo_box_new ();
+ cell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo), cell, TRUE);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->combo, NULL);
+
+ g_signal_connect (priv->combo, "changed", G_CALLBACK (combo_changed), combo);
+ g_signal_connect (priv->combo, "key-press-event", G_CALLBACK (combo_key_press), combo);
+ g_signal_connect (gtk_bin_get_child (GTK_BIN (priv->button)), "size-allocate", G_CALLBACK (update_button_padding), combo);
+
+
+ gtk_container_add (GTK_CONTAINER (combo), (GtkWidget*)priv->notebook);
+
+ gtk_widget_show ((GtkWidget*)priv->notebook);
+ gtk_widget_show ((GtkWidget*)priv->label);
+ gtk_widget_show ((GtkWidget*)priv->button);
+ gtk_widget_show ((GtkWidget*)priv->combo);
+
+ gtk_notebook_set_current_page (priv->notebook, 0);
+
+ /* ugly hack to catch the combo box losing focus */
+ g_signal_connect (combo, "hierarchy-changed",
+ G_CALLBACK (combo_hierarchy_changed), combo);
+}
+
+GtkWidget *
+um_editable_combo_new (void)
+{
+ return (GtkWidget *) g_object_new (UM_TYPE_EDITABLE_COMBO, NULL);
+}
diff --git a/panels/user-accounts/um-editable-combo.h b/panels/user-accounts/um-editable-combo.h
new file mode 100644
index 0000000..0d4e4a6
--- /dev/null
+++ b/panels/user-accounts/um-editable-combo.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef _UM_EDITABLE_COMBO_H
+#define _UM_EDITABLE_COMBO_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_EDITABLE_COMBO um_editable_combo_get_type()
+
+#define UM_EDITABLE_COMBO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_EDITABLE_COMBO, UmEditableCombo))
+#define UM_EDITABLE_COMBO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_EDITABLE_COMBO, UmEditableComboClass))
+#define UM_IS_EDITABLE_COMBO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_EDITABLE_COMBO))
+#define UM_IS_EDITABLE_COMBO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_EDITABLE_COMBO))
+#define UM_EDITABLE_COMBO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_EDITABLE_COMBO, UmEditableComboClass))
+
+typedef struct _UmEditableCombo UmEditableCombo;
+typedef struct _UmEditableComboClass UmEditableComboClass;
+typedef struct _UmEditableComboPrivate UmEditableComboPrivate;
+
+struct _UmEditableCombo
+{
+ GtkAlignment parent;
+
+ UmEditableComboPrivate *priv;
+};
+
+struct _UmEditableComboClass
+{
+ GtkAlignmentClass parent_class;
+
+ void (* editing_done) (UmEditableCombo *combo);
+};
+
+GType um_editable_combo_get_type (void) G_GNUC_CONST;
+GtkWidget *um_editable_combo_new (void);
+void um_editable_combo_set_editable (UmEditableCombo *combo,
+ gboolean editable);
+gboolean um_editable_combo_get_editable (UmEditableCombo *combo);
+void um_editable_combo_set_model (UmEditableCombo *combo,
+ GtkTreeModel *model);
+GtkTreeModel *um_editable_combo_get_model (UmEditableCombo *combo);
+void um_editable_combo_set_text_column (UmEditableCombo *combo,
+ gint column);
+gint um_editable_combo_get_text_column (UmEditableCombo *combo);
+gint um_editable_combo_get_active (UmEditableCombo *combo);
+void um_editable_combo_set_active (UmEditableCombo *combo,
+ gint active);
+gboolean um_editable_combo_get_active_iter (UmEditableCombo *combo,
+ GtkTreeIter *iter);
+void um_editable_combo_set_active_iter (UmEditableCombo *combo,
+ GtkTreeIter *iter);
+
+G_END_DECLS
+
+#endif /* _UM_EDITABLE_COMBO_H_ */
diff --git a/panels/user-accounts/um-editable-entry.c b/panels/user-accounts/um-editable-entry.c
new file mode 100644
index 0000000..2a18323
--- /dev/null
+++ b/panels/user-accounts/um-editable-entry.c
@@ -0,0 +1,489 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include <gdk/gdkkeysyms.h>
+#include "um-editable-entry.h"
+
+#define EMPTY_TEXT "\xe2\x80\x94"
+
+struct _UmEditableEntryPrivate {
+ GtkNotebook *notebook;
+ GtkLabel *label;
+ GtkButton *button;
+ GtkEntry *entry;
+
+ gchar *text;
+ gboolean editable;
+ gint weight;
+ gboolean weight_set;
+ gdouble scale;
+ gboolean scale_set;
+};
+
+#define UM_EDITABLE_ENTRY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), UM_TYPE_EDITABLE_ENTRY, UmEditableEntryPrivate))
+
+enum {
+ PROP_0,
+ PROP_TEXT,
+ PROP_EDITABLE,
+ PROP_SCALE,
+ PROP_SCALE_SET,
+ PROP_WEIGHT,
+ PROP_WEIGHT_SET
+};
+
+enum {
+ EDITING_DONE,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (UmEditableEntry, um_editable_entry, GTK_TYPE_ALIGNMENT);
+
+void
+um_editable_entry_set_text (UmEditableEntry *e,
+ const gchar *text)
+{
+ UmEditableEntryPrivate *priv;
+ gchar *tmp;
+ GtkWidget *label;
+
+ priv = e->priv;
+
+ tmp = g_strdup (text);
+ g_free (priv->text);
+ priv->text = tmp;
+
+ gtk_entry_set_text (priv->entry, tmp);
+
+ if (tmp == NULL || tmp[0] == '\0')
+ tmp = EMPTY_TEXT;
+
+ gtk_label_set_text (priv->label, tmp);
+ label = gtk_bin_get_child (GTK_BIN (priv->button));
+ gtk_label_set_text (GTK_LABEL (label), tmp);
+
+ g_object_notify (G_OBJECT (e), "text");
+}
+
+const gchar *
+um_editable_entry_get_text (UmEditableEntry *e)
+{
+ return e->priv->text;
+}
+
+void
+um_editable_entry_set_editable (UmEditableEntry *e,
+ gboolean editable)
+{
+ UmEditableEntryPrivate *priv;
+
+ priv = e->priv;
+
+ if (priv->editable != editable) {
+ priv->editable = editable;
+
+ gtk_notebook_set_current_page (priv->notebook, editable ? 1 : 0);
+
+ g_object_notify (G_OBJECT (e), "editable");
+ }
+}
+
+gboolean
+um_editable_entry_get_editable (UmEditableEntry *e)
+{
+ return e->priv->editable;
+}
+
+static void
+update_entry_font (GtkWidget *widget,
+ GtkStyle *previous_style,
+ UmEditableEntry *e)
+{
+ UmEditableEntryPrivate *priv = e->priv;
+ PangoFontDescription *desc;
+ GtkStyle *style;
+ gint size;
+
+ if (!priv->weight_set && !priv->scale_set)
+ return;
+
+ g_signal_handlers_block_by_func (widget, update_entry_font, e);
+
+ gtk_widget_modify_font (widget, NULL);
+
+ style = gtk_widget_get_style (widget);
+ desc = pango_font_description_copy (style->font_desc);
+ if (priv->weight_set)
+ pango_font_description_set_weight (desc, priv->weight);
+ if (priv->scale_set) {
+ size = pango_font_description_get_size (desc);
+ pango_font_description_set_size (desc, priv->scale * size);
+ }
+ gtk_widget_modify_font (widget, desc);
+
+ pango_font_description_free (desc);
+
+ g_signal_handlers_unblock_by_func (widget, update_entry_font, e);
+}
+
+static void
+update_fonts (UmEditableEntry *e)
+{
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+ GtkWidget *label;
+
+ UmEditableEntryPrivate *priv = e->priv;
+
+ attrs = pango_attr_list_new ();
+ if (priv->scale_set) {
+ attr = pango_attr_scale_new (priv->scale);
+ pango_attr_list_insert (attrs, attr);
+ }
+ if (priv->weight_set) {
+ attr = pango_attr_weight_new (priv->weight);
+ pango_attr_list_insert (attrs, attr);
+ }
+
+ gtk_label_set_attributes (priv->label, attrs);
+
+ label = gtk_bin_get_child (GTK_BIN (priv->button));
+ gtk_label_set_attributes (GTK_LABEL (label), attrs);
+
+ pango_attr_list_unref (attrs);
+
+ update_entry_font ((GtkWidget *)priv->entry, NULL, e);
+}
+
+void
+um_editable_entry_set_weight (UmEditableEntry *e,
+ gint weight)
+{
+ UmEditableEntryPrivate *priv = e->priv;
+
+ if (priv->weight == weight && priv->weight_set)
+ return;
+
+ priv->weight = weight;
+ priv->weight_set = TRUE;
+
+ update_fonts (e);
+
+ g_object_notify (G_OBJECT (e), "weight");
+ g_object_notify (G_OBJECT (e), "weight-set");
+}
+
+gint
+um_editable_entry_get_weight (UmEditableEntry *e)
+{
+ return e->priv->weight;
+}
+
+void
+um_editable_entry_set_scale (UmEditableEntry *e,
+ gdouble scale)
+{
+ UmEditableEntryPrivate *priv = e->priv;
+
+ if (priv->scale == scale && priv->scale_set)
+ return;
+
+ priv->scale = scale;
+ priv->scale_set = TRUE;
+
+ update_fonts (e);
+
+ g_object_notify (G_OBJECT (e), "scale");
+ g_object_notify (G_OBJECT (e), "scale-set");
+}
+
+gdouble
+um_editable_entry_get_scale (UmEditableEntry *e)
+{
+ return e->priv->scale;
+}
+
+static void
+um_editable_entry_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableEntry *e = UM_EDITABLE_ENTRY (object);
+
+ switch (prop_id) {
+ case PROP_TEXT:
+ um_editable_entry_set_text (e, g_value_get_string (value));
+ break;
+ case PROP_EDITABLE:
+ um_editable_entry_set_editable (e, g_value_get_boolean (value));
+ break;
+ case PROP_WEIGHT:
+ um_editable_entry_set_weight (e, g_value_get_int (value));
+ break;
+ case PROP_WEIGHT_SET:
+ e->priv->weight_set = g_value_get_boolean (value);
+ break;
+ case PROP_SCALE:
+ um_editable_entry_set_scale (e, g_value_get_double (value));
+ break;
+ case PROP_SCALE_SET:
+ e->priv->scale_set = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_entry_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableEntry *e = UM_EDITABLE_ENTRY (object);
+
+ switch (prop_id) {
+ case PROP_TEXT:
+ g_value_set_string (value,
+ um_editable_entry_get_text (e));
+ break;
+ case PROP_EDITABLE:
+ g_value_set_boolean (value,
+ um_editable_entry_get_editable (e));
+ break;
+ case PROP_WEIGHT:
+ g_value_set_int (value,
+ um_editable_entry_get_weight (e));
+ break;
+ case PROP_WEIGHT_SET:
+ g_value_set_boolean (value, e->priv->weight_set);
+ break;
+ case PROP_SCALE:
+ g_value_set_double (value,
+ um_editable_entry_get_scale (e));
+ break;
+ case PROP_SCALE_SET:
+ g_value_set_boolean (value, e->priv->scale_set);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_entry_finalize (GObject *object)
+{
+ UmEditableEntry *e = (UmEditableEntry*)object;
+
+ g_free (e->priv->text);
+
+ G_OBJECT_CLASS (um_editable_entry_parent_class)->finalize (object);
+}
+
+static void
+um_editable_entry_class_init (UmEditableEntryClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->set_property = um_editable_entry_set_property;
+ object_class->get_property = um_editable_entry_get_property;
+ object_class->finalize = um_editable_entry_finalize;
+
+ signals[EDITING_DONE] =
+ g_signal_new ("editing-done",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmEditableEntryClass, editing_done),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_property (object_class, PROP_TEXT,
+ g_param_spec_string ("text",
+ "Text", "The text of the button",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_EDITABLE,
+ g_param_spec_boolean ("editable",
+ "Editable", "Whether the text can be edited",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WEIGHT,
+ g_param_spec_int ("weight",
+ "Font Weight", "The font weight to use",
+ 0, G_MAXINT, PANGO_WEIGHT_NORMAL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WEIGHT_SET,
+ g_param_spec_boolean ("weight-set",
+ "Font Weight Set", "Whether a font weight is set",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SCALE,
+ g_param_spec_double ("scale",
+ "Font Scale", "The font scale to use",
+ 0.0, G_MAXDOUBLE, 1.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SCALE_SET,
+ g_param_spec_boolean ("scale-set",
+ "Font Scale Set", "Whether a font scale is set",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (class, sizeof (UmEditableEntryPrivate));
+}
+
+static void
+start_editing (UmEditableEntry *e)
+{
+ gtk_notebook_set_current_page (e->priv->notebook, 2);
+}
+
+static void
+stop_editing (UmEditableEntry *e)
+{
+ um_editable_entry_set_text (e, gtk_entry_get_text (e->priv->entry));
+ gtk_notebook_set_current_page (e->priv->notebook, 1);
+ g_signal_emit (e, signals[EDITING_DONE], 0);
+}
+
+static void
+cancel_editing (UmEditableEntry *e)
+{
+ gtk_entry_set_text (e->priv->entry, um_editable_entry_get_text (e));
+ gtk_notebook_set_current_page (e->priv->notebook, 1);
+}
+
+static void
+button_clicked (GtkWidget *widget,
+ UmEditableEntry *e)
+{
+ start_editing (e);
+}
+
+static void
+entry_activated (GtkWidget *widget,
+ UmEditableEntry *e)
+{
+ stop_editing (e);
+}
+
+static gboolean
+entry_focus_out (GtkWidget *widget,
+ GdkEventFocus *event,
+ UmEditableEntry *e)
+{
+ stop_editing (e);
+ return FALSE;
+}
+
+static gboolean
+entry_key_press (GtkWidget *widget,
+ GdkEventKey *event,
+ UmEditableEntry *e)
+{
+ if (event->keyval == GDK_KEY_Escape) {
+ cancel_editing (e);
+ }
+ return FALSE;
+}
+
+static void
+update_button_padding (GtkWidget *widget,
+ GtkAllocation *allocation,
+ UmEditableEntry *e)
+{
+ UmEditableEntryPrivate *priv = e->priv;
+ GtkAllocation alloc;
+ gint offset;
+ gint pad;
+
+ gtk_widget_get_allocation (gtk_widget_get_parent (widget), &alloc);
+
+ offset = allocation->x - alloc.x;
+
+ gtk_misc_get_padding (GTK_MISC (priv->label), &pad, NULL);
+ if (offset != pad)
+ gtk_misc_set_padding (GTK_MISC (priv->label), offset, 0);
+}
+
+static void
+um_editable_entry_init (UmEditableEntry *e)
+{
+ UmEditableEntryPrivate *priv;
+
+ priv = e->priv = UM_EDITABLE_ENTRY_GET_PRIVATE (e);
+
+ priv->weight = PANGO_WEIGHT_NORMAL;
+ priv->weight_set = FALSE;
+ priv->scale = 1.0;
+ priv->scale_set = FALSE;
+
+ priv->notebook = (GtkNotebook*)gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (priv->notebook, FALSE);
+ gtk_notebook_set_show_border (priv->notebook, FALSE);
+
+ priv->label = (GtkLabel*)gtk_label_new (EMPTY_TEXT);
+ gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->label, NULL);
+
+ priv->button = (GtkButton*)gtk_button_new_with_label (EMPTY_TEXT);
+ gtk_widget_set_receives_default ((GtkWidget*)priv->button, TRUE);
+ gtk_button_set_relief (priv->button, GTK_RELIEF_NONE);
+ gtk_button_set_alignment (priv->button, 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->button, NULL);
+ g_signal_connect (priv->button, "clicked", G_CALLBACK (button_clicked), e);
+
+ priv->entry = (GtkEntry*)gtk_entry_new ();
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->entry, NULL);
+
+ g_signal_connect (priv->entry, "activate", G_CALLBACK (entry_activated), e);
+ g_signal_connect (priv->entry, "focus-out-event", G_CALLBACK (entry_focus_out), e);
+ g_signal_connect (priv->entry, "key-press-event", G_CALLBACK (entry_key_press), e);
+ g_signal_connect (priv->entry, "style-set", G_CALLBACK (update_entry_font), e);
+ g_signal_connect (gtk_bin_get_child (GTK_BIN (priv->button)), "size-allocate", G_CALLBACK (update_button_padding), e);
+
+ gtk_container_add (GTK_CONTAINER (e), (GtkWidget*)priv->notebook);
+
+ gtk_widget_show ((GtkWidget*)priv->notebook);
+ gtk_widget_show ((GtkWidget*)priv->label);
+ gtk_widget_show ((GtkWidget*)priv->button);
+ gtk_widget_show ((GtkWidget*)priv->entry);
+
+ gtk_notebook_set_current_page (priv->notebook, 0);
+}
+
+GtkWidget *
+um_editable_entry_new (void)
+{
+ return (GtkWidget *) g_object_new (UM_TYPE_EDITABLE_ENTRY, NULL);
+}
diff --git a/panels/user-accounts/um-editable-entry.h b/panels/user-accounts/um-editable-entry.h
new file mode 100644
index 0000000..1f5f3f4
--- /dev/null
+++ b/panels/user-accounts/um-editable-entry.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef _UM_EDITABLE_ENTRY_H_
+#define _UM_EDITABLE_ENTRY_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_EDITABLE_ENTRY um_editable_entry_get_type()
+
+#define UM_EDITABLE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_EDITABLE_ENTRY, UmEditableEntry))
+#define UM_EDITABLE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_EDITABLE_ENTRY, UmEditableEntryClass))
+#define UM_IS_EDITABLE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_EDITABLE_ENTRY))
+#define UM_IS_EDITABLE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_EDITABLE_ENTRY))
+#define UM_EDITABLE_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_EDITABLE_ENTRY, UmEditableEntryClass))
+
+typedef struct _UmEditableEntry UmEditableEntry;
+typedef struct _UmEditableEntryClass UmEditableEntryClass;
+typedef struct _UmEditableEntryPrivate UmEditableEntryPrivate;
+
+struct _UmEditableEntry
+{
+ GtkAlignment parent;
+
+ UmEditableEntryPrivate *priv;
+};
+
+struct _UmEditableEntryClass
+{
+ GtkAlignmentClass parent_class;
+
+ void (* editing_done) (UmEditableEntry *entry);
+};
+
+GType um_editable_entry_get_type (void) G_GNUC_CONST;
+GtkWidget *um_editable_entry_new (void);
+void um_editable_entry_set_text (UmEditableEntry *entry,
+ const gchar *text);
+const gchar *um_editable_entry_get_text (UmEditableEntry *entry);
+void um_editable_entry_set_editable (UmEditableEntry *entry,
+ gboolean editable);
+gboolean um_editable_entry_get_editable (UmEditableEntry *entry);
+void um_editable_entry_set_weight (UmEditableEntry *entry,
+ gint weight);
+gint um_editable_entry_get_weight (UmEditableEntry *entry);
+void um_editable_entry_set_scale (UmEditableEntry *entry,
+ gdouble scale);
+gdouble um_editable_entry_get_scale (UmEditableEntry *entry);
+
+G_END_DECLS
+
+#endif /* _UM_EDITABLE_ENTRY_H_ */
diff --git a/panels/user-accounts/um-fingerprint-dialog.c b/panels/user-accounts/um-fingerprint-dialog.c
new file mode 100644
index 0000000..de11c95
--- /dev/null
+++ b/panels/user-accounts/um-fingerprint-dialog.c
@@ -0,0 +1,674 @@
+/* gnome-about-me-fingerprint.h
+ * Copyright (C) 2008 Bastien Nocera <hadess hadess net>
+ *
+ * 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, 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <dbus/dbus-glib-bindings.h>
+
+#include "um-fingerprint-dialog.h"
+
+#include "fingerprint-strings.h"
+
+/* Retrieve a widget from the UI object */
+#define WID(s) GTK_WIDGET (gtk_builder_get_object (dialog, s))
+
+/* Translate fprintd strings */
+#define TR(s) dgettext("fprintd", s)
+
+/* This must match the number of images on the 2nd page in the UI file */
+#define MAX_ENROLL_STAGES 5
+
+static DBusGProxy *manager = NULL;
+static DBusGConnection *connection = NULL;
+static gboolean is_disable = FALSE;
+
+enum {
+ STATE_NONE,
+ STATE_CLAIMED,
+ STATE_ENROLLING
+};
+
+typedef struct {
+ GtkWidget *label1;
+ GtkWidget *label2;
+
+ GtkWidget *ass;
+ GtkBuilder *dialog;
+
+ DBusGProxy *device;
+ gboolean is_swipe;
+ int num_enroll_stages;
+ int num_stages_done;
+ char *name;
+ const char *finger;
+ gint state;
+} EnrollData;
+
+static void create_manager (void)
+{
+ GError *error = NULL;
+
+ connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+ if (connection == NULL) {
+ g_warning ("Failed to connect to session bus: %s", error->message);
+ return;
+ }
+
+ manager = dbus_g_proxy_new_for_name (connection,
+ "net.reactivated.Fprint",
+ "/net/reactivated/Fprint/Manager",
+ "net.reactivated.Fprint.Manager");
+}
+
+static DBusGProxy *
+get_first_device (void)
+{
+ DBusGProxy *device;
+ char *device_str;
+
+ if (!dbus_g_proxy_call (manager, "GetDefaultDevice", NULL, G_TYPE_INVALID,
+ DBUS_TYPE_G_OBJECT_PATH, &device_str, G_TYPE_INVALID)) {
+ return NULL;
+ }
+
+ device = dbus_g_proxy_new_for_name(connection,
+ "net.reactivated.Fprint",
+ device_str,
+ "net.reactivated.Fprint.Device");
+
+ g_free (device_str);
+
+ return device;
+}
+
+static const char *
+get_reason_for_error (const char *dbus_error)
+{
+ if (g_str_equal (dbus_error, "net.reactivated.Fprint.Error.PermissionDenied"))
+ return N_("You are not allowed to access the device. Contact your system administrator.");
+ if (g_str_equal (dbus_error, "net.reactivated.Fprint.Error.AlreadyInUse"))
+ return N_("The device is already in use.");
+ if (g_str_equal (dbus_error, "net.reactivated.Fprint.Error.Internal"))
+ return N_("An internal error occurred.");
+
+ return NULL;
+}
+
+static GtkWidget *
+get_error_dialog (const char *title,
+ const char *dbus_error,
+ GtkWindow *parent)
+{
+ GtkWidget *error_dialog;
+ const char *reason;
+
+ if (dbus_error == NULL)
+ g_warning ("get_error_dialog called with reason == NULL");
+
+ error_dialog =
+ gtk_message_dialog_new (parent,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s", title);
+ reason = get_reason_for_error (dbus_error);
+ gtk_message_dialog_format_secondary_text
+ (GTK_MESSAGE_DIALOG (error_dialog), "%s", reason ? _(reason) : _(dbus_error));
+
+ gtk_window_set_title (GTK_WINDOW (error_dialog), ""); /* as per HIG */
+ gtk_container_set_border_width (GTK_CONTAINER (error_dialog), 5);
+ gtk_dialog_set_default_response (GTK_DIALOG (error_dialog),
+ GTK_RESPONSE_OK);
+ gtk_window_set_modal (GTK_WINDOW (error_dialog), TRUE);
+ gtk_window_set_position (GTK_WINDOW (error_dialog), GTK_WIN_POS_CENTER_ON_PARENT);
+
+ return error_dialog;
+}
+
+gboolean
+set_fingerprint_label (GtkWidget *label1,
+ GtkWidget *label2)
+{
+ char **fingers;
+ DBusGProxy *device;
+ GError *error = NULL;
+
+ if (manager == NULL) {
+ create_manager ();
+ if (manager == NULL) {
+ return FALSE;
+ }
+ }
+
+ device = get_first_device ();
+ if (device == NULL)
+ return FALSE;
+
+ if (!dbus_g_proxy_call (device, "ListEnrolledFingers", &error, G_TYPE_STRING, "", G_TYPE_INVALID,
+ G_TYPE_STRV, &fingers, G_TYPE_INVALID)) {
+ if (dbus_g_error_has_name (error, "net.reactivated.Fprint.Error.NoEnrolledPrints") == FALSE) {
+ g_object_unref (device);
+ return FALSE;
+ }
+ fingers = NULL;
+ }
+
+ if (fingers == NULL || g_strv_length (fingers) == 0) {
+ is_disable = FALSE;
+ gtk_label_set_text (GTK_LABEL (label1), _("Disabled"));
+ gtk_label_set_text (GTK_LABEL (label2), _("Disabled"));
+ } else {
+ is_disable = TRUE;
+ gtk_label_set_text (GTK_LABEL (label1), _("Enabled"));
+ gtk_label_set_text (GTK_LABEL (label2), _("Enabled"));
+ }
+
+ g_strfreev (fingers);
+ g_object_unref (device);
+
+ return TRUE;
+}
+
+static void
+delete_fingerprints (void)
+{
+ DBusGProxy *device;
+
+ if (manager == NULL) {
+ create_manager ();
+ if (manager == NULL)
+ return;
+ }
+
+ device = get_first_device ();
+ if (device == NULL)
+ return;
+
+ dbus_g_proxy_call (device, "DeleteEnrolledFingers", NULL, G_TYPE_STRING, "", G_TYPE_INVALID, G_TYPE_INVALID);
+
+ g_object_unref (device);
+}
+
+static void
+delete_fingerprints_question (GtkWindow *parent,
+ GtkWidget *label1,
+ GtkWidget *label2,
+ UmUser *user)
+{
+ GtkWidget *question;
+ GtkWidget *button;
+
+ question = gtk_message_dialog_new (parent,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("Delete registered fingerprints?"));
+ gtk_dialog_add_button (GTK_DIALOG (question), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_window_set_modal (GTK_WINDOW (question), TRUE);
+
+ button = gtk_button_new_with_mnemonic (_("_Delete Fingerprints"));
+ gtk_button_set_image (GTK_BUTTON (button), gtk_image_new_from_stock (GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON));
+ gtk_widget_set_can_default (button, TRUE);
+ gtk_widget_show (button);
+ gtk_dialog_add_action_widget (GTK_DIALOG (question), button, GTK_RESPONSE_OK);
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (question),
+ _("Do you want to delete your registered fingerprints so fingerprint login is disabled?"));
+ gtk_container_set_border_width (GTK_CONTAINER (question), 5);
+ gtk_dialog_set_default_response (GTK_DIALOG (question), GTK_RESPONSE_OK);
+ gtk_window_set_position (GTK_WINDOW (question), GTK_WIN_POS_CENTER_ON_PARENT);
+ gtk_window_set_modal (GTK_WINDOW (question), TRUE);
+
+ if (gtk_dialog_run (GTK_DIALOG (question)) == GTK_RESPONSE_OK) {
+ delete_fingerprints ();
+ set_fingerprint_label (label1, label2);
+ }
+
+ gtk_widget_destroy (question);
+}
+
+static void
+enroll_data_destroy (EnrollData *data)
+{
+ switch (data->state) {
+ case STATE_ENROLLING:
+ dbus_g_proxy_call(data->device, "EnrollStop", NULL, G_TYPE_INVALID, G_TYPE_INVALID);
+ /* fall-through */
+ case STATE_CLAIMED:
+ dbus_g_proxy_call(data->device, "Release", NULL, G_TYPE_INVALID, G_TYPE_INVALID);
+ /* fall-through */
+ case STATE_NONE:
+ g_free (data->name);
+ g_object_unref (data->device);
+ g_object_unref (data->dialog);
+ gtk_widget_destroy (data->ass);
+
+ g_free (data);
+ }
+}
+
+static const char *
+selected_finger (GtkBuilder *dialog)
+{
+ int index;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (WID ("radiobutton1")))) {
+ gtk_widget_set_sensitive (WID ("finger_combobox"), FALSE);
+ return "right-index-finger";
+ }
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (WID ("radiobutton2")))) {
+ gtk_widget_set_sensitive (WID ("finger_combobox"), FALSE);
+ return "left-index-finger";
+ }
+ gtk_widget_set_sensitive (WID ("finger_combobox"), TRUE);
+ index = gtk_combo_box_get_active (GTK_COMBO_BOX (WID ("finger_combobox")));
+ switch (index) {
+ case 0:
+ return "left-thumb";
+ case 1:
+ return "left-middle-finger";
+ case 2:
+ return "left-ring-finger";
+ case 3:
+ return "left-little-finger";
+ case 4:
+ return "right-thumb";
+ case 5:
+ return "right-middle-finger";
+ case 6:
+ return "right-ring-finger";
+ case 7:
+ return "right-little-finger";
+ default:
+ g_assert_not_reached ();
+ }
+
+ return NULL;
+}
+
+static void
+finger_radio_button_toggled (GtkToggleButton *button, EnrollData *data)
+{
+ GtkBuilder *dialog = data->dialog;
+ char *msg;
+
+ data->finger = selected_finger (data->dialog);
+
+ msg = g_strdup_printf (TR(finger_str_to_msg (data->finger, data->is_swipe)), data->name);
+ gtk_label_set_text (GTK_LABEL (WID("enroll-label")), msg);
+ g_free (msg);
+}
+
+static void
+finger_combobox_changed (GtkComboBox *combobox, EnrollData *data)
+{
+ GtkBuilder *dialog = data->dialog;
+ char *msg;
+
+ data->finger = selected_finger (data->dialog);
+
+ msg = g_strdup_printf (TR(finger_str_to_msg (data->finger, data->is_swipe)), data->name);
+ gtk_label_set_text (GTK_LABEL (WID("enroll-label")), msg);
+ g_free (msg);
+}
+
+static void
+assistant_cancelled (GtkAssistant *ass, EnrollData *data)
+{
+ GtkWidget *label1, *label2;
+
+ label1 = data->label1;
+ label2 = data->label2;
+
+ enroll_data_destroy (data);
+ set_fingerprint_label (label1, label2);
+}
+
+static void
+enroll_result (GObject *object, const char *result, gboolean done, EnrollData *data)
+{
+ GtkBuilder *dialog = data->dialog;
+ char *msg;
+
+ if (g_str_equal (result, "enroll-completed") || g_str_equal (result, "enroll-stage-passed")) {
+ char *name, *path;
+
+ data->num_stages_done++;
+ name = g_strdup_printf ("image%d", data->num_stages_done);
+ path = g_build_filename (UM_PIXMAP_DIR, "print_ok.png", NULL);
+ gtk_image_set_from_file (GTK_IMAGE (WID (name)), path);
+ g_free (name);
+ g_free (path);
+ }
+ if (g_str_equal (result, "enroll-completed")) {
+ gtk_label_set_text (GTK_LABEL (WID ("status-label")), _("Done!"));
+ gtk_assistant_set_page_complete (GTK_ASSISTANT (data->ass), WID ("page2"), TRUE);
+ }
+
+ if (done != FALSE) {
+ dbus_g_proxy_call(data->device, "EnrollStop", NULL, G_TYPE_INVALID, G_TYPE_INVALID);
+ data->state = STATE_CLAIMED;
+ if (g_str_equal (result, "enroll-completed") == FALSE) {
+ /* The enrollment failed, restart it */
+ dbus_g_proxy_call(data->device, "EnrollStart", NULL, G_TYPE_STRING, data->finger, G_TYPE_INVALID, G_TYPE_INVALID);
+ data->state = STATE_ENROLLING;
+ result = "enroll-retry-scan";
+ } else {
+ return;
+ }
+ }
+
+ msg = g_strdup_printf (TR(enroll_result_str_to_msg (result, data->is_swipe)), data->name);
+ gtk_label_set_text (GTK_LABEL (WID ("status-label")), msg);
+ g_free (msg);
+}
+
+static void
+assistant_prepare (GtkAssistant *ass, GtkWidget *page, EnrollData *data)
+{
+ const char *name;
+
+ name = g_object_get_data (G_OBJECT (page), "name");
+ if (name == NULL)
+ return;
+
+ if (g_str_equal (name, "enroll")) {
+ DBusGProxy *p;
+ GError *error = NULL;
+ GtkBuilder *dialog = data->dialog;
+ char *path;
+ guint i;
+ GValue value = { 0, };
+
+ if (!dbus_g_proxy_call (data->device, "Claim", &error, G_TYPE_STRING, "", G_TYPE_INVALID, G_TYPE_INVALID)) {
+ GtkWidget *d;
+ char *msg;
+
+ /* translators:
+ * The variable is the name of the device, for example:
+ * "Could you not access "Digital Persona U.are.U 4000/4000B" device */
+ msg = g_strdup_printf (_("Could not access '%s' device"), data->name);
+ d = get_error_dialog (msg, dbus_g_error_get_name (error), GTK_WINDOW (data->ass));
+ g_error_free (error);
+ gtk_dialog_run (GTK_DIALOG (d));
+ gtk_widget_destroy (d);
+ g_free (msg);
+
+ enroll_data_destroy (data);
+
+ return;
+ }
+ data->state = STATE_CLAIMED;
+
+ p = dbus_g_proxy_new_from_proxy (data->device, "org.freedesktop.DBus.Properties", NULL);
+ if (!dbus_g_proxy_call (p, "Get", NULL, G_TYPE_STRING, "net.reactivated.Fprint.Device", G_TYPE_STRING, "num-enroll-stages", G_TYPE_INVALID,
+ G_TYPE_VALUE, &value, G_TYPE_INVALID) || g_value_get_int (&value) < 1) {
+ GtkWidget *d;
+ char *msg;
+
+ /* translators:
+ * The variable is the name of the device, for example:
+ * "Could you not access "Digital Persona U.are.U 4000/4000B" device */
+ msg = g_strdup_printf (_("Could not access '%s' device"), data->name);
+ d = get_error_dialog (msg, "net.reactivated.Fprint.Error.Internal", GTK_WINDOW (data->ass));
+ gtk_dialog_run (GTK_DIALOG (d));
+ gtk_widget_destroy (d);
+ g_free (msg);
+
+ enroll_data_destroy (data);
+
+ g_object_unref (p);
+ return;
+ }
+ g_object_unref (p);
+
+ data->num_enroll_stages = g_value_get_int (&value);
+
+ /* Hide the extra "bulbs" if not needed */
+ for (i = MAX_ENROLL_STAGES; i > data->num_enroll_stages; i--) {
+ char *name;
+
+ name = g_strdup_printf ("image%d", i);
+ gtk_widget_hide (WID (name));
+ g_free (name);
+ }
+ /* And set the right image */
+ {
+ char *filename;
+
+ filename = g_strdup_printf ("%s.png", data->finger);
+ path = g_build_filename (UM_PIXMAP_DIR, filename, NULL);
+ g_free (filename);
+ }
+ for (i = 1; i <= data->num_enroll_stages; i++) {
+ char *name;
+ name = g_strdup_printf ("image%d", i);
+ gtk_image_set_from_file (GTK_IMAGE (WID (name)), path);
+ g_free (name);
+ }
+ g_free (path);
+
+ dbus_g_proxy_add_signal(data->device, "EnrollStatus", G_TYPE_STRING, G_TYPE_BOOLEAN, NULL);
+ dbus_g_proxy_connect_signal(data->device, "EnrollStatus", G_CALLBACK(enroll_result), data, NULL);
+
+ if (!dbus_g_proxy_call(data->device, "EnrollStart", &error, G_TYPE_STRING, data->finger, G_TYPE_INVALID, G_TYPE_INVALID)) {
+ GtkWidget *d;
+ char *msg;
+
+ /* translators:
+ * The variable is the name of the device, for example:
+ * "Could you not access "Digital Persona U.are.U 4000/4000B" device */
+ msg = g_strdup_printf (_("Could not start finger capture on '%s' device"), data->name);
+ d = get_error_dialog (msg, dbus_g_error_get_name (error), GTK_WINDOW (data->ass));
+ g_error_free (error);
+ gtk_dialog_run (GTK_DIALOG (d));
+ gtk_widget_destroy (d);
+ g_free (msg);
+
+ enroll_data_destroy (data);
+
+ return;
+ }
+ data->state = STATE_ENROLLING;;
+ } else {
+ if (data->state == STATE_ENROLLING) {
+ dbus_g_proxy_call(data->device, "EnrollStop", NULL, G_TYPE_INVALID, G_TYPE_INVALID);
+ data->state = STATE_CLAIMED;
+ }
+ if (data->state == STATE_CLAIMED) {
+ dbus_g_proxy_call(data->device, "Release", NULL, G_TYPE_INVALID, G_TYPE_INVALID);
+ data->state = STATE_NONE;
+ }
+ }
+}
+
+static void
+align_image (GtkWidget *child, gpointer data)
+{
+ if (GTK_IS_IMAGE (child)) {
+ gtk_misc_set_alignment (GTK_MISC (child), 0, 0.5);
+ gtk_misc_set_padding (GTK_MISC (child), 10, 10);
+ }
+
+ if (GTK_IS_LABEL (child)) {
+ gtk_label_set_use_markup (GTK_LABEL (child), TRUE);
+ gtk_widget_modify_font (child, NULL);
+ gtk_misc_set_padding (GTK_MISC (child), 68, 10);
+ }
+}
+
+static void
+enroll_fingerprints (GtkWindow *parent,
+ GtkWidget *label1,
+ GtkWidget *label2,
+ UmUser *user)
+{
+ DBusGProxy *device, *p;
+ GHashTable *props;
+ GtkBuilder *dialog;
+ EnrollData *data;
+ GtkWidget *ass;
+ const char *filename;
+ char *msg;
+ GError *error = NULL;
+ GdkPixbuf *pixbuf;
+ gchar *title;
+ GtkStyle *style;
+
+ device = NULL;
+
+ if (manager == NULL) {
+ create_manager ();
+ if (manager != NULL)
+ device = get_first_device ();
+ } else {
+ device = get_first_device ();
+ }
+
+ if (manager == NULL || device == NULL) {
+ GtkWidget *d;
+
+ d = get_error_dialog (_("Could not access any fingerprint readers"),
+ _("Please contact your system administrator for help."),
+ parent);
+ gtk_dialog_run (GTK_DIALOG (d));
+ gtk_widget_destroy (d);
+ return;
+ }
+
+ data = g_new0 (EnrollData, 1);
+ data->device = device;
+ data->label1 = label1;
+ data->label2 = label2;
+
+ /* Get some details about the device */
+ p = dbus_g_proxy_new_from_proxy (device, "org.freedesktop.DBus.Properties", NULL);
+ if (dbus_g_proxy_call (p, "GetAll", NULL, G_TYPE_STRING, "net.reactivated.Fprint.Device", G_TYPE_INVALID,
+ dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), &props, G_TYPE_INVALID)) {
+ const char *scan_type;
+ data->name = g_value_dup_string (g_hash_table_lookup (props, "name"));
+ scan_type = g_value_dup_string (g_hash_table_lookup (props, "scan-type"));
+ if (g_str_equal (scan_type, "swipe"))
+ data->is_swipe = TRUE;
+ g_hash_table_destroy (props);
+ }
+ g_object_unref (p);
+
+ dialog = gtk_builder_new ();
+ filename = UIDIR "/account-fingerprint.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ filename = "../data/account-fingerprint.ui";
+ if (!gtk_builder_add_from_file (dialog, filename, &error)) {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+ data->dialog = dialog;
+
+ ass = WID ("assistant");
+ gtk_window_set_title (GTK_WINDOW (ass), _("Enable Fingerprint Login"));
+ gtk_window_set_transient_for (GTK_WINDOW (ass), parent);
+ gtk_window_set_modal (GTK_WINDOW (ass), TRUE);
+ gtk_window_set_position (GTK_WINDOW (ass), GTK_WIN_POS_CENTER_ON_PARENT);
+
+ gtk_widget_realize (ass);
+ style = gtk_widget_get_style (ass);
+ gtk_widget_modify_fg (ass, GTK_STATE_SELECTED, &style->fg[GTK_STATE_NORMAL]);
+ gtk_widget_modify_bg (ass, GTK_STATE_SELECTED, &style->bg[GTK_STATE_NORMAL]);
+
+ g_signal_connect (G_OBJECT (ass), "cancel",
+ G_CALLBACK (assistant_cancelled), data);
+ g_signal_connect (G_OBJECT (ass), "close",
+ G_CALLBACK (assistant_cancelled), data);
+ g_signal_connect (G_OBJECT (ass), "prepare",
+ G_CALLBACK (assistant_prepare), data);
+
+ /* Page 1 */
+ gtk_combo_box_set_active (GTK_COMBO_BOX (WID ("finger_combobox")), 0);
+
+ g_signal_connect (G_OBJECT (WID ("radiobutton1")), "toggled",
+ G_CALLBACK (finger_radio_button_toggled), data);
+ g_signal_connect (G_OBJECT (WID ("radiobutton2")), "toggled",
+ G_CALLBACK (finger_radio_button_toggled), data);
+ g_signal_connect (G_OBJECT (WID ("radiobutton3")), "toggled",
+ G_CALLBACK (finger_radio_button_toggled), data);
+ g_signal_connect (G_OBJECT (WID ("finger_combobox")), "changed",
+ G_CALLBACK (finger_combobox_changed), data);
+
+ data->finger = selected_finger (dialog);
+
+ g_object_set_data (G_OBJECT (WID("page1")), "name", "intro");
+
+ /* translators:
+ * The variable is the name of the device, for example:
+ * "To enable fingerprint login, you need to save one of your fingerprints, using the
+ * 'Digital Persona U.are.U 4000/4000B' device." */
+ msg = g_strdup_printf (_("To enable fingerprint login, you need to save one of your fingerprints, using the '%s' device."),
+ data->name);
+ gtk_label_set_text (GTK_LABEL (WID("intro-label")), msg);
+ g_free (msg);
+
+ gtk_assistant_set_page_complete (GTK_ASSISTANT (ass), WID("page1"), TRUE);
+
+ pixbuf = um_user_render_icon (user, FALSE, 48);
+ title = g_strdup_printf (_("Enrolling fingerprints for\n<b><big>%s</big></b>"), um_user_get_real_name (user));
+
+ gtk_assistant_set_page_header_image (GTK_ASSISTANT (ass), WID("page1"), pixbuf);
+ gtk_assistant_set_page_title (GTK_ASSISTANT (ass), WID("page1"), title);
+ gtk_assistant_set_page_header_image (GTK_ASSISTANT (ass), WID("page2"), pixbuf);
+ gtk_assistant_set_page_title (GTK_ASSISTANT (ass), WID("page2"), title);
+ gtk_assistant_set_page_header_image (GTK_ASSISTANT (ass), WID("page3"), pixbuf);
+ gtk_assistant_set_page_title (GTK_ASSISTANT (ass), WID("page3"), title);
+ gtk_container_forall (GTK_CONTAINER (ass), align_image, NULL);
+ g_object_unref (pixbuf);
+ g_free (title);
+
+ /* Page 2 */
+ g_object_set_data (G_OBJECT (WID("page2")), "name", "enroll");
+
+ msg = g_strdup_printf (TR(finger_str_to_msg (data->finger, data->is_swipe)), data->name);
+ gtk_label_set_text (GTK_LABEL (WID("enroll-label")), msg);
+ g_free (msg);
+
+ /* Page 3 */
+ g_object_set_data (G_OBJECT (WID("page3")), "name", "summary");
+
+ data->ass = ass;
+ gtk_widget_show_all (ass);
+}
+
+void
+fingerprint_button_clicked (GtkWindow *parent,
+ GtkWidget *label1,
+ GtkWidget *label2,
+ UmUser *user)
+{
+ bindtextdomain ("fprintd", GNOMELOCALEDIR);
+ bind_textdomain_codeset ("fprintd", "UTF-8");
+
+ if (is_disable != FALSE) {
+ delete_fingerprints_question (parent, label1, label2, user);
+ } else {
+ enroll_fingerprints (parent, label1, label2, user);
+ }
+}
+
diff --git a/panels/user-accounts/um-fingerprint-dialog.h b/panels/user-accounts/um-fingerprint-dialog.h
new file mode 100644
index 0000000..cca4b58
--- /dev/null
+++ b/panels/user-accounts/um-fingerprint-dialog.h
@@ -0,0 +1,28 @@
+/* gnome-about-me-fingerprint.h
+ * Copyright (C) 2008 Bastien Nocera <hadess hadess net>
+ *
+ * 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, 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+#include "um-user.h"
+
+gboolean set_fingerprint_label (GtkWidget *label1,
+ GtkWidget *label2);
+void fingerprint_button_clicked (GtkWindow *parent,
+ GtkWidget *label1,
+ GtkWidget *label2,
+ UmUser *user);
diff --git a/panels/user-accounts/um-language-dialog.c b/panels/user-accounts/um-language-dialog.c
new file mode 100644
index 0000000..9362b62
--- /dev/null
+++ b/panels/user-accounts/um-language-dialog.c
@@ -0,0 +1,555 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <fontconfig/fontconfig.h>
+
+#include "um-language-dialog.h"
+#include "um-user-manager.h"
+
+#include "gdm-languages.h"
+
+struct _UmLanguageDialog {
+ GtkWidget *dialog;
+ GtkWidget *user_icon;
+ GtkWidget *user_name;
+ GtkWidget *dialog_combo;
+ GtkListStore *dialog_store;
+
+ GtkWidget *chooser;
+ GtkWidget *chooser_list;
+ GtkListStore *chooser_store;
+
+ char *language;
+ UmUser *user;
+
+ gboolean force_setting;
+};
+
+enum {
+ LOCALE_COL,
+ DISPLAY_LOCALE_COL,
+ NUM_COLS
+};
+
+static void
+cancel_language_dialog (GtkButton *button,
+ UmLanguageDialog *um)
+{
+ if (um->force_setting)
+ um_user_set_language (um->user, um->language);
+ gtk_widget_hide (um->dialog);
+ um_language_dialog_set_user (um, NULL);
+
+}
+
+static void
+accept_language_dialog (GtkButton *button,
+ UmLanguageDialog *um)
+{
+ um_user_set_language (um->user, um->language);
+
+ gtk_widget_hide (um->dialog);
+ um_language_dialog_set_user (um, NULL);
+}
+
+gchar *
+um_language_chooser_get_language (GtkWidget *chooser)
+{
+ GtkTreeView *tv;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *lang;
+
+ tv = (GtkTreeView *) g_object_get_data (G_OBJECT (chooser), "list");
+ selection = gtk_tree_view_get_selection (tv);
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ gtk_tree_model_get (model, &iter, LOCALE_COL, &lang, -1);
+ else
+ lang = NULL;
+
+ return lang;
+}
+
+static gint
+sort_languages (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer data)
+{
+ char *ca, *cb;
+ char *la, *lb;
+ gint result;
+
+ gtk_tree_model_get (model, a, LOCALE_COL, &ca, DISPLAY_LOCALE_COL, &la, -1);
+ gtk_tree_model_get (model, b, LOCALE_COL, &cb, DISPLAY_LOCALE_COL, &lb, -1);
+
+ if (!ca)
+ result = 1;
+ else if (!cb)
+ result = -1;
+ else
+ result = strcmp (la, lb);
+
+ g_free (ca);
+ g_free (cb);
+ g_free (la);
+ g_free (lb);
+
+ return result;
+}
+
+gboolean
+um_get_iter_for_language (GtkTreeModel *model,
+ const gchar *lang,
+ GtkTreeIter *iter)
+{
+ char *l;
+ char *name;
+ char *language;
+
+ gtk_tree_model_get_iter_first (model, iter);
+ do {
+ gtk_tree_model_get (model, iter, LOCALE_COL, &l, -1);
+ if (g_strcmp0 (l, lang) == 0) {
+ g_free (l);
+ return TRUE;
+ }
+ g_free (l);
+ } while (gtk_tree_model_iter_next (model, iter));
+
+ name = gdm_normalize_language_name (lang);
+ if (name != NULL) {
+ language = gdm_get_language_from_name (name, NULL);
+
+ gtk_list_store_append (GTK_LIST_STORE (model), iter);
+ gtk_list_store_set (GTK_LIST_STORE (model), iter, LOCALE_COL, name, DISPLAY_LOCALE_COL, language, -1);
+ g_free (name);
+ g_free (language);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+select_language (UmLanguageDialog *um,
+ const gchar *lang)
+{
+ if (um->chooser)
+ gtk_widget_hide (um->chooser);
+}
+
+static void
+row_activated (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GtkWidget *chooser)
+{
+ gtk_dialog_response (GTK_DIALOG (chooser), GTK_RESPONSE_OK);
+}
+
+static gboolean
+language_has_font (const gchar *locale)
+{
+ const FcCharSet *charset;
+ FcPattern *pattern;
+ FcObjectSet *object_set;
+ FcFontSet *font_set;
+ gchar *language_code;
+ gboolean is_displayable;
+
+ is_displayable = FALSE;
+ pattern = NULL;
+ object_set = NULL;
+ font_set = NULL;
+
+ if (!gdm_parse_language_name (locale, &language_code, NULL, NULL, NULL))
+ return FALSE;
+
+ charset = FcLangGetCharSet ((FcChar8 *) language_code);
+ if (!charset) {
+ /* fontconfig does not know about this language */
+ is_displayable = TRUE;
+ }
+ else {
+ /* see if any fonts support rendering it */
+ pattern = FcPatternBuild (NULL, FC_LANG, FcTypeString, language_code, NULL);
+
+ if (pattern == NULL)
+ goto done;
+
+ object_set = FcObjectSetCreate ();
+
+ if (object_set == NULL)
+ goto done;
+
+ font_set = FcFontList (NULL, pattern, object_set);
+
+ if (font_set == NULL)
+ goto done;
+
+ is_displayable = (font_set->nfont > 0);
+ }
+
+ done:
+ if (font_set != NULL)
+ FcFontSetDestroy (font_set);
+
+ if (object_set != NULL)
+ FcObjectSetDestroy (object_set);
+
+ if (pattern != NULL)
+ FcPatternDestroy (pattern);
+
+ g_free (language_code);
+
+ return is_displayable;
+}
+
+static void
+add_available_languages (GtkListStore *store)
+{
+ char **languages;
+ int i;
+ char *name;
+ char *language;
+ GtkTreeIter iter;
+
+ gtk_list_store_clear (store);
+
+ languages = gdm_get_all_language_names ();
+
+ for (i = 0; languages[i] != NULL; i++) {
+ if (!language_has_font (languages[i]))
+ continue;
+
+ name = gdm_normalize_language_name (languages[i]);
+ language = gdm_get_language_from_name (name, NULL);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, LOCALE_COL, name, DISPLAY_LOCALE_COL, language, -1);
+
+ g_free (name);
+ g_free (language);
+ }
+
+ g_strfreev (languages);
+}
+
+void
+um_add_user_languages (GtkTreeModel *model)
+{
+ GHashTable *seen;
+ GSList *users, *l;
+ UmUser *user;
+ const char *lang;
+ char *name;
+ char *language;
+ GtkTreeIter iter;
+ UmUserManager *manager;
+ GtkListStore *store = GTK_LIST_STORE (model);
+
+ gtk_list_store_clear (store);
+
+ seen = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ manager = um_user_manager_ref_default ();
+ users = um_user_manager_list_users (manager);
+ g_object_unref (manager);
+
+ for (l = users; l; l = l->next) {
+ user = l->data;
+ lang = um_user_get_language (user);
+ if (!lang || !language_has_font (lang)) {
+ continue;
+ }
+
+ name = gdm_normalize_language_name (lang);
+
+ if (g_hash_table_lookup (seen, name)) {
+ g_free (name);
+ continue;
+ }
+
+ g_hash_table_insert (seen, name, GINT_TO_POINTER (TRUE));
+
+ language = gdm_get_language_from_name (name, NULL);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, LOCALE_COL, name, DISPLAY_LOCALE_COL, language, -1);
+
+ g_free (language);
+ }
+
+ g_slist_free (users);
+
+ /* Make sure the current locale is present */
+ name = um_get_current_language ();
+
+ if (!g_hash_table_lookup (seen, name)) {
+ language = gdm_get_language_from_name (name, NULL);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, LOCALE_COL, name, DISPLAY_LOCALE_COL, language, -1);
+ g_free (language);
+ }
+
+ g_free (name);
+
+ g_hash_table_destroy (seen);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, LOCALE_COL, NULL, DISPLAY_LOCALE_COL, _("Other..."), -1);
+}
+
+gchar *
+um_get_current_language (void)
+{
+ gchar *language;
+ const gchar *locale;
+
+ locale = (const gchar *) setlocale (LC_MESSAGES, NULL);
+ if (locale)
+ language = gdm_normalize_language_name (locale);
+ else
+ language = NULL;
+
+ return language;
+}
+
+GtkWidget *
+um_language_chooser_new (void)
+{
+ GtkBuilder *builder;
+ const char *filename;
+ GError *error = NULL;
+ GtkWidget *chooser;
+ GtkWidget *list;
+ GtkWidget *button;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ GtkListStore *store;
+
+ builder = gtk_builder_new ();
+ filename = UIDIR "/language-chooser.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ filename = "../data/language-chooser.ui";
+ if (!gtk_builder_add_from_file (builder, filename, &error)) {
+ g_warning ("failed to load language chooser: %s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ chooser = (GtkWidget *) gtk_builder_get_object (builder, "dialog");
+
+ list = (GtkWidget *) gtk_builder_get_object (builder, "language-list");
+ g_object_set_data (G_OBJECT (chooser), "list", list);
+ g_signal_connect (list, "row-activated",
+ G_CALLBACK (row_activated), chooser);
+
+ button = (GtkWidget *) gtk_builder_get_object (builder, "cancel-button");
+ button = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+ gtk_widget_grab_default (button);
+
+ cell = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (NULL, cell, "text", DISPLAY_LOCALE_COL, NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);
+ store = gtk_list_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
+ sort_languages, NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+ GTK_SORT_ASCENDING);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (list), GTK_TREE_MODEL (store));
+
+ add_available_languages (store);
+
+ g_object_unref (builder);
+
+ return chooser;
+}
+
+static void
+language_combo_changed (GtkComboBox *combo,
+ UmLanguageDialog *um)
+{
+ GtkTreeIter iter;
+ char *lang;
+
+ if (!gtk_combo_box_get_active_iter (combo, &iter))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (um->dialog_store), &iter, LOCALE_COL, &lang, -1);
+
+ if (lang) {
+ g_free (um->language);
+ um->language = lang;
+ return;
+ }
+
+#if 0
+ if (!um->chooser)
+ setup_language_chooser (um);
+#endif
+
+ gtk_window_present (GTK_WINDOW (um->chooser));
+ gtk_widget_grab_focus (um->chooser_list);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE);
+}
+
+UmLanguageDialog *
+um_language_dialog_new (void)
+{
+ GtkBuilder *builder;
+ GtkWidget *widget;
+ UmLanguageDialog *um;
+ const gchar *filename;
+ GtkListStore *store;
+ GError *error = NULL;
+
+ builder = gtk_builder_new ();
+
+ filename = UIDIR "/language-dialog.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ filename = "../data/language-dialog.ui";
+ if (!gtk_builder_add_from_file (builder, filename, &error)) {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ um = g_new0 (UmLanguageDialog, 1);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dialog");
+ g_signal_connect (widget, "delete-event",
+ G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+ um->dialog = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "cancel-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (cancel_language_dialog), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (accept_language_dialog), um);
+ gtk_widget_grab_default (widget);
+
+ store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
+ sort_languages, NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+ GTK_SORT_ASCENDING);
+ um->dialog_store = store;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "language-combobox");
+ gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (store));
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (language_combo_changed), um);
+ um->dialog_combo = widget;
+
+ um->user_icon = (GtkWidget *) gtk_builder_get_object (builder, "user-icon");
+ um->user_name = (GtkWidget *) gtk_builder_get_object (builder, "user-name");
+
+ return um;
+}
+
+void
+um_language_dialog_free (UmLanguageDialog *um)
+{
+ gtk_widget_destroy (um->dialog);
+
+ if (um->chooser)
+ gtk_widget_destroy (um->chooser);
+
+ g_free (um->language);
+
+ if (um->user)
+ g_object_unref (um->user);
+
+ g_free (um);
+}
+
+void
+um_language_dialog_set_user (UmLanguageDialog *um,
+ UmUser *user)
+{
+ GdkPixbuf *pixbuf;
+ const gchar *name;
+
+ if (um->user) {
+ g_object_unref (um->user);
+ um->user = NULL;
+ }
+ if (um->language) {
+ g_free (um->language);
+ um->language = NULL;
+ }
+ um->force_setting = FALSE;
+
+ um->user = user;
+ if (um->user) {
+ const gchar *language;
+
+ g_object_ref (user);
+
+ pixbuf = um_user_render_icon (user, FALSE, 48);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (um->user_icon), pixbuf);
+ g_object_unref (pixbuf);
+
+ name = um_user_get_real_name (user);
+ gtk_label_set_label (GTK_LABEL (um->user_name), name);
+
+ um_add_user_languages (gtk_combo_box_get_model (GTK_COMBO_BOX (um->dialog_combo)));
+
+ language = um_user_get_language (user);
+ if (language) {
+ select_language (um, language);
+ } else if (um_user_get_uid (user) == getuid ()) {
+ gchar *lang;
+
+ lang = um_get_current_language ();
+ select_language (um, lang);
+ g_free (lang);
+ um->force_setting = TRUE;
+ }
+ }
+}
+
+void
+um_language_dialog_show (UmLanguageDialog *um,
+ GtkWindow *parent)
+{
+ gtk_window_set_transient_for (GTK_WINDOW (um->dialog), parent);
+ gtk_window_present (GTK_WINDOW (um->dialog));
+ gtk_widget_grab_focus (um->dialog_combo);
+}
+
diff --git a/panels/user-accounts/um-language-dialog.h b/panels/user-accounts/um-language-dialog.h
new file mode 100644
index 0000000..5df8af3
--- /dev/null
+++ b/panels/user-accounts/um-language-dialog.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __UM_LANGUAGE_DIALOG_H__
+#define __UM_LANGUAGE_DIALOG_H__
+
+#include <gtk/gtk.h>
+#include "um-user.h"
+
+G_BEGIN_DECLS
+
+typedef struct _UmLanguageDialog UmLanguageDialog;
+
+UmLanguageDialog *um_language_dialog_new (void);
+void um_language_dialog_free (UmLanguageDialog *dialog);
+void um_language_dialog_set_user (UmLanguageDialog *dialog,
+ UmUser *user);
+void um_language_dialog_show (UmLanguageDialog *dialog,
+ GtkWindow *parent);
+void um_add_user_languages (GtkTreeModel *model);
+gchar *um_get_current_language (void);
+gboolean um_get_iter_for_language (GtkTreeModel *model,
+ const gchar *lang,
+ GtkTreeIter *iter);
+
+GtkWidget *um_language_chooser_new (void);
+gchar *um_language_chooser_get_language (GtkWidget *chooser);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-lockbutton.c b/panels/user-accounts/um-lockbutton.c
new file mode 100644
index 0000000..88da58c
--- /dev/null
+++ b/panels/user-accounts/um-lockbutton.c
@@ -0,0 +1,637 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Author: Matthias Clasen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "um-lockbutton.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#define P_(s) s
+
+struct _UmLockButtonPrivate
+{
+ GPermission *permission;
+
+ gchar *text_lock;
+ gchar *text_unlock;
+ gchar *text_not_authorized;
+
+ gchar *tooltip_lock;
+ gchar *tooltip_unlock;
+ gchar *tooltip_not_authorized;
+
+ GtkWidget *box;
+ GtkWidget *eventbox;
+ GtkWidget *image;
+ GtkWidget *button;
+ GtkWidget *notebook;
+
+ GtkWidget *label_lock;
+ GtkWidget *label_unlock;
+ GtkWidget *label_not_authorized;
+
+ GCancellable *cancellable;
+
+ gboolean constructed;
+};
+
+enum
+{
+ PROP_0,
+ PROP_PERMISSION,
+ PROP_TEXT_LOCK,
+ PROP_TEXT_UNLOCK,
+ PROP_TEXT_NOT_AUTHORIZED,
+ PROP_TOOLTIP_LOCK,
+ PROP_TOOLTIP_UNLOCK,
+ PROP_TOOLTIP_NOT_AUTHORIZED
+};
+
+enum
+{
+ CHANGED_SIGNAL,
+ LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = {0, };
+
+static void initiate_check (UmLockButton *button);
+static void do_sync_check (UmLockButton *button);
+static void update_state (UmLockButton *button);
+
+static void on_permission_changed (GPermission *permission,
+ GParamSpec *pspec,
+ gpointer user_data);
+
+static void on_clicked (GtkButton *button,
+ gpointer user_data);
+
+static void on_button_press (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data);
+
+G_DEFINE_TYPE (UmLockButton, um_lock_button, GTK_TYPE_BIN);
+
+static void
+um_lock_button_finalize (GObject *object)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (object);
+ UmLockButtonPrivate *priv = button->priv;
+
+ g_free (priv->text_lock);
+ g_free (priv->text_unlock);
+ g_free (priv->text_not_authorized);
+
+ g_free (priv->tooltip_lock);
+ g_free (priv->tooltip_unlock);
+ g_free (priv->tooltip_not_authorized);
+
+ if (priv->cancellable != NULL)
+ {
+ g_cancellable_cancel (priv->cancellable);
+ g_object_unref (priv->cancellable);
+ }
+
+ g_signal_handlers_disconnect_by_func (priv->permission,
+ on_permission_changed,
+ button);
+
+ g_object_unref (priv->permission);
+
+ G_OBJECT_CLASS (um_lock_button_parent_class)->finalize (object);
+}
+
+static void
+um_lock_button_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (object);
+ UmLockButtonPrivate *priv = button->priv;
+
+ switch (property_id)
+ {
+ case PROP_PERMISSION:
+ g_value_set_object (value, priv->permission);
+ break;
+
+ case PROP_TEXT_LOCK:
+ g_value_set_string (value, priv->text_lock);
+ break;
+
+ case PROP_TEXT_UNLOCK:
+ g_value_set_string (value, priv->text_unlock);
+ break;
+
+ case PROP_TEXT_NOT_AUTHORIZED:
+ g_value_set_string (value, priv->text_not_authorized);
+ break;
+
+ case PROP_TOOLTIP_LOCK:
+ g_value_set_string (value, priv->tooltip_lock);
+ break;
+
+ case PROP_TOOLTIP_UNLOCK:
+ g_value_set_string (value, priv->tooltip_unlock);
+ break;
+
+ case PROP_TOOLTIP_NOT_AUTHORIZED:
+ g_value_set_string (value, priv->tooltip_not_authorized);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+um_lock_button_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (object);
+ UmLockButtonPrivate *priv = button->priv;
+
+ switch (property_id)
+ {
+ case PROP_PERMISSION:
+ priv->permission = g_value_get_object (value);
+ break;
+
+ case PROP_TEXT_LOCK:
+ g_free (priv->text_lock);
+ priv->text_lock = g_value_dup_string (value);
+ break;
+
+ case PROP_TEXT_UNLOCK:
+ g_free (priv->text_unlock);
+ priv->text_unlock = g_value_dup_string (value);
+ break;
+
+ case PROP_TEXT_NOT_AUTHORIZED:
+ g_free (priv->text_not_authorized);
+ priv->text_not_authorized = g_value_dup_string (value);
+ break;
+
+ case PROP_TOOLTIP_LOCK:
+ g_free (priv->tooltip_lock);
+ priv->tooltip_lock = g_value_dup_string (value);
+ break;
+
+ case PROP_TOOLTIP_UNLOCK:
+ g_free (priv->tooltip_unlock);
+ priv->tooltip_unlock = g_value_dup_string (value);
+ break;
+
+ case PROP_TOOLTIP_NOT_AUTHORIZED:
+ g_free (priv->tooltip_not_authorized);
+ priv->tooltip_not_authorized = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+
+ if (priv->constructed)
+ update_state (button);
+}
+
+static void
+um_lock_button_init (UmLockButton *button)
+{
+ button->priv = G_TYPE_INSTANCE_GET_PRIVATE (button,
+ UM_TYPE_LOCK_BUTTON,
+ UmLockButtonPrivate);
+}
+
+static void
+um_lock_button_constructed (GObject *object)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (object);
+ UmLockButtonPrivate *priv = button->priv;
+
+ priv->constructed = TRUE;
+
+ g_signal_connect (priv->permission, "notify",
+ G_CALLBACK (on_permission_changed), button);
+
+ priv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (button), priv->box);
+
+ priv->eventbox = gtk_event_box_new ();
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (priv->eventbox), FALSE);
+ gtk_container_add (GTK_CONTAINER (priv->box), priv->eventbox);
+ gtk_widget_show (priv->eventbox);
+
+ priv->image = gtk_image_new (); /* image is set in update_state() */
+ gtk_container_add (GTK_CONTAINER (priv->eventbox), priv->image);
+ gtk_widget_show (priv->image);
+
+ priv->notebook = gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->notebook), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->notebook), FALSE);
+ gtk_widget_show (priv->notebook);
+
+ priv->button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (priv->button), priv->notebook);
+ gtk_widget_show (priv->button);
+
+ priv->label_lock = gtk_label_new (""); /* text is set in update_state */
+ gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), priv->label_lock, NULL);
+ gtk_widget_show (priv->label_lock);
+
+ priv->label_unlock = gtk_label_new ("");
+ gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), priv->label_unlock, NULL);
+ gtk_widget_show (priv->label_unlock);
+
+ priv->label_not_authorized = gtk_label_new ("");
+ gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), priv->label_not_authorized, NULL);
+ gtk_widget_show (priv->label_not_authorized);
+
+ gtk_box_pack_start (GTK_BOX (priv->box), priv->button, FALSE, FALSE, 0);
+ gtk_widget_show (priv->button);
+
+ g_signal_connect (priv->eventbox, "button-press-event",
+ G_CALLBACK (on_button_press), button);
+ g_signal_connect (priv->button, "clicked",
+ G_CALLBACK (on_clicked), button);
+
+ gtk_widget_set_no_show_all (priv->box, TRUE);
+
+ update_state (button);
+
+ if (G_OBJECT_CLASS (um_lock_button_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (um_lock_button_parent_class)->constructed (object);
+}
+
+static void
+um_lock_button_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ UmLockButtonPrivate *priv = UM_LOCK_BUTTON (widget)->priv;
+
+ gtk_widget_get_preferred_size (priv->box, requisition, NULL);
+}
+
+static void
+um_lock_button_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ UmLockButtonPrivate *priv = UM_LOCK_BUTTON (widget)->priv;
+ GtkRequisition requisition;
+ GtkAllocation child_allocation;
+
+ gtk_widget_set_allocation (widget, allocation);
+ gtk_widget_get_preferred_size (priv->box, &requisition, NULL);
+ child_allocation.x = allocation->x;
+ child_allocation.y = allocation->y;
+ child_allocation.width = requisition.width;
+ child_allocation.height = requisition.height;
+ gtk_widget_size_allocate (priv->box, &child_allocation);
+}
+
+static void
+um_lock_button_class_init (UmLockButtonClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gobject_class->finalize = um_lock_button_finalize;
+ gobject_class->get_property = um_lock_button_get_property;
+ gobject_class->set_property = um_lock_button_set_property;
+ gobject_class->constructed = um_lock_button_constructed;
+
+ widget_class->size_request = um_lock_button_size_request;
+ widget_class->size_allocate = um_lock_button_size_allocate;
+
+ g_type_class_add_private (klass, sizeof (UmLockButtonPrivate));
+
+ g_object_class_install_property (gobject_class, PROP_PERMISSION,
+ g_param_spec_object ("permission",
+ P_("Permission"),
+ P_("The GPermission object controlling this button"),
+ G_TYPE_PERMISSION,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TEXT_LOCK,
+ g_param_spec_string ("text-lock",
+ P_("Lock Text"),
+ P_("The text to display when prompting the user to lock"),
+ _("Lock"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TEXT_UNLOCK,
+ g_param_spec_string ("text-unlock",
+ P_("Unlock Text"),
+ P_("The text to display when prompting the user to unlock"),
+ _("Unlock"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TEXT_NOT_AUTHORIZED,
+ g_param_spec_string ("text-not-authorized",
+ P_("Not Authorized Text"),
+ P_("The text to display when prompting the user cannot obtain authorization"),
+ _("Locked"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TOOLTIP_LOCK,
+ g_param_spec_string ("tooltip-lock",
+ P_("Lock Tooltip"),
+ P_("The tooltip to display when prompting the user to lock"),
+ _("Dialog is unlocked.\nClick to prevent further changes"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TOOLTIP_UNLOCK,
+ g_param_spec_string ("tooltip-unlock",
+ P_("Unlock Tooltip"),
+ P_("The tooltip to display when prompting the user to unlock"),
+ _("Dialog is locked.\nClick to make changes"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TOOLTIP_NOT_AUTHORIZED,
+ g_param_spec_string ("tooltip-not-authorized",
+ P_("Not Authorized Tooltip"),
+ P_("The tooltip to display when prompting the user cannot obtain authorization"),
+ _("System policy prevents changes.\nContact your system administrator"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ signals[CHANGED_SIGNAL] = g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmLockButtonClass, changed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+/**
+ * um_lock_button_new:
+ * @permission: a #GPermission
+ *
+ * Creates a new lock button which reflects the @permission.
+ *
+ * Returns: a new #UmLockButton
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+um_lock_button_new (GPermission *permission)
+{
+ g_return_val_if_fail (permission != NULL, NULL);
+
+ return GTK_WIDGET (g_object_new (UM_TYPE_LOCK_BUTTON,
+ "permission", permission,
+ NULL));
+}
+
+static void
+update_state (UmLockButton *button)
+{
+ UmLockButtonPrivate *priv = button->priv;
+ gint page;
+ const gchar *tooltip;
+ gboolean sensitive;
+ gboolean visible;
+ GIcon *icon;
+
+ visible = TRUE;
+ sensitive = TRUE;
+
+ gtk_label_set_text (GTK_LABEL (priv->label_lock), priv->text_lock);
+ gtk_label_set_text (GTK_LABEL (priv->label_unlock), priv->text_unlock);
+ gtk_label_set_text (GTK_LABEL (priv->label_not_authorized), priv->text_not_authorized);
+
+ if (g_permission_get_allowed (priv->permission))
+ {
+ if (g_permission_get_can_release (priv->permission))
+ {
+ page = 0;
+ tooltip = priv->tooltip_lock;
+ sensitive = TRUE;
+ }
+ else
+ {
+ page = 0;
+ tooltip = "";
+ visible = FALSE;
+ }
+ }
+ else
+ {
+ if (g_permission_get_can_acquire (priv->permission))
+ {
+ page = 1;
+ tooltip = button->priv->tooltip_unlock;
+ sensitive = TRUE;
+ }
+ else
+ {
+ page = 2;
+ tooltip = button->priv->tooltip_not_authorized;
+ sensitive = FALSE;
+ }
+ }
+
+ if (g_permission_get_allowed (priv->permission))
+ {
+ gchar *names[3];
+
+ names[0] = "changes-allow-symbolic";
+ names[1] = "changes-allow";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+ }
+ else
+ {
+ gchar *names[3];
+
+ names[0] = "changes-prevent-symbolic";
+ names[1] = "changes-prevent";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+ }
+
+ gtk_image_set_from_gicon (GTK_IMAGE (priv->image), icon, GTK_ICON_SIZE_BUTTON);
+ g_object_unref (icon);
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), page);
+ gtk_widget_set_tooltip_markup (priv->box, tooltip);
+
+ gtk_widget_set_sensitive (priv->box, sensitive);
+
+ if (visible)
+ gtk_widget_show (priv->box);
+ else
+ gtk_widget_hide (priv->box);
+}
+
+static void
+on_permission_changed (GPermission *permission,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (user_data);
+
+ update_state (button);
+}
+
+static void
+acquire_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (user_data);
+ UmLockButtonPrivate *priv = button->priv;
+ GError *error;
+ gboolean res;
+
+ error = NULL;
+ res = g_permission_acquire_finish (priv->permission, result, &error);
+
+ if (error)
+ {
+ g_warning ("Error acquiring permission: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+
+ update_state (button);
+}
+
+static void
+release_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (user_data);
+ UmLockButtonPrivate *priv = button->priv;
+ GError *error;
+ gboolean res;
+
+ error = NULL;
+ res = g_permission_release_finish (priv->permission, result, &error);
+
+ if (error)
+ {
+ g_warning ("Error releasing permission: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+
+ update_state (button);
+}
+
+static void
+handle_click (UmLockButton *button)
+{
+ UmLockButtonPrivate *priv = button->priv;
+
+ if (!g_permission_get_allowed (priv->permission) &&
+ g_permission_get_can_acquire (priv->permission))
+ {
+ /* if we already have a pending interactive check, then do nothing */
+ if (priv->cancellable != NULL)
+ goto out;
+
+ priv->cancellable = g_cancellable_new ();
+
+ g_permission_acquire_async (priv->permission,
+ priv->cancellable,
+ acquire_cb,
+ button);
+ }
+ else if (g_permission_get_allowed (priv->permission) &&
+ g_permission_get_can_release (priv->permission))
+ {
+ /* if we already have a pending interactive check, then do nothing */
+ if (priv->cancellable != NULL)
+ goto out;
+
+ priv->cancellable = g_cancellable_new ();
+
+ g_permission_release_async (priv->permission,
+ priv->cancellable,
+ release_cb,
+ button);
+ }
+
+ out: ;
+}
+
+static void
+on_clicked (GtkButton *_button,
+ gpointer user_data)
+
+{
+ handle_click (UM_LOCK_BUTTON (user_data));
+}
+
+static void
+on_button_press (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ handle_click (UM_LOCK_BUTTON (user_data));
+}
+
+/**
+ * um_lock_button_get_permission:
+ * @button: a #UmLockButton
+ *
+ * Obtains the #GPermission object that controls @button.
+ *
+ * Returns: the #GPermission of @button
+ *
+ * Since: 3.0
+ */
+GPermission *
+um_lock_button_get_permission (UmLockButton *button)
+{
+ g_return_val_if_fail (UM_IS_LOCK_BUTTON (button), NULL);
+
+ return button->priv->permission;
+}
+
diff --git a/panels/user-accounts/um-lockbutton.h b/panels/user-accounts/um-lockbutton.h
new file mode 100644
index 0000000..6becfbc
--- /dev/null
+++ b/panels/user-accounts/um-lockbutton.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Author: Matthias Clasen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __UM_LOCK_BUTTON_H__
+#define __UM_LOCK_BUTTON_H__
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_LOCK_BUTTON (um_lock_button_get_type ())
+#define UM_LOCK_BUTTON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), UM_TYPE_LOCK_BUTTON, UmLockButton))
+#define UM_LOCK_BUTTON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), UM_LOCK_BUTTON, UmLockButtonClass))
+#define UM_IS_LOCK_BUTTON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), UM_TYPE_LOCK_BUTTON))
+#define UM_IS_LOCK_BUTTON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), UM_TYPE_LOCK_BUTTON))
+#define UM_LOCK_BUTTON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), UM_TYPE_LOCK_BUTTON, UmLockButtonClass))
+
+typedef struct _UmLockButton UmLockButton;
+typedef struct _UmLockButtonClass UmLockButtonClass;
+typedef struct _UmLockButtonPrivate UmLockButtonPrivate;
+
+struct _UmLockButton
+{
+ GtkBin parent;
+
+ UmLockButtonPrivate *priv;
+};
+
+struct _UmLockButtonClass
+{
+ GtkBinClass parent_class;
+
+ void (*changed) (UmLockButton *button);
+
+ void (*reserved0) (void);
+ void (*reserved1) (void);
+ void (*reserved2) (void);
+ void (*reserved3) (void);
+ void (*reserved4) (void);
+ void (*reserved5) (void);
+ void (*reserved6) (void);
+ void (*reserved7) (void);
+};
+
+GType um_lock_button_get_type (void) G_GNUC_CONST;
+GtkWidget *um_lock_button_new (GPermission *permission);
+GPermission *um_lock_button_get_permission (UmLockButton *button);
+
+
+G_END_DECLS
+
+#endif /* __UM_LOCK_BUTTON_H__ */
diff --git a/panels/user-accounts/um-login-options.c b/panels/user-accounts/um-login-options.c
new file mode 100644
index 0000000..99b57ce
--- /dev/null
+++ b/panels/user-accounts/um-login-options.c
@@ -0,0 +1,433 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gconf/gconf-client.h>
+#include <gconf/gconf-value.h>
+#include <polkit/polkit.h>
+
+#include "um-login-options.h"
+#include "um-lockbutton.h"
+#include "um-user-manager.h"
+#include "um-user.h"
+
+struct _UmLoginOptions {
+ GtkWidget *autologin_combo;
+ GtkWidget *userlist_check;
+ GtkWidget *power_check;
+ GtkWidget *hints_check;
+ GtkWidget *guest_check;
+ GtkWidget *lock_button;
+ PolkitPermission *permission;
+
+ UmUserManager *manager;
+
+ DBusGProxy *proxy;
+ DBusGConnection *connection;
+};
+
+enum {
+ AUTOLOGIN_NAME_COL,
+ AUTOLOGIN_USER_COL,
+ NUM_AUTOLOGIN_COLS
+};
+
+static gint
+sort_login_users (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer data)
+{
+ UmUser *ua, *ub;
+ gint result;
+
+ gtk_tree_model_get (model, a, AUTOLOGIN_USER_COL, &ua, -1);
+ gtk_tree_model_get (model, b, AUTOLOGIN_USER_COL, &ub, -1);
+
+ if (ua == NULL)
+ result = -1;
+ else if (ub == NULL)
+ result = 1;
+ else if (um_user_get_uid (ua) == getuid ())
+ result = -1;
+ else if (um_user_get_uid (ub) == getuid ())
+ result = 1;
+ else
+ result = um_user_collate (ua, ub);
+
+ if (ua)
+ g_object_unref (ua);
+
+ if (ub)
+ g_object_unref (ub);
+
+ return result;
+}
+
+static void
+user_added (UmUserManager *um, UmUser *user, UmLoginOptions *d)
+{
+ GtkComboBox *combo;
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ g_debug ("adding user '%s', %d", um_user_get_user_name (user), um_user_get_automatic_login (user));
+ combo = GTK_COMBO_BOX (d->autologin_combo);
+ store = (GtkListStore*)gtk_combo_box_get_model (combo);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ AUTOLOGIN_NAME_COL, um_user_get_display_name (user),
+ AUTOLOGIN_USER_COL, user,
+ -1);
+
+ if (um_user_get_automatic_login (user)) {
+ gtk_combo_box_set_active_iter (combo, &iter);
+ }
+}
+
+static void
+user_removed (UmUserManager *um, UmUser *user, UmLoginOptions *d)
+{
+ GtkComboBox *combo;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ UmUser *u;
+
+ combo = GTK_COMBO_BOX (d->autologin_combo);
+ model = gtk_combo_box_get_model (combo);
+ store = (GtkListStore*)model;
+
+ gtk_combo_box_get_active_iter (combo, &iter);
+ gtk_tree_model_get (model, &iter, AUTOLOGIN_USER_COL, &u, -1);
+ if (u != NULL) {
+ if (um_user_get_uid (user) == um_user_get_uid (u)) {
+ /* autologin user got removed, set back to Disabled */
+ gtk_list_store_remove (store, &iter);
+ gtk_combo_box_set_active (combo, 0);
+ g_object_unref (u);
+ return;
+ }
+ g_object_unref (u);
+ }
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ do {
+ gtk_tree_model_get (model, &iter, AUTOLOGIN_USER_COL, &u, -1);
+
+ if (u != NULL) {
+ if (um_user_get_uid (user) == um_user_get_uid (u)) {
+ gtk_list_store_remove (store, &iter);
+ g_object_unref (u);
+ return;
+ }
+ g_object_unref (u);
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+ }
+}
+
+static void
+user_changed (UmUserManager *manager,
+ UmUser *user,
+ UmLoginOptions *d)
+{
+ /* FIXME */
+}
+
+static void
+users_loaded (UmUserManager *manager,
+ UmLoginOptions *d)
+{
+ GSList *list, *l;
+ UmUser *user;
+
+ list = um_user_manager_list_users (manager);
+ for (l = list; l; l = l->next) {
+ user = l->data;
+ user_added (manager, user, d);
+ }
+ g_slist_free (list);
+
+ g_signal_connect (manager, "user-added", G_CALLBACK (user_added), d);
+ g_signal_connect (manager, "user-removed", G_CALLBACK (user_removed), d);
+ g_signal_connect (manager, "user-changed", G_CALLBACK (user_changed), d);
+}
+
+static void update_login_options (GtkWidget *widget, UmLoginOptions *d);
+
+static void
+update_boolean_from_gconf (GtkWidget *widget,
+ UmLoginOptions *d)
+{
+ gchar *cmdline;
+ gboolean value;
+ gchar *std_out;
+ gchar *std_err;
+ gint status;
+ GError *error;
+ const gchar *key;
+
+ key = g_object_get_data (G_OBJECT (widget), "gconf-key");
+
+ /* GConf fail.
+ * gconfd does not pick up any changes in the default or mandatory
+ * databases at runtime. Even a SIGHUP doesn't seem to help. So we
+ * have to use gconftool to go get the current mandatory values.
+ */
+ cmdline = g_strdup_printf ("gconftool-2 --direct --config-source=\"xml:readonly:/etc/gconf/gconf.xml.defaults;xml:readonly:/etc/gconf/gconf.xml.mandatory\" --get %s", key);
+
+ error = NULL;
+ std_out = NULL;
+ std_err = NULL;
+ if (!g_spawn_command_line_sync (cmdline, &std_out, &std_err, &status, &error)) {
+ g_warning ("Failed to run '%s': %s", cmdline, error->message);
+ g_error_free (error);
+ g_free (cmdline);
+ g_free (std_out);
+ g_free (std_err);
+ return;
+ }
+ if (WEXITSTATUS (status) != 0) {
+ g_warning ("Failed to run '%s': %s", cmdline, std_err);
+ g_free (cmdline);
+ g_free (std_out);
+ g_free (std_err);
+ return;
+ }
+
+ if (std_out[strlen (std_out) - 1] == '\n') {
+ std_out[strlen (std_out) - 1] = 0;
+ }
+
+ if (g_strcmp0 (std_out, "true") == 0) {
+ value = TRUE;
+ }
+ else {
+ value = FALSE;
+ }
+ g_signal_handlers_block_by_func (widget, update_login_options, d);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), !value);
+ g_signal_handlers_unblock_by_func (widget, update_login_options, d);
+
+ g_free (cmdline);
+ g_free (std_out);
+ g_free (std_err);
+}
+
+static void
+update_login_options (GtkWidget *widget,
+ UmLoginOptions *d)
+{
+ GError *error;
+ gboolean active;
+ GConfValue *value;
+ const gchar *key = NULL;
+ gchar *value_string;
+
+ if (widget == d->userlist_check ||
+ widget == d->power_check) {
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+ key = g_object_get_data (G_OBJECT (widget), "gconf-key");
+ }
+ else {
+ g_warning ("unhandled option in update_login_options");
+ return;
+ }
+
+ error = NULL;
+ value = gconf_value_new (GCONF_VALUE_BOOL);
+ gconf_value_set_bool (value, !active);
+ value_string = gconf_value_encode (value);
+ if (!dbus_g_proxy_call (d->proxy, "SetMandatoryValue",
+ &error,
+ G_TYPE_STRING, key,
+ G_TYPE_STRING, value_string,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("error calling SetMandatoryValue: %s\n", error->message);
+ g_error_free (error);
+ }
+ g_free (value_string);
+ gconf_value_free (value);
+ update_boolean_from_gconf (widget, d);
+}
+
+static void
+update_autologin (GtkWidget *widget,
+ UmLoginOptions *d)
+{
+ GtkComboBox *combo = GTK_COMBO_BOX (widget);
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ UmUser *user;
+ gboolean enabled;
+
+ if (!gtk_widget_is_sensitive (widget))
+ return;
+
+ model = gtk_combo_box_get_model (combo);
+ gtk_combo_box_get_active_iter (combo, &iter);
+ gtk_tree_model_get (model, &iter, AUTOLOGIN_USER_COL, &user, -1);
+ if (user) {
+ enabled = TRUE;
+ }
+ else {
+ enabled = FALSE;
+ user = um_user_manager_get_user_by_id (d->manager, getuid ());
+ g_object_ref (user);
+ }
+
+ um_user_set_automatic_login (user, enabled);
+
+ g_object_unref (user);
+}
+
+static void
+lockbutton_changed (UmLockButton *button,
+ gpointer data)
+{
+ UmLoginOptions *d = data;
+ gboolean authorized;
+
+ authorized = g_permission_get_allowed (G_PERMISSION (d->permission));
+
+ gtk_widget_set_sensitive (d->autologin_combo, authorized);
+ gtk_widget_set_sensitive (d->userlist_check, authorized);
+ gtk_widget_set_sensitive (d->power_check, authorized);
+ gtk_widget_set_sensitive (d->hints_check, authorized);
+ gtk_widget_set_sensitive (d->guest_check, authorized);
+}
+
+UmLoginOptions *
+um_login_options_new (GtkBuilder *builder)
+{
+ GtkWidget *widget;
+ GtkWidget *box;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GError *error;
+ UmLoginOptions *um;
+
+ /* TODO: get actual login screen options */
+
+ um = g_new0 (UmLoginOptions, 1);
+
+ um->manager = um_user_manager_ref_default ();
+ g_signal_connect (um->manager, "users-loaded",
+ G_CALLBACK (users_loaded), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dm-automatic-login-combobox");
+ um->autologin_combo = widget;
+
+ store = gtk_list_store_new (2, G_TYPE_STRING, UM_TYPE_USER);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (store));
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ AUTOLOGIN_NAME_COL, _("Disabled"),
+ AUTOLOGIN_USER_COL, NULL,
+ -1);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
+
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store), sort_login_users, NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
+
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (update_autologin), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dm-show-user-list-checkbutton");
+ um->userlist_check = widget;
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (update_login_options), um);
+ g_object_set_data (G_OBJECT (widget), "gconf-key",
+ "/apps/gdm/simple-greeter/disable_user_list");
+ update_boolean_from_gconf (widget, um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dm-show-power-buttons-checkbutton");
+ um->power_check = widget;
+ g_signal_connect(widget, "toggled",
+ G_CALLBACK (update_login_options), um);
+ g_object_set_data (G_OBJECT (widget), "gconf-key",
+ "/apps/gdm/simple-greeter/disable_restart_buttons");
+ update_boolean_from_gconf (widget, um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dm-show-password-hints-checkbutton");
+ um->hints_check = widget;
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (update_login_options), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dm-allow-guest-login-checkbutton");
+ um->guest_check = widget;
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (update_login_options), um);
+
+ um->permission = polkit_permission_new_sync ("org.freedesktop.accounts.set-login-option", NULL, NULL, NULL);
+ widget = um_lock_button_new (um->permission);
+ gtk_widget_show (widget);
+ box = (GtkWidget *)gtk_builder_get_object (builder, "lockbutton-alignment");
+ gtk_container_add (GTK_CONTAINER (box), widget);
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (lockbutton_changed), um);
+ lockbutton_changed (UM_LOCK_BUTTON (widget), um);
+ um->lock_button = widget;
+
+ error = NULL;
+ um->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+ if (error != NULL) {
+ g_warning ("Failed to get system bus connection: %s", error->message);
+ g_error_free (error);
+ }
+
+ um->proxy = dbus_g_proxy_new_for_name (um->connection,
+ "org.gnome.GConf.Defaults",
+ "/",
+ "org.gnome.GConf.Defaults");
+
+ if (um->proxy == NULL) {
+ g_warning ("Cannot connect to GConf defaults mechanism");
+ }
+
+ return um;
+}
+
+void
+um_login_options_free (UmLoginOptions *um)
+{
+ if (um->manager)
+ g_object_unref (um->manager);
+ if (um->proxy)
+ g_object_unref (um->proxy);
+ if (um->connection)
+ dbus_g_connection_unref (um->connection);
+
+ g_free (um);
+}
+
diff --git a/panels/user-accounts/um-login-options.h b/panels/user-accounts/um-login-options.h
new file mode 100644
index 0000000..949e0c4
--- /dev/null
+++ b/panels/user-accounts/um-login-options.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __UM_LOGIN_OPTIONS_H__
+#define __UM_LOGIN_OPTIONS_H__
+
+G_BEGIN_DECLS
+
+typedef struct _UmLoginOptions UmLoginOptions;
+
+UmLoginOptions *um_login_options_new (GtkBuilder *builder);
+void um_login_options_free (UmLoginOptions *options);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-password-dialog.c b/panels/user-accounts/um-password-dialog.c
new file mode 100644
index 0000000..bd6ad8c
--- /dev/null
+++ b/panels/user-accounts/um-password-dialog.c
@@ -0,0 +1,834 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "um-password-dialog.h"
+#include "um-user-manager.h"
+#include "um-strength-bar.h"
+#include "um-utils.h"
+#include "run-passwd.h"
+
+#define MIN_PASSWORD_LEN 6
+
+struct _UmPasswordDialog {
+ GtkWidget *dialog;
+ GtkWidget *user_icon;
+ GtkWidget *user_name;
+ GtkWidget *action_label;
+ GtkWidget *action_combo;
+ GtkWidget *password_entry;
+ GtkWidget *verify_entry;
+ GtkWidget *strength_indicator;
+ GtkWidget *strength_indicator_label;
+ GtkWidget *normal_hint_entry;
+ GtkWidget *normal_hint_label;
+ GtkWidget *generate_button;
+ GtkWidget *generate_menu;
+ GtkWidget *show_password_button;
+ GtkWidget *ok_button;
+
+ UmUser *user;
+
+ GtkWidget *old_password_label;
+ GtkWidget *old_password_entry;
+ gboolean old_password_ok;
+
+ PasswdHandler *passwd_handler;
+};
+
+static void
+generate_clicked (GtkButton *button,
+ UmPasswordDialog *um)
+{
+ gtk_menu_popup (GTK_MENU (um->generate_menu),
+ NULL, NULL,
+ (GtkMenuPositionFunc) popup_menu_below_button, um->generate_button,
+ 0, gtk_get_current_event_time ());
+
+ gtk_widget_set_has_tooltip (um->generate_button, FALSE);
+}
+
+static void
+generate_draw (GtkWidget *widget,
+ cairo_t *cr,
+ UmPasswordDialog *um)
+{
+ GtkAllocation allocation;
+
+ if (!gtk_widget_is_sensitive (widget))
+ return;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_paint_expander (gtk_widget_get_style (widget),
+ cr,
+ gtk_widget_get_state (widget),
+ widget,
+ NULL,
+ allocation.x + allocation.width,
+ allocation.y + allocation.height,
+ GTK_EXPANDER_EXPANDED);
+}
+
+static void
+activate_password_item (GtkMenuItem *item,
+ UmPasswordDialog *um)
+{
+ const char *password;
+
+ password = gtk_menu_item_get_label (item);
+
+ gtk_entry_set_text (GTK_ENTRY (um->password_entry), password);
+ gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (um->show_password_button), TRUE);
+ gtk_widget_grab_focus (um->verify_entry);
+}
+
+static void generate_passwords (UmPasswordDialog *um);
+
+static void
+activate_generate_item (GtkMenuItem *item,
+ UmPasswordDialog *um)
+{
+ generate_passwords (um);
+ generate_clicked (GTK_BUTTON (um->generate_button), um);
+}
+
+static void
+on_generate_menu_unmap (GtkWidget *menu,
+ UmPasswordDialog *um)
+{
+ gtk_widget_set_has_tooltip (um->generate_button, TRUE);
+}
+
+static void
+generate_passwords (UmPasswordDialog *um)
+{
+ gint min_len, max_len;
+ gchar *output, *err, *cmdline;
+ gint status;
+ GError *error;
+ gchar **lines;
+ gint i;
+ GtkWidget *item;
+
+ min_len = 6;
+ max_len = 12;
+
+ if (um->generate_menu) {
+ gtk_widget_destroy (um->generate_menu);
+ }
+
+ um->generate_menu = gtk_menu_new ();
+ g_signal_connect (um->generate_menu, "unmap",
+ G_CALLBACK (on_generate_menu_unmap), um);
+
+ cmdline = g_strdup_printf ("apg -n 6 -M SNC -m %d -x %d", min_len, max_len);
+ error = NULL;
+ output = NULL;
+ err = NULL;
+ if (!g_spawn_command_line_sync (cmdline, &output, &err, &status, &error)) {
+ g_warning ("Failed to run apg: %s", error->message);
+ g_error_free (error);
+ }
+
+ if (WEXITSTATUS (status) == 0) {
+ lines = g_strsplit (output, "\n", 0);
+ for (i = 0; lines[i]; i++) {
+ if (lines[i][0] == 0)
+ continue;
+
+ item = gtk_menu_item_new_with_label (lines[i]);
+ g_signal_connect (item, "activate",
+ G_CALLBACK (activate_password_item), um);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (um->generate_menu), item);
+ }
+ g_strfreev (lines);
+ }
+ else {
+ g_warning ("agp returned an error: %s", err);
+ }
+
+ g_free (cmdline);
+ g_free (output);
+ g_free (err);
+
+ item = gtk_separator_menu_item_new ();
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (um->generate_menu), item);
+
+ item = gtk_menu_item_new_with_label (_("More choices..."));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (activate_generate_item), um);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (um->generate_menu), item);
+}
+
+/* This code is based on the Master Password dialog in Firefox
+ * (pref-masterpass.js)
+ * Original code triple-licensed under the MPL, GPL, and LGPL
+ * so is license-compatible with this file
+ */
+static gdouble
+compute_password_strength (const gchar *password)
+{
+ gint length;
+ gint upper, lower, digit, misc;
+ gint i;
+ gdouble strength;
+
+ length = strlen (password);
+ upper = 0;
+ lower = 0;
+ digit = 0;
+ misc = 0;
+
+ for (i = 0; i < length ; i++) {
+ if (g_ascii_isdigit (password[i]))
+ digit++;
+ else if (g_ascii_islower (password[i]))
+ lower++;
+ else if (g_ascii_isupper (password[i]))
+ upper++;
+ else
+ misc++;
+ }
+
+ if (length > 5)
+ length = 5;
+
+ if (digit > 3)
+ digit = 3;
+
+ if (upper > 3)
+ upper = 3;
+
+ if (misc > 3)
+ misc = 3;
+
+ strength = ((length * 0.1) - 0.2) +
+ (digit * 0.1) +
+ (misc * 0.15) +
+ (upper * 0.1);
+
+ strength = CLAMP (strength, 0.0, 1.0);
+
+ return strength;
+}
+
+static void
+finish_password_change (UmPasswordDialog *um)
+{
+ gtk_widget_hide (um->dialog);
+
+ gtk_entry_set_text (GTK_ENTRY (um->password_entry), " ");
+ gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (um->normal_hint_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (um->old_password_entry), "");
+
+ um_password_dialog_set_user (um, NULL);
+}
+
+static void
+cancel_password_dialog (GtkButton *button,
+ UmPasswordDialog *um)
+{
+ finish_password_change (um);
+}
+
+static void
+dialog_closed (GtkWidget *dialog,
+ gint response_id,
+ UmPasswordDialog *um)
+{
+ gtk_widget_destroy (dialog);
+}
+
+static void
+password_changed_cb (PasswdHandler *handler,
+ GError *error,
+ UmPasswordDialog *um)
+{
+ GtkWidget *dialog;
+ const gchar *primary_text;
+ const gchar *secondary_text;
+
+ gtk_widget_set_sensitive (um->dialog, TRUE);
+ gdk_window_set_cursor (gtk_widget_get_window (um->dialog), NULL);
+
+ if (!error) {
+ finish_password_change (um);
+ return;
+ }
+
+ if (error->code == PASSWD_ERROR_REJECTED) {
+ primary_text = error->message;
+ secondary_text = _("Please choose another password.");
+
+ gtk_entry_set_text (GTK_ENTRY (um->password_entry), "");
+ gtk_widget_grab_focus (um->password_entry);
+
+ gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
+ }
+ else if (error->code == PASSWD_ERROR_AUTH_FAILED) {
+ primary_text = error->message;
+ secondary_text = _("Please type your current password again.");
+
+ gtk_entry_set_text (GTK_ENTRY (um->old_password_entry), "");
+ gtk_widget_grab_focus (um->old_password_entry);
+ }
+ else {
+ primary_text = _("Password could not be changed");
+ secondary_text = error->message;
+ }
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (um->dialog),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s", primary_text);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", secondary_text);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (dialog_closed), um);
+ gtk_window_present (GTK_WINDOW (dialog));
+
+}
+
+static void
+accept_password_dialog (GtkButton *button,
+ UmPasswordDialog *um)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gint mode;
+ const gchar *hint;
+ const gchar *password;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->action_combo));
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (um->action_combo), &iter);
+ gtk_tree_model_get (model, &iter, 1, &mode, -1);
+
+ password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
+ hint = gtk_entry_get_text (GTK_ENTRY (um->normal_hint_entry));
+
+ if (mode == 0 && um_user_get_uid (um->user) == getuid ()) {
+ GdkDisplay *display;
+ GdkCursor *cursor;
+
+ /* When setting a password for the current user,
+ * use passwd directly, to preserve the audit trail
+ * and to e.g. update the keyring password.
+ */
+ passwd_change_password (um->passwd_handler, password, (PasswdCallback) password_changed_cb, um);
+ gtk_widget_set_sensitive (um->dialog, FALSE);
+ display = gtk_widget_get_display (um->dialog);
+ cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
+ gdk_window_set_cursor (gtk_widget_get_window (um->dialog), cursor);
+ gdk_display_flush (display);
+ gdk_cursor_unref (cursor);
+ }
+ else {
+ um_user_set_password (um->user, mode, password, hint);
+ finish_password_change (um);
+ }
+}
+
+static void
+update_sensitivity (UmPasswordDialog *um)
+{
+ const gchar *password, *verify;
+ const gchar *old_password;
+ const gchar *tooltip;
+ gboolean can_change;
+
+ password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
+ verify = gtk_entry_get_text (GTK_ENTRY (um->verify_entry));
+ old_password = gtk_entry_get_text (GTK_ENTRY (um->old_password_entry));
+
+ /* TODO: configurable policies for acceptable passwords */
+ if (strlen (password) < MIN_PASSWORD_LEN) {
+ can_change = FALSE;
+ if (password[0] == '\0') {
+ tooltip = _("You need to enter a new password");
+ }
+ else {
+ tooltip = _("The new password is too short");
+ }
+ }
+ else if (strcmp (password, verify) != 0) {
+ can_change = FALSE;
+ if (verify[0] == '\0') {
+ tooltip = _("You need to confirm the password");
+ }
+ else {
+ tooltip = _("The passwords do not match");
+ }
+ }
+ else if (!um->old_password_ok) {
+ can_change = FALSE;
+ if (old_password[0] == '\0') {
+ tooltip = _("You need to enter your current password");
+ }
+ else {
+ tooltip = _("The current password is not correct");
+ }
+ }
+ else {
+ can_change = TRUE;
+ tooltip = NULL;
+ }
+
+ gtk_widget_set_sensitive (um->ok_button, can_change);
+ gtk_widget_set_tooltip_text (um->ok_button, tooltip);
+}
+
+static void
+action_changed (GtkComboBox *combo,
+ UmPasswordDialog *um)
+{
+ gint active;
+
+ active = gtk_combo_box_get_active (combo);
+ if (active == 0) {
+ gtk_widget_set_sensitive (um->password_entry, TRUE);
+ gtk_widget_set_sensitive (um->generate_button, TRUE);
+ gtk_widget_set_has_tooltip (um->generate_button, TRUE);
+ gtk_widget_set_sensitive (um->verify_entry, TRUE);
+ gtk_widget_set_sensitive (um->old_password_entry, TRUE);
+ gtk_widget_set_sensitive (um->normal_hint_entry, TRUE);
+ gtk_widget_set_sensitive (um->normal_hint_label, TRUE);
+ gtk_widget_set_sensitive (um->strength_indicator_label, TRUE);
+ gtk_widget_set_sensitive (um->show_password_button, TRUE);
+
+ update_sensitivity (um);
+ }
+ else {
+ gtk_widget_set_sensitive (um->password_entry, FALSE);
+ gtk_widget_set_sensitive (um->generate_button, FALSE);
+ gtk_widget_set_has_tooltip (um->generate_button, FALSE);
+ gtk_widget_set_sensitive (um->verify_entry, FALSE);
+ gtk_widget_set_sensitive (um->old_password_entry, FALSE);
+ gtk_widget_set_sensitive (um->normal_hint_entry, FALSE);
+ gtk_widget_set_sensitive (um->normal_hint_label, FALSE);
+ gtk_widget_set_sensitive (um->strength_indicator_label, FALSE);
+ gtk_widget_set_sensitive (um->show_password_button, FALSE);
+ gtk_widget_set_sensitive (um->ok_button, TRUE);
+ }
+}
+
+static void
+show_password_toggled (GtkToggleButton *button,
+ UmPasswordDialog *um)
+{
+ gboolean active;
+
+ active = gtk_toggle_button_get_active (button);
+ gtk_entry_set_visibility (GTK_ENTRY (um->password_entry), active);
+ gtk_entry_set_visibility (GTK_ENTRY (um->verify_entry), active);
+}
+
+static void
+update_password_strength (UmPasswordDialog *um)
+{
+ const gchar *password;
+ gdouble strength;
+ const gchar *hint;
+
+ password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
+
+ strength = compute_password_strength (password);
+
+ if (strlen (password) < MIN_PASSWORD_LEN) {
+ strength = 0.0;
+ if (password[0] == '\0')
+ hint = "";
+ else
+ hint = C_("Password strength", "Too short");
+ }
+ else if (strength < 0.50)
+ hint = C_("Password strength", "Weak");
+ else if (strength < 0.75)
+ hint = C_("Password strength", "Fair");
+ else if (strength < 0.90)
+ hint = C_("Password strength", "Good");
+ else
+ hint = C_("Password strength", "Strong");
+
+ um_strength_bar_set_strength (UM_STRENGTH_BAR (um->strength_indicator), strength);
+ gtk_label_set_label (GTK_LABEL (um->strength_indicator_label), hint);
+}
+
+static void
+password_entry_changed (GtkEntry *entry,
+ GParamSpec *pspec,
+ UmPasswordDialog *um)
+{
+ update_password_strength (um);
+ update_sensitivity (um);
+}
+
+static void
+verify_entry_changed (GtkEntry *entry,
+ GParamSpec *pspec,
+ UmPasswordDialog *um)
+{
+ clear_entry_validation_error (GTK_ENTRY (entry));
+ update_password_strength (um);
+ update_sensitivity (um);
+}
+
+static gboolean
+verify_entry_focus_out (GtkWidget *entry,
+ GdkEventFocus *event,
+ UmPasswordDialog *um)
+{
+ const char *password;
+ const char *verify;
+
+ password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
+ verify = gtk_entry_get_text (GTK_ENTRY (um->verify_entry));
+
+ if (strlen (password) > 0 && strlen (verify) > 0) {
+ if (strcmp (password, verify) != 0) {
+ set_entry_validation_error (GTK_ENTRY (um->verify_entry),
+ _("Passwords do not match"));
+ }
+ else {
+ clear_entry_validation_error (GTK_ENTRY (um->verify_entry));
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+entry_size_changed (GtkWidget *entry,
+ GtkAllocation *allocation,
+ GtkWidget *label)
+{
+ gtk_widget_set_size_request (label, allocation->width, -1);
+}
+
+static void
+auth_cb (PasswdHandler *handler,
+ GError *error,
+ UmPasswordDialog *um)
+{
+ if (error) {
+ um->old_password_ok = FALSE;
+ set_entry_validation_error (GTK_ENTRY (um->old_password_entry),
+ _("Wrong password"));
+ }
+ else {
+ um->old_password_ok = TRUE;
+ clear_entry_validation_error (GTK_ENTRY (um->old_password_entry));
+ }
+
+ update_sensitivity (um);
+}
+
+static gboolean
+old_password_entry_focus_out (GtkWidget *entry,
+ GdkEventFocus *event,
+ UmPasswordDialog *um)
+{
+ const char *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ if (strlen (text) > 0) {
+ passwd_authenticate (um->passwd_handler, text,
+ (PasswdCallback)auth_cb, um);
+ }
+
+ return FALSE;
+}
+
+static void
+old_password_entry_activate (GtkWidget *entry,
+ UmPasswordDialog *um)
+{
+ const char *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ if (strlen (text) > 0) {
+ passwd_authenticate (um->passwd_handler, text,
+ (PasswdCallback)auth_cb, um);
+ }
+}
+
+
+static void
+old_password_entry_changed (GtkEntry *entry,
+ GParamSpec *pspec,
+ UmPasswordDialog *um)
+{
+ clear_entry_validation_error (GTK_ENTRY (entry));
+ um->old_password_ok = FALSE;
+ update_sensitivity (um);
+}
+
+void
+um_password_dialog_set_privileged (UmPasswordDialog *um,
+ gboolean privileged)
+{
+ if (privileged) {
+ gtk_widget_set_visible (um->action_label, TRUE);
+ gtk_widget_set_visible (um->action_combo, TRUE);
+ }
+ else {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (um->action_combo), 0);
+ gtk_widget_set_visible (um->action_label, FALSE);
+ gtk_widget_set_visible (um->action_combo, FALSE);
+ }
+}
+
+UmPasswordDialog *
+um_password_dialog_new (void)
+{
+ GtkBuilder *builder;
+ GError *error;
+ const gchar *filename;
+ UmPasswordDialog *um;
+ GtkWidget *widget;
+ gint len;
+
+ builder = gtk_builder_new ();
+
+ error = NULL;
+ filename = UIDIR "/password-dialog.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ filename = "../data/password-dialog.ui";
+ if (!gtk_builder_add_from_file (builder, filename, &error)) {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ um = g_new0 (UmPasswordDialog, 1);
+
+ um->action_label = (GtkWidget *) gtk_builder_get_object (builder, "action-label");
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "action-combo");
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (action_changed), um);
+ um->action_combo = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dialog");
+ g_signal_connect (widget, "delete-event",
+ G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+ um->dialog = widget;
+
+ um->user_icon = (GtkWidget *) gtk_builder_get_object (builder, "user-icon");
+ um->user_name = (GtkWidget *) gtk_builder_get_object (builder, "user-name");
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "cancel-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (cancel_password_dialog), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (accept_password_dialog), um);
+ gtk_widget_grab_default (widget);
+ um->ok_button = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "show-password-checkbutton");
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (show_password_toggled), um);
+ um->show_password_button = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "password-entry");
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (password_entry_changed), um);
+ gtk_entry_set_visibility (GTK_ENTRY (widget), FALSE);
+
+ um->password_entry = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "old-password-entry");
+ g_signal_connect_after (widget, "focus-out-event",
+ G_CALLBACK (old_password_entry_focus_out), um);
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (old_password_entry_changed), um);
+ g_signal_connect (widget, "activate",
+ G_CALLBACK (old_password_entry_activate), um);
+ um->old_password_entry = widget;
+ um->old_password_label = (GtkWidget *) gtk_builder_get_object (builder, "old-password-label");
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "verify-entry");
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (verify_entry_changed), um);
+ g_signal_connect_after (widget, "focus-out-event",
+ G_CALLBACK (verify_entry_focus_out), um);
+ um->verify_entry = widget;
+
+ len = 0;
+ len = MAX (len, strlen (C_("Password strength", "Too short")));
+ len = MAX (len, strlen (C_("Password strength", "Weak")));
+ len = MAX (len, strlen (C_("Password strength", "Fair")));
+ len = MAX (len, strlen (C_("Password strength", "Good")));
+ len = MAX (len, strlen (C_("Password strength", "Strong")));
+ len += 2;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "strength-indicator-label");
+ gtk_label_set_width_chars (GTK_LABEL (widget), len);
+
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "generate-again-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (generate_clicked), um);
+#if 0
+ g_signal_connect (widget, "state-changed",
+ G_CALLBACK (generate_state_changed), um);
+#endif
+ um->generate_button = widget;
+ g_signal_connect_after (gtk_bin_get_child (GTK_BIN (widget)), "draw",
+ G_CALLBACK (generate_draw), um);
+
+ um->normal_hint_entry = (GtkWidget *) gtk_builder_get_object (builder, "normal-hint-entry");
+
+ /* Label size hack.
+ * This only sort-of works because the dialog is non-resizable.
+ */
+ widget = (GtkWidget *)gtk_builder_get_object (builder, "password-normal-hint-description-label");
+ g_signal_connect (um->normal_hint_entry, "size-allocate",
+ G_CALLBACK (entry_size_changed), widget);
+ um->normal_hint_label = widget;
+
+ um->strength_indicator = (GtkWidget *) gtk_builder_get_object (builder, "strength-indicator");
+
+ um->strength_indicator_label = (GtkWidget *) gtk_builder_get_object (builder, "strength-indicator-label");
+
+ g_object_unref (builder);
+
+ generate_passwords (um);
+
+ return um;
+}
+
+void
+um_password_dialog_free (UmPasswordDialog *um)
+{
+ gtk_widget_destroy (um->dialog);
+
+ if (um->user)
+ g_object_unref (um->user);
+
+ if (um->passwd_handler)
+ passwd_destroy (um->passwd_handler);
+
+ g_free (um);
+}
+
+static gboolean
+visible_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ UmPasswordDialog *um)
+{
+ if (um->user) {
+ gint mode;
+ gboolean locked = um_user_get_locked (um->user);
+
+ gtk_tree_model_get (model, iter, 1, &mode, -1);
+
+ if (mode == 3 && locked)
+ return FALSE;
+
+ if (mode == 4 && !locked)
+ return FALSE;
+
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+void
+um_password_dialog_set_user (UmPasswordDialog *um,
+ UmUser *user)
+{
+ GdkPixbuf *pixbuf;
+ GtkTreeModel *model;
+
+ if (um->user) {
+ g_object_unref (um->user);
+ um->user = NULL;
+ }
+ if (user) {
+ um->user = g_object_ref (user);
+
+ pixbuf = um_user_render_icon (user, FALSE, 48);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (um->user_icon), pixbuf);
+ g_object_unref (pixbuf);
+
+ gtk_label_set_label (GTK_LABEL (um->user_name),
+ um_user_get_real_name (user));
+
+ gtk_entry_set_text (GTK_ENTRY (um->password_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (um->normal_hint_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (um->old_password_entry), "");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (um->show_password_button), FALSE);
+ if (um_user_get_uid (um->user) == getuid()) {
+ gtk_widget_show (um->old_password_label);
+ gtk_widget_show (um->old_password_entry);
+ if (um->passwd_handler != NULL)
+ passwd_destroy (um->passwd_handler);
+ um->passwd_handler = passwd_init ();
+ um->old_password_ok = FALSE;
+ }
+ else {
+ gtk_widget_hide (um->old_password_label);
+ gtk_widget_hide (um->old_password_entry);
+ um->old_password_ok = TRUE;
+ }
+ }
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->action_combo));
+ if (!GTK_IS_TREE_MODEL_FILTER (model)) {
+ model = gtk_tree_model_filter_new (model, NULL);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (um->action_combo), model);
+ gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (model),
+ (GtkTreeModelFilterVisibleFunc) visible_func,
+ um, NULL);
+ }
+
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
+ gtk_combo_box_set_active (GTK_COMBO_BOX (um->action_combo), 0);
+}
+
+void
+um_password_dialog_show (UmPasswordDialog *um,
+ GtkWindow *parent)
+{
+ gtk_window_set_transient_for (GTK_WINDOW (um->dialog), parent);
+ gtk_window_present (GTK_WINDOW (um->dialog));
+ gtk_widget_grab_focus (um->password_entry);
+}
+
diff --git a/panels/user-accounts/um-password-dialog.h b/panels/user-accounts/um-password-dialog.h
new file mode 100644
index 0000000..8354e7c
--- /dev/null
+++ b/panels/user-accounts/um-password-dialog.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __UM_PASSWORD_DIALOG_H__
+#define __UM_PASSWORD_DIALOG_H__
+
+#include <gtk/gtk.h>
+#include "um-user.h"
+
+G_BEGIN_DECLS
+
+typedef struct _UmPasswordDialog UmPasswordDialog;
+
+UmPasswordDialog *um_password_dialog_new (void);
+void um_password_dialog_free (UmPasswordDialog *dialog);
+void um_password_dialog_set_user (UmPasswordDialog *dialog,
+ UmUser *user);
+void um_password_dialog_set_privileged (UmPasswordDialog *dialog,
+ gboolean privileged);
+void um_password_dialog_show (UmPasswordDialog *dialog,
+ GtkWindow *parent);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-photo-dialog.c b/panels/user-accounts/um-photo-dialog.c
new file mode 100644
index 0000000..2061ebe
--- /dev/null
+++ b/panels/user-accounts/um-photo-dialog.c
@@ -0,0 +1,693 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnomeui/gnome-desktop-thumbnail.h>
+
+#ifdef HAVE_CHEESE
+#include <cheese-avatar-chooser.h>
+#include <cheese-camera-device-monitor.h>
+#endif /* HAVE_CHEESE */
+
+#include "um-photo-dialog.h"
+#include "um-user-manager.h"
+#include "um-crop-area.h"
+#include "um-utils.h"
+
+#define ROW_SPAN 6
+
+struct _UmPhotoDialog {
+ GtkWidget *photo_popup;
+ GtkWidget *popup_button;
+ GtkWidget *crop_area;
+
+#ifdef HAVE_CHEESE
+ CheeseCameraDeviceMonitor *monitor;
+ GtkWidget *take_photo_menuitem;
+ guint num_cameras;
+#endif /* HAVE_CHEESE */
+
+ GnomeDesktopThumbnailFactory *thumb_factory;
+
+ UmUser *user;
+};
+
+static void
+crop_dialog_response (GtkWidget *dialog,
+ gint response_id,
+ UmPhotoDialog *um)
+{
+ GdkPixbuf *pb, *pb2;
+
+ if (response_id != GTK_RESPONSE_ACCEPT) {
+ um->crop_area = NULL;
+ gtk_widget_destroy (dialog);
+ return;
+ }
+
+ pb = um_crop_area_get_picture (UM_CROP_AREA (um->crop_area));
+ pb2 = gdk_pixbuf_scale_simple (pb, 96, 96, GDK_INTERP_BILINEAR);
+
+ um_user_set_icon_data (um->user, pb2);
+
+ g_object_unref (pb2);
+ g_object_unref (pb);
+
+ um->crop_area = NULL;
+ gtk_widget_destroy (dialog);
+}
+
+static void
+um_photo_dialog_crop (UmPhotoDialog *um,
+ GdkPixbuf *pixbuf)
+{
+ GtkWidget *dialog;
+ GtkWidget *frame;
+
+ dialog = gtk_dialog_new_with_buttons ("",
+ GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)),
+ 0,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_REJECT,
+ _("Select"),
+ GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users");
+
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (crop_dialog_response), um);
+
+ /* Content */
+ um->crop_area = um_crop_area_new ();
+ um_crop_area_set_min_size (UM_CROP_AREA (um->crop_area), 48, 48);
+ um_crop_area_set_constrain_aspect (UM_CROP_AREA (um->crop_area), TRUE);
+ um_crop_area_set_picture (UM_CROP_AREA (um->crop_area), pixbuf);
+ frame = gtk_frame_new (NULL);
+ gtk_container_add (GTK_CONTAINER (frame), um->crop_area);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ frame,
+ TRUE, TRUE, 8);
+
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 300);
+
+ gtk_widget_show_all (dialog);
+}
+
+static void
+file_chooser_response (GtkDialog *chooser,
+ gint response,
+ UmPhotoDialog *um)
+{
+ gchar *filename;
+ GError *error;
+ GdkPixbuf *pixbuf;
+
+ if (response != GTK_RESPONSE_ACCEPT) {
+ gtk_widget_destroy (GTK_WIDGET (chooser));
+ return;
+ }
+
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser));
+
+ error = NULL;
+ pixbuf = gdk_pixbuf_new_from_file (filename, &error);
+ if (pixbuf == NULL) {
+ g_warning ("Failed to load %s: %s", filename, error->message);
+ g_error_free (error);
+ }
+ g_free (filename);
+
+ gtk_widget_destroy (GTK_WIDGET (chooser));
+
+ um_photo_dialog_crop (um, pixbuf);
+ g_object_unref (pixbuf);
+}
+
+static void
+update_preview (GtkFileChooser *chooser,
+ GnomeDesktopThumbnailFactory *thumb_factory)
+{
+ gchar *uri;
+
+ uri = gtk_file_chooser_get_preview_uri (chooser);
+
+ if (uri) {
+ GdkPixbuf *pixbuf = NULL;
+ const gchar *mime_type = NULL;
+ GFile *file;
+ GFileInfo *file_info;
+ GtkWidget *preview;
+
+ preview = gtk_file_chooser_get_preview_widget (chooser);
+
+ file = g_file_new_for_uri (uri);
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+ g_object_unref (file);
+
+ if (file_info != NULL) {
+ mime_type = g_file_info_get_content_type (file_info);
+ g_object_unref (file_info);
+ }
+
+ if (mime_type) {
+ pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumb_factory,
+ uri,
+ mime_type);
+ }
+
+ if (pixbuf != NULL) {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (preview), pixbuf);
+ g_object_unref (pixbuf);
+ }
+ else {
+ gtk_image_set_from_stock (GTK_IMAGE (preview),
+ GTK_STOCK_DIALOG_QUESTION,
+ GTK_ICON_SIZE_DIALOG);
+ }
+
+ g_free (uri);
+ }
+
+ gtk_file_chooser_set_preview_widget_active (chooser, TRUE);
+}
+
+static void
+um_photo_dialog_select_file (UmPhotoDialog *um)
+{
+ GtkWidget *chooser;
+ const gchar *folder;
+ GtkWidget *preview;
+
+ chooser = gtk_file_chooser_dialog_new (_("Browse for more pictures"),
+ GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_window_set_modal (GTK_WINDOW (chooser), TRUE);
+
+ preview = gtk_image_new ();
+ gtk_widget_set_size_request (preview, 128, -1);
+ gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (chooser), preview);
+ gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (chooser), FALSE);
+ gtk_widget_show (preview);
+ g_signal_connect (chooser, "update-preview",
+ G_CALLBACK (update_preview), um->thumb_factory);
+
+ folder = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
+ if (folder)
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser),
+ folder);
+
+ g_signal_connect (chooser, "response",
+ G_CALLBACK (file_chooser_response), um);
+
+ gtk_window_present (GTK_WINDOW (chooser));
+}
+
+static void
+none_icon_selected (GtkMenuItem *menuitem,
+ UmPhotoDialog *um)
+{
+ um_user_set_icon_file (um->user, NULL);
+}
+
+static void
+file_icon_selected (GtkMenuItem *menuitem,
+ UmPhotoDialog *um)
+{
+ um_photo_dialog_select_file (um);
+}
+
+#ifdef HAVE_CHEESE
+static gboolean
+destroy_chooser (GtkWidget *chooser)
+{
+ gtk_widget_destroy (chooser);
+ return FALSE;
+}
+
+static void
+webcam_response_cb (GtkDialog *dialog,
+ int response,
+ UmPhotoDialog *um)
+{
+ if (response == GTK_RESPONSE_ACCEPT) {
+ GdkPixbuf *pb, *pb2;
+
+ g_object_get (G_OBJECT (dialog), "pixbuf", &pb, NULL);
+ pb2 = gdk_pixbuf_scale_simple (pb, 96, 96, GDK_INTERP_BILINEAR);
+
+ um_user_set_icon_data (um->user, pb2);
+
+ g_object_unref (pb2);
+ g_object_unref (pb);
+ }
+ if (response != GTK_RESPONSE_DELETE_EVENT &&
+ response != GTK_RESPONSE_NONE)
+ g_idle_add ((GSourceFunc) destroy_chooser, dialog);
+}
+
+static void
+webcam_icon_selected (GtkMenuItem *menuitem,
+ UmPhotoDialog *um)
+{
+ GtkWidget *window;
+
+ window = cheese_avatar_chooser_new ();
+ gtk_window_set_transient_for (GTK_WINDOW (window),
+ GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)));
+ gtk_window_set_modal (GTK_WINDOW (window), TRUE);
+ g_signal_connect (G_OBJECT (window), "response",
+ G_CALLBACK (webcam_response_cb), um);
+ gtk_widget_show (window);
+}
+
+static void
+update_photo_menu_status (UmPhotoDialog *um)
+{
+ if (um->num_cameras == 0)
+ gtk_widget_set_sensitive (um->take_photo_menuitem, FALSE);
+ else
+ gtk_widget_set_sensitive (um->take_photo_menuitem, TRUE);
+}
+
+static void
+device_added (CheeseCameraDeviceMonitor *monitor,
+ const gchar *id,
+ const gchar *device_file,
+ const gchar *product_name,
+ gint api_version,
+ UmPhotoDialog *um)
+{
+ um->num_cameras++;
+ update_photo_menu_status (um);
+}
+
+static void
+device_removed (CheeseCameraDeviceMonitor *monitor,
+ const char *id,
+ UmPhotoDialog *um)
+{
+ um->num_cameras--;
+ update_photo_menu_status (um);
+}
+
+#endif /* HAVE_CHEESE */
+
+static void
+stock_icon_selected (GtkMenuItem *menuitem,
+ UmPhotoDialog *um)
+{
+ const char *filename;
+
+ filename = g_object_get_data (G_OBJECT (menuitem), "filename");
+ um_user_set_icon_file (um->user, filename);
+}
+
+static GtkWidget *
+menu_item_for_filename (UmPhotoDialog *um,
+ const char *filename)
+{
+ GtkWidget *image, *menuitem;
+ GFile *file;
+ GIcon *icon;
+
+ file = g_file_new_for_path (filename);
+ icon = g_file_icon_new (file);
+ g_object_unref (file);
+ image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
+ g_object_unref (icon);
+
+ menuitem = gtk_menu_item_new ();
+ gtk_container_add (GTK_CONTAINER (menuitem), image);
+ gtk_widget_show_all (menuitem);
+
+ g_object_set_data_full (G_OBJECT (menuitem), "filename",
+ g_strdup (filename), (GDestroyNotify) g_free);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (stock_icon_selected), um);
+
+ return menuitem;
+}
+
+static void
+setup_photo_popup (UmPhotoDialog *um)
+{
+ GtkWidget *menu, *menuitem, *image;
+ guint x, y;
+ GDir *dir;
+ const char *face;
+ GError *error;
+
+ menu = gtk_menu_new ();
+
+ x = 0;
+ y = 0;
+
+ error = NULL;
+ dir = g_dir_open (DATADIR "/pixmaps/faces", 0, &error);
+ if (dir == NULL) {
+ g_warning ("Failed to load faces: %s", error->message);
+ g_error_free (error);
+ goto skip_faces;
+ }
+
+ while ((face = g_dir_read_name (dir)) != NULL) {
+ char *filename;
+
+ filename = g_build_filename (DATADIR "/pixmaps/faces", face, NULL);
+ menuitem = menu_item_for_filename (um, filename);
+ g_free (filename);
+ if (menuitem == NULL)
+ continue;
+
+ gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+ x, x + 1, y, y + 1);
+ gtk_widget_show (menuitem);
+
+ x++;
+ if (x >= ROW_SPAN - 1) {
+ y++;
+ x = 0;
+ }
+ }
+ g_dir_close (dir);
+
+ image = gtk_image_new_from_icon_name ("avatar-default", GTK_ICON_SIZE_DIALOG);
+ menuitem = gtk_menu_item_new ();
+ gtk_container_add (GTK_CONTAINER (menuitem), image);
+ gtk_widget_show_all (menuitem);
+ gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+ x, x + 1, y, y + 1);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (none_icon_selected), um);
+ gtk_widget_show (menuitem);
+ y++;
+
+ /* Separator */
+ menuitem = gtk_separator_menu_item_new ();
+ gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+ 0, ROW_SPAN - 1, y, y + 1);
+ gtk_widget_show (menuitem);
+
+ y++;
+
+skip_faces:
+
+#ifdef HAVE_CHEESE
+ um->take_photo_menuitem = gtk_menu_item_new_with_label (_("Take a photo..."));
+ gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (um->take_photo_menuitem),
+ 0, ROW_SPAN - 1, y, y + 1);
+ g_signal_connect (G_OBJECT (um->take_photo_menuitem), "activate",
+ G_CALLBACK (webcam_icon_selected), um);
+ gtk_widget_set_sensitive (um->take_photo_menuitem, FALSE);
+ gtk_widget_show (um->take_photo_menuitem);
+
+ um->monitor = cheese_camera_device_monitor_new ();
+ g_signal_connect (G_OBJECT (um->monitor), "added",
+ G_CALLBACK (device_added), um);
+ g_signal_connect (G_OBJECT (um->monitor), "removed",
+ G_CALLBACK (device_removed), um);
+ cheese_camera_device_monitor_coldplug (um->monitor);
+
+ y++;
+#endif /* HAVE_CHEESE */
+
+ menuitem = gtk_menu_item_new_with_label (_("Browse for more pictures..."));
+ gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+ 0, ROW_SPAN - 1, y, y + 1);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (file_icon_selected), um);
+ gtk_widget_show (menuitem);
+
+ um->photo_popup = menu;
+}
+
+static void
+popup_icon_menu (GtkToggleButton *button, UmPhotoDialog *um)
+{
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) && !gtk_widget_get_visible (um->photo_popup)) {
+ gtk_menu_popup (GTK_MENU (um->photo_popup),
+ NULL, NULL,
+ (GtkMenuPositionFunc) popup_menu_below_button, um->popup_button,
+ 0, gtk_get_current_event_time ());
+ } else {
+ gtk_menu_popdown (GTK_MENU (um->photo_popup));
+ }
+}
+
+static gboolean
+on_popup_button_button_pressed (GtkToggleButton *button,
+ GdkEventButton *event,
+ UmPhotoDialog *um)
+{
+ if (event->button == 1) {
+ if (!gtk_widget_get_visible (um->photo_popup)) {
+ popup_icon_menu (button, um);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ } else {
+ gtk_menu_popdown (GTK_MENU (um->photo_popup));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+on_photo_popup_unmap (GtkWidget *popup_menu,
+ UmPhotoDialog *um)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (um->popup_button), FALSE);
+}
+
+static void
+popup_button_draw (GtkWidget *widget,
+ cairo_t *cr,
+ UmPhotoDialog *um)
+{
+ GtkAllocation allocation;
+
+ if (gtk_widget_get_state (widget) != GTK_STATE_PRELIGHT &&
+ !gtk_widget_is_focus (gtk_widget_get_parent (widget))) {
+ return;
+ }
+
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_paint_expander (gtk_widget_get_style (widget),
+ cr,
+ gtk_widget_get_state (widget),
+ widget,
+ NULL,
+ allocation.x + allocation.width,
+ allocation.y + allocation.height,
+ GTK_EXPANDER_EXPANDED);
+}
+
+static void
+popup_button_focus_changed (GObject *button,
+ GParamSpec *pspec,
+ UmPhotoDialog *um)
+{
+ gtk_widget_queue_draw (gtk_bin_get_child (GTK_BIN (button)));
+}
+
+UmPhotoDialog *
+um_photo_dialog_new (GtkWidget *button)
+{
+ UmPhotoDialog *um;
+
+ um = g_new0 (UmPhotoDialog, 1);
+
+ um->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+
+ /* Set up the popup */
+ um->popup_button = button;
+ setup_photo_popup (um);
+ g_signal_connect (button, "toggled",
+ G_CALLBACK (popup_icon_menu), um);
+ g_signal_connect (button, "button-press-event",
+ G_CALLBACK (on_popup_button_button_pressed), um);
+ g_signal_connect (button, "notify::is-focus",
+ G_CALLBACK (popup_button_focus_changed), um);
+ g_signal_connect_after (gtk_bin_get_child (GTK_BIN (button)), "draw",
+ G_CALLBACK (popup_button_draw), um);
+
+ g_signal_connect (um->photo_popup, "unmap",
+ G_CALLBACK (on_photo_popup_unmap), um);
+
+ return um;
+}
+
+void
+um_photo_dialog_free (UmPhotoDialog *um)
+{
+ gtk_widget_destroy (um->photo_popup);
+
+ if (um->thumb_factory)
+ g_object_unref (um->thumb_factory);
+#ifdef HAVE_CHEESE
+ if (um->monitor)
+ g_object_unref (um->monitor);
+#endif
+ if (um->user)
+ g_object_unref (um->user);
+
+ g_free (um);
+}
+
+static void
+clear_tip (GtkMenuItem *item,
+ gpointer user_data)
+{
+ GList *children;
+ GtkWidget *image;
+ GIcon *icon, *icon2;
+ const char *filename;
+
+ /* Not a stock icon? */
+ filename = g_object_get_data (G_OBJECT (item), "filename");
+ if (filename == NULL)
+ return;
+
+ children = gtk_container_get_children (GTK_CONTAINER (item));
+ image = children->data;
+ g_assert (image != NULL);
+ g_list_free (children);
+
+ gtk_image_get_gicon (GTK_IMAGE (image), &icon, NULL);
+
+ if (G_IS_EMBLEMED_ICON (icon))
+ icon2 = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (icon));
+ else
+ return;
+
+ gtk_image_set_from_gicon (GTK_IMAGE (image), icon2, GTK_ICON_SIZE_DIALOG);
+ g_object_unref (icon);
+}
+
+static void
+set_tip (GtkWidget *item,
+ const char *tip,
+ GEmblem *emblem)
+{
+ GList *children;
+ GtkWidget *image;
+ GIcon *icon, *icon2;
+
+ children = gtk_container_get_children (GTK_CONTAINER (item));
+ image = children->data;
+ g_assert (image != NULL);
+ g_list_free (children);
+
+ gtk_image_get_gicon (GTK_IMAGE (image), &icon, NULL);
+ if (G_IS_EMBLEMED_ICON (icon)) {
+ return;
+ }
+
+ icon2 = g_emblemed_icon_new (icon, emblem);
+ gtk_image_set_from_gicon (GTK_IMAGE (image), icon2, GTK_ICON_SIZE_DIALOG);
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (item), tip);
+}
+
+void
+um_photo_dialog_set_user (UmPhotoDialog *um,
+ UmUser *user)
+{
+ UmUserManager *manager;
+ GSList *list, *l;
+ UmUser *u;
+ GIcon *icon;
+ GEmblem *emblem;
+ GList *children, *c;
+
+ g_return_if_fail (um != NULL);
+
+ if (um->user) {
+ g_object_unref (um->user);
+ um->user = NULL;
+ }
+ um->user = user;
+
+ if (um->user) {
+ g_object_ref (um->user);
+
+ children = gtk_container_get_children (GTK_CONTAINER (um->photo_popup));
+ g_list_foreach (children, (GFunc) clear_tip, NULL);
+
+ manager = um_user_manager_ref_default ();
+ list = um_user_manager_list_users (manager);
+ g_object_unref (manager);
+
+ icon = g_themed_icon_new ("avatar-default");
+ emblem = g_emblem_new (icon);
+ g_object_unref (icon);
+
+ for (l = list; l; l = l->next) {
+ const char *filename;
+
+ u = l->data;
+ if (u == user)
+ continue;
+ filename = um_user_get_icon_file (u);
+ if (filename == NULL)
+ continue;
+ for (c = children; c; c = c->next) {
+ const char *f;
+
+ f = g_object_get_data (G_OBJECT (c->data), "filename");
+ if (f == NULL)
+ continue;
+ if (strcmp (f, filename) == 0) {
+ char *tip;
+
+ tip = g_strdup_printf (_("Used by %s"),
+ um_user_get_real_name (u));
+ set_tip (GTK_WIDGET (c->data), tip, emblem);
+ g_free (tip);
+ break;
+ }
+ }
+ }
+ g_slist_free (list);
+
+ g_object_unref (emblem);
+ }
+}
+
diff --git a/panels/user-accounts/um-photo-dialog.h b/panels/user-accounts/um-photo-dialog.h
new file mode 100644
index 0000000..3f0c404
--- /dev/null
+++ b/panels/user-accounts/um-photo-dialog.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __UM_PHOTO_DIALOG_H__
+#define __UM_PHOTO_DIALOG_H__
+
+#include <gtk/gtk.h>
+#include "um-user.h"
+
+G_BEGIN_DECLS
+
+typedef struct _UmPhotoDialog UmPhotoDialog;
+
+UmPhotoDialog *um_photo_dialog_new (GtkWidget *button);
+void um_photo_dialog_free (UmPhotoDialog *dialog);
+void um_photo_dialog_set_user (UmPhotoDialog *dialog,
+ UmUser *user);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-strength-bar.c b/panels/user-accounts/um-strength-bar.c
new file mode 100644
index 0000000..64af45f
--- /dev/null
+++ b/panels/user-accounts/um-strength-bar.c
@@ -0,0 +1,261 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "um-strength-bar.h"
+
+#define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), UM_TYPE_STRENGTH_BAR, UmStrengthBarPrivate))
+
+struct _UmStrengthBarPrivate {
+ gdouble strength;
+};
+
+enum {
+ PROP_0,
+ PROP_STRENGTH
+};
+
+G_DEFINE_TYPE (UmStrengthBar, um_strength_bar, GTK_TYPE_WIDGET);
+
+
+static void
+um_strength_bar_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UmStrengthBar *bar = UM_STRENGTH_BAR (object);
+
+ switch (prop_id) {
+ case PROP_STRENGTH:
+ um_strength_bar_set_strength (bar, g_value_get_double (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_strength_bar_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ UmStrengthBar *bar = UM_STRENGTH_BAR (object);
+
+ switch (prop_id) {
+ case PROP_STRENGTH:
+ g_value_set_double (value, um_strength_bar_get_strength (bar));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+curved_rectangle (cairo_t *cr,
+ double x0,
+ double y0,
+ double width,
+ double height,
+ double radius)
+{
+ double x1;
+ double y1;
+
+ x1 = x0 + width;
+ y1 = y0 + height;
+
+ if (!width || !height) {
+ return;
+ }
+
+ if (width / 2 < radius) {
+ if (height / 2 < radius) {
+ cairo_move_to (cr, x0, (y0 + y1) / 2);
+ cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0);
+ cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
+ cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
+ cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
+ } else {
+ cairo_move_to (cr, x0, y0 + radius);
+ cairo_curve_to (cr, x0, y0, x0, y0, (x0 + x1) / 2, y0);
+ cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
+ cairo_line_to (cr, x1, y1 - radius);
+ cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
+ cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius);
+ }
+ } else {
+ if (height / 2 < radius) {
+ cairo_move_to (cr, x0, (y0 + y1) / 2);
+ cairo_curve_to (cr, x0, y0, x0 , y0, x0 + radius, y0);
+ cairo_line_to (cr, x1 - radius, y0);
+ cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
+ cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
+ cairo_line_to (cr, x0 + radius, y1);
+ cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
+ } else {
+ cairo_move_to (cr, x0, y0 + radius);
+ cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
+ cairo_line_to (cr, x1 - radius, y0);
+ cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
+ cairo_line_to (cr, x1, y1 - radius);
+ cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
+ cairo_line_to (cr, x0 + radius, y1);
+ cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius);
+ }
+ }
+
+ cairo_close_path (cr);
+}
+
+gdouble color1[3] = { 213.0/255.0, 4.0/255.0, 4.0/255.0 };
+gdouble color2[3] = { 234.0/255.0, 236.0/255.0, 31.0/255.0 };
+gdouble color3[3] = { 141.0/255.0, 133.0/255.0, 241.0/255.0 };
+gdouble color4[3] = { 99.0/255.0, 251.0/255.0, 107.0/255.0 };
+
+static void
+get_color (gdouble value, gdouble *r, gdouble *g, gdouble *b)
+{
+ gdouble *c;
+
+ if (value < 0.50) {
+ c = color1;
+ }
+ else if (value < 0.75) {
+ c = color2;
+ }
+ else if (value < 0.90) {
+ c = color3;
+ }
+ else {
+ c = color4;
+ }
+
+ *r = c[0];
+ *g = c[1];
+ *b = c[2];
+}
+
+static gboolean
+um_strength_bar_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ UmStrengthBar *bar = UM_STRENGTH_BAR (widget);
+ gdouble r, g, b;
+ GdkWindow *window;
+ GtkAllocation allocation;
+
+ window = gtk_widget_get_window (widget);
+ gtk_widget_get_allocation (widget, &allocation);
+ cr = gdk_cairo_create (window);
+ cairo_set_line_width (cr, 1);
+
+ cairo_rectangle (cr,
+ allocation.x,
+ allocation.y,
+ bar->priv->strength * allocation.width,
+ allocation.height);
+ cairo_clip (cr);
+
+ curved_rectangle (cr,
+ allocation.x + 0.5,
+ allocation.y + 0.5,
+ allocation.width - 1,
+ allocation.height - 1,
+ 4);
+ get_color (bar->priv->strength, &r, &g ,&b);
+ cairo_set_source_rgb (cr, r, g, b);
+ cairo_fill_preserve (cr);
+
+ cairo_reset_clip (cr);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_stroke (cr);
+
+ return FALSE;
+}
+
+static void
+um_strength_bar_class_init (UmStrengthBarClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+
+ gobject_class = (GObjectClass*)class;
+ widget_class = (GtkWidgetClass*)class;
+
+ gobject_class->set_property = um_strength_bar_set_property;
+ gobject_class->get_property = um_strength_bar_get_property;
+
+ widget_class->draw = um_strength_bar_draw;
+
+ g_object_class_install_property (gobject_class,
+ PROP_STRENGTH,
+ g_param_spec_double ("strength",
+ "Strength",
+ "Strength",
+ 0.0, 1.0, 0.0,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (class, sizeof (UmStrengthBarPrivate));
+}
+
+static void
+um_strength_bar_init (UmStrengthBar *bar)
+{
+ gtk_widget_set_has_window (GTK_WIDGET (bar), FALSE);
+ gtk_widget_set_size_request (GTK_WIDGET (bar), 120, 8);
+
+ bar->priv = GET_PRIVATE (bar);
+ bar->priv->strength = 0.0;
+}
+
+GtkWidget *
+um_strength_bar_new (void)
+{
+ return (GtkWidget*) g_object_new (UM_TYPE_STRENGTH_BAR, NULL);
+}
+
+void
+um_strength_bar_set_strength (UmStrengthBar *bar,
+ gdouble strength)
+{
+ bar->priv->strength = strength;
+
+ g_object_notify (G_OBJECT (bar), "strength");
+
+ if (gtk_widget_is_drawable (GTK_WIDGET (bar))) {
+ gtk_widget_queue_draw (GTK_WIDGET (bar));
+ }
+}
+
+gdouble
+um_strength_bar_get_strength (UmStrengthBar *bar)
+{
+ return bar->priv->strength;
+}
diff --git a/panels/user-accounts/um-strength-bar.h b/panels/user-accounts/um-strength-bar.h
new file mode 100644
index 0000000..8bf0572
--- /dev/null
+++ b/panels/user-accounts/um-strength-bar.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * Licensed under the GNU General Public License Version 3
+ *
+ * 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 3 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/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#ifndef _UM_STRENGTH_BAR_H_
+#define _UM_STRENGTH_BAR_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_STRENGTH_BAR (um_strength_bar_get_type ())
+#define UM_STRENGTH_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_STRENGTH_BAR, \
+ UmStrengthBar))
+#define UM_STRENGTH_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_STRENGTH_BAR, \
+ UmStrengthBarClass))
+#define UM_IS_STRENGTH_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_STRENGTH_BAR))
+#define UM_IS_STRENGTH_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_STRENGTH_BAR))
+#define UM_STRENGTH_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_STRENGTH_BAR, \
+ UmStrengthBarClass))
+
+typedef struct _UmStrengthBarClass UmStrengthBarClass;
+typedef struct _UmStrengthBar UmStrengthBar;
+typedef struct _UmStrengthBarPrivate UmStrengthBarPrivate;
+
+struct _UmStrengthBarClass {
+ GtkWidgetClass parent_class;
+};
+
+struct _UmStrengthBar {
+ GtkWidget parent_instance;
+ UmStrengthBarPrivate *priv;
+};
+
+GType um_strength_bar_get_type (void) G_GNUC_CONST;
+
+GtkWidget *um_strength_bar_new (void);
+void um_strength_bar_set_strength (UmStrengthBar *bar,
+ gdouble strength);
+gdouble um_strength_bar_get_strength (UmStrengthBar *bar);
+
+G_END_DECLS
+
+#endif /* _UM_STRENGTH_BAR_H_ */
diff --git a/panels/user-accounts/um-user-manager.c b/panels/user-accounts/um-user-manager.c
new file mode 100644
index 0000000..1e39018
--- /dev/null
+++ b/panels/user-accounts/um-user-manager.c
@@ -0,0 +1,624 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2010 Red Hat, Inc.
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif /* HAVE_PATHS_H */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "um-user-manager.h"
+
+enum {
+ USERS_LOADED,
+ USER_ADDED,
+ USER_REMOVED,
+ USER_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void um_user_manager_class_init (UmUserManagerClass *klass);
+static void um_user_manager_init (UmUserManager *user_manager);
+static void um_user_manager_finalize (GObject *object);
+
+static gpointer user_manager_object = NULL;
+
+G_DEFINE_TYPE (UmUserManager, um_user_manager, G_TYPE_OBJECT)
+
+static void
+um_user_manager_class_init (UmUserManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = um_user_manager_finalize;
+
+ signals [USERS_LOADED] =
+ g_signal_new ("users-loaded",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmUserManagerClass, users_loaded),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals [USER_ADDED] =
+ g_signal_new ("user-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmUserManagerClass, user_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, UM_TYPE_USER);
+ signals [USER_REMOVED] =
+ g_signal_new ("user-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmUserManagerClass, user_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, UM_TYPE_USER);
+ signals [USER_CHANGED] =
+ g_signal_new ("user-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmUserManagerClass, user_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, UM_TYPE_USER);
+}
+
+
+/* We maintain a ring for each group of users with the same real name.
+ * We need this to pick the right display names.
+ */
+static void
+remove_user_from_dupe_ring (UmUserManager *manager,
+ UmUser *user)
+{
+ GList *dupes;
+ UmUser *dup;
+
+ um_user_show_short_display_name (user);
+
+ dupes = g_object_get_data (G_OBJECT (user), "dupes");
+
+ if (dupes == NULL) {
+ return;
+ }
+
+ if (dupes->next == dupes->prev) {
+ dup = dupes->next->data;
+ um_user_show_short_display_name (dup);
+ g_signal_emit (manager, signals[USER_CHANGED], 0, dup);
+
+ g_list_free_1 (dupes->next);
+ g_object_set_data (G_OBJECT (dup), "dupes", NULL);
+ }
+ else {
+ dupes->next->prev = dupes->prev;
+ dupes->prev->next = dupes->next;
+ }
+
+ g_list_free_1 (dupes);
+ g_object_set_data (G_OBJECT (user), "dupes", NULL);
+}
+
+static gboolean
+match_real_name_hrfunc (gpointer key,
+ gpointer value,
+ gpointer user)
+{
+ return (value != user && g_strcmp0 (um_user_get_real_name (user), um_user_get_real_name (value)) == 0);
+}
+
+static void
+add_user_to_dupe_ring (UmUserManager *manager,
+ UmUser *user)
+{
+ UmUser *dup;
+ GList *dupes;
+ GList *l;
+
+ dup = g_hash_table_find (manager->user_by_object_path,
+ match_real_name_hrfunc, user);
+
+ if (!dup) {
+ return;
+ }
+
+ um_user_show_full_display_name (user);
+
+ dupes = g_object_get_data (G_OBJECT (dup), "dupes");
+ if (!dupes) {
+ um_user_show_full_display_name (dup);
+ g_signal_emit (manager, signals[USER_CHANGED], 0, dup);
+ dupes = g_list_append (NULL, dup);
+ g_object_set_data (G_OBJECT (dup), "dupes", dupes);
+ dupes->next = dupes->prev = dupes;
+ }
+
+ l = g_list_append (NULL, user);
+ g_object_set_data (G_OBJECT (user), "dupes", l);
+ l->prev = dupes->prev;
+ dupes->prev->next = l;
+ l->next = dupes;
+ dupes->prev = l;
+}
+
+static void
+user_changed_handler (UmUser *user,
+ UmUserManager *manager)
+{
+ remove_user_from_dupe_ring (manager, user);
+ add_user_to_dupe_ring (manager, user);
+ g_signal_emit (manager, signals[USER_CHANGED], 0, user);
+}
+
+static void
+user_added_handler (DBusGProxy *proxy,
+ const char *object_path,
+ gpointer user_data)
+{
+ UmUserManager *manager = UM_USER_MANAGER (user_data);
+ UmUser *user;
+
+ if (g_hash_table_lookup (manager->user_by_object_path, object_path))
+ return;
+
+ user = um_user_new_from_object_path (object_path);
+ if (!user)
+ return;
+
+ add_user_to_dupe_ring (manager, user);
+
+ g_signal_connect (user, "changed",
+ G_CALLBACK (user_changed_handler), manager);
+ g_hash_table_insert (manager->user_by_object_path, g_strdup (um_user_get_object_path (user)), g_object_ref (user));
+ g_hash_table_insert (manager->user_by_name, g_strdup (um_user_get_user_name (user)), g_object_ref (user));
+
+ g_signal_emit (manager, signals[USER_ADDED], 0, user);
+ g_object_unref (user);
+}
+
+static void
+user_deleted_handler (DBusGProxy *proxy,
+ const char *object_path,
+ gpointer user_data)
+{
+ UmUserManager *manager = UM_USER_MANAGER (user_data);
+ UmUser *user;
+
+ user = g_hash_table_lookup (manager->user_by_object_path, object_path);
+ g_object_ref (user);
+ g_signal_handlers_disconnect_by_func (user, user_changed_handler, manager);
+
+ remove_user_from_dupe_ring (manager, user);
+
+ g_hash_table_remove (manager->user_by_object_path, um_user_get_object_path (user));
+ g_hash_table_remove (manager->user_by_name, um_user_get_user_name (user));
+ g_signal_emit (manager, signals[USER_REMOVED], 0, user);
+ g_object_unref (user);
+}
+
+static void
+add_user (const gchar *object_path,
+ UmUserManager *manager)
+{
+ user_added_handler (NULL, object_path, manager);
+}
+
+static void
+got_users (DBusGProxy *proxy,
+ DBusGProxyCall *call_id,
+ gpointer data)
+{
+ UmUserManager *manager = data;
+ GError *error = NULL;
+ GPtrArray *paths;
+
+ if (!dbus_g_proxy_end_call (proxy,
+ call_id,
+ &error,
+ dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &paths,
+ G_TYPE_INVALID)) {
+ manager->no_service = TRUE;
+ g_error_free (error);
+ goto done;
+ }
+
+ g_ptr_array_foreach (paths, (GFunc)add_user, manager);
+
+ g_ptr_array_foreach (paths, (GFunc)g_free, NULL);
+ g_ptr_array_free (paths, TRUE);
+
+ done:
+ g_signal_emit (G_OBJECT (manager), signals[USERS_LOADED], 0);
+}
+
+static void
+get_users (UmUserManager *manager)
+{
+ g_debug ("calling 'ListCachedUsers'");
+ dbus_g_proxy_begin_call (manager->proxy,
+ "ListCachedUsers",
+ got_users,
+ manager,
+ NULL,
+ G_TYPE_INVALID);
+}
+
+static void
+um_user_manager_init (UmUserManager *manager)
+{
+ GError *error = NULL;
+
+ manager->user_by_object_path = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_object_unref);
+ manager->user_by_name = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_object_unref);
+
+ manager->bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+ if (manager->bus == NULL) {
+ g_warning ("Couldn't connect to system bus: %s", error->message);
+ g_error_free (error);
+ goto error;
+ }
+
+ manager->proxy = dbus_g_proxy_new_for_name (manager->bus,
+ "org.freedesktop.Accounts",
+ "/org/freedesktop/Accounts",
+ "org.freedesktop.Accounts");
+
+ dbus_g_proxy_add_signal (manager->proxy, "UserAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
+ dbus_g_proxy_add_signal (manager->proxy, "UserDeleted", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
+
+ dbus_g_proxy_connect_signal (manager->proxy, "UserAdded",
+ G_CALLBACK (user_added_handler), manager, NULL);
+ dbus_g_proxy_connect_signal (manager->proxy, "UserDeleted",
+ G_CALLBACK (user_deleted_handler), manager, NULL);
+
+ get_users (manager);
+
+ error: ;
+}
+
+static void
+clear_dup (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ GList *dupes;
+
+ /* don't bother maintaining the ring, we're destroying the
+ * entire hash table anyway
+ */
+ dupes = g_object_get_data (G_OBJECT (value), "dupes");
+
+ if (dupes) {
+ g_list_free_1 (dupes);
+ g_object_set_data (G_OBJECT (value), "dupes", NULL);
+ }
+}
+
+static void
+um_user_manager_finalize (GObject *object)
+{
+ UmUserManager *manager;
+
+ manager = UM_USER_MANAGER (object);
+
+ g_hash_table_foreach (manager->user_by_object_path, clear_dup, NULL);
+ g_hash_table_destroy (manager->user_by_object_path);
+ g_hash_table_destroy (manager->user_by_name);
+
+ G_OBJECT_CLASS (um_user_manager_parent_class)->finalize (object);
+}
+
+UmUserManager *
+um_user_manager_ref_default (void)
+{
+ if (user_manager_object != NULL) {
+ g_object_ref (user_manager_object);
+ } else {
+ user_manager_object = g_object_new (UM_TYPE_USER_MANAGER, NULL);
+ g_object_add_weak_pointer (user_manager_object,
+ (gpointer *) &user_manager_object);
+ }
+
+ return UM_USER_MANAGER (user_manager_object);
+}
+
+typedef struct {
+ UmUserManager *manager;
+ gchar *user_name;
+ GAsyncReadyCallback callback;
+ gpointer data;
+ GDestroyNotify destroy;
+} AsyncUserOpData;
+
+static void
+async_user_op_data_free (gpointer d)
+{
+ AsyncUserOpData *data = d;
+
+ g_object_unref (data->manager);
+
+ g_free (data->user_name);
+
+ if (data->destroy)
+ data->destroy (data->data);
+
+ g_free (data);
+}
+
+static void
+create_user_done (DBusGProxy *proxy,
+ DBusGProxyCall *call_id,
+ gpointer user_data)
+{
+ AsyncUserOpData *data = user_data;
+ gchar *path;
+ GError *error;
+ GSimpleAsyncResult *res;
+
+ res = g_simple_async_result_new (G_OBJECT (data->manager),
+ data->callback,
+ data->data,
+ um_user_manager_create_user);
+ error = NULL;
+ if (!dbus_g_proxy_end_call (proxy,
+ call_id,
+ &error,
+ DBUS_TYPE_G_OBJECT_PATH, &path,
+ G_TYPE_INVALID)) {
+ /* dbus-glib fail:
+ * We have to translate the errors manually here, since
+ * calling dbus_g_error_has_name on the error returned in
+ * um_user_manager_create_user_finish doesn't work.
+ */
+ if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.PermissionDenied")) {
+ g_simple_async_result_set_error (res,
+ UM_USER_MANAGER_ERROR,
+ UM_USER_MANAGER_ERROR_PERMISSION_DENIED,
+ "Not authorized");
+ }
+ else if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.UserExists")) {
+ g_simple_async_result_set_error (res,
+ UM_USER_MANAGER_ERROR,
+ UM_USER_MANAGER_ERROR_USER_EXISTS,
+ _("A user with name '%s' already exists."),
+ data->user_name);
+ }
+ else {
+ g_simple_async_result_set_from_error (res, error);
+ }
+ g_error_free (error);
+ }
+ else {
+ g_simple_async_result_set_op_res_gpointer (res, path, g_free);
+ }
+
+ data->callback (G_OBJECT (data->manager), G_ASYNC_RESULT (res), data->data);
+}
+
+gboolean
+um_user_manager_create_user_finish (UmUserManager *manager,
+ GAsyncResult *result,
+ UmUser **user,
+ GError **error)
+{
+ gchar *path;
+ GSimpleAsyncResult *res;
+
+ res = G_SIMPLE_ASYNC_RESULT (result);
+
+ *user = NULL;
+
+ if (g_simple_async_result_propagate_error (res, error)) {
+ return FALSE;
+ }
+
+ path = g_simple_async_result_get_op_res_gpointer (res);
+ *user = g_hash_table_lookup (manager->user_by_object_path, path);
+
+ return TRUE;
+}
+
+void
+um_user_manager_create_user (UmUserManager *manager,
+ const char *user_name,
+ const char *real_name,
+ gint account_type,
+ GAsyncReadyCallback done,
+ gpointer done_data,
+ GDestroyNotify destroy)
+{
+ AsyncUserOpData *data;
+
+ data = g_new0 (AsyncUserOpData, 1);
+ data->manager = g_object_ref (manager);
+ data->user_name = g_strdup (user_name);
+ data->callback = done;
+ data->data = done_data;
+ data->destroy = destroy;
+
+ dbus_g_proxy_begin_call (manager->proxy,
+ "CreateUser",
+ create_user_done,
+ data,
+ async_user_op_data_free,
+ G_TYPE_STRING, user_name,
+ G_TYPE_STRING, real_name,
+ G_TYPE_INT, account_type,
+ G_TYPE_INVALID);
+}
+
+static void
+delete_user_done (DBusGProxy *proxy,
+ DBusGProxyCall *call_id,
+ gpointer user_data)
+{
+ AsyncUserOpData *data = user_data;
+ GError *error;
+ GSimpleAsyncResult *res;
+
+ res = g_simple_async_result_new (G_OBJECT (data->manager),
+ data->callback,
+ data->data,
+ um_user_manager_delete_user);
+ error = NULL;
+ if (!dbus_g_proxy_end_call (proxy,
+ call_id,
+ &error,
+ G_TYPE_INVALID)) {
+ if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.PermissionDenied")) {
+ g_simple_async_result_set_error (res,
+ UM_USER_MANAGER_ERROR,
+ UM_USER_MANAGER_ERROR_PERMISSION_DENIED,
+ "Not authorized");
+ }
+ else if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.UserDoesntExists")) {
+ g_simple_async_result_set_error (res,
+ UM_USER_MANAGER_ERROR,
+ UM_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST,
+ _("This user does not exist."));
+ }
+ else {
+ g_simple_async_result_set_from_error (res, error);
+ g_error_free (error);
+ }
+ }
+
+ data->callback (G_OBJECT (data->manager), G_ASYNC_RESULT (res), data->data);
+}
+
+gboolean
+um_user_manager_delete_user_finish (UmUserManager *manager,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *res;
+
+ res = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (res, error)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+um_user_manager_delete_user (UmUserManager *manager,
+ UmUser *user,
+ gboolean remove_files,
+ GAsyncReadyCallback done,
+ gpointer done_data,
+ GDestroyNotify destroy)
+{
+ AsyncUserOpData *data;
+
+ data = g_new0 (AsyncUserOpData, 1);
+ data->manager = g_object_ref (manager);
+ data->callback = done;
+ data->data = done_data;
+ data->destroy = destroy;
+
+ dbus_g_proxy_begin_call (manager->proxy,
+ "DeleteUser",
+ delete_user_done,
+ data,
+ async_user_op_data_free,
+ G_TYPE_INT64, um_user_get_uid (user),
+ G_TYPE_BOOLEAN, remove_files,
+ G_TYPE_INVALID);
+}
+
+GSList *
+um_user_manager_list_users (UmUserManager *manager)
+{
+ GSList *list = NULL;
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, manager->user_by_name);
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ list = g_slist_prepend (list, value);
+ }
+
+ return g_slist_sort (list, (GCompareFunc) um_user_collate);
+}
+
+UmUser *
+um_user_manager_get_user (UmUserManager *manager,
+ const gchar *name)
+{
+ return g_hash_table_lookup (manager->user_by_name, name);
+}
+
+UmUser *
+um_user_manager_get_user_by_id (UmUserManager *manager,
+ uid_t uid)
+{
+ struct passwd *pwent;
+
+ pwent = getpwuid (uid);
+ if (!pwent) {
+ return NULL;
+ }
+
+ return um_user_manager_get_user (manager, pwent->pw_name);
+}
+
+gboolean
+um_user_manager_no_service (UmUserManager *manager)
+{
+ return manager->no_service;
+}
+
+GQuark
+um_user_manager_error_quark (void)
+{
+ return g_quark_from_static_string ("um-user-manager-error-quark");
+}
diff --git a/panels/user-accounts/um-user-manager.h b/panels/user-accounts/um-user-manager.h
new file mode 100644
index 0000000..b051a88
--- /dev/null
+++ b/panels/user-accounts/um-user-manager.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2010 Red Hat, Inc.
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __UM_USER_MANAGER__
+#define __UM_USER_MANAGER__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <dbus/dbus-glib.h>
+
+#include "um-user.h"
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_USER_MANAGER (um_user_manager_get_type ())
+#define UM_USER_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), UM_TYPE_USER_MANAGER, UmUserManager))
+#define UM_USER_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), UM_TYPE_USER_MANAGER, UmUserManagerClass))
+#define UM_IS_USER_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), UM_TYPE_USER_MANAGER))
+#define UM_IS_USER_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), UM_TYPE_USER_MANAGER))
+#define UM_USER_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), UM_TYPE_USER_MANAGER, UmUserManagerClass))
+
+typedef struct
+{
+ GObject parent;
+
+ DBusGConnection *bus;
+ DBusGProxy *proxy;
+
+ GHashTable *user_by_object_path;
+ GHashTable *user_by_name;
+
+ gboolean no_service;
+} UmUserManager;
+
+typedef struct
+{
+ GObjectClass parent_class;
+
+ void (* users_loaded) (UmUserManager *user_managaer);
+ void (* user_added) (UmUserManager *user_manager,
+ UmUser *user);
+ void (* user_removed) (UmUserManager *user_manager,
+ UmUser *user);
+ void (* user_changed) (UmUserManager *user_manager,
+ UmUser *user);
+} UmUserManagerClass;
+
+
+typedef enum {
+ UM_USER_MANAGER_ERROR_FAILED,
+ UM_USER_MANAGER_ERROR_USER_EXISTS,
+ UM_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST,
+ UM_USER_MANAGER_ERROR_PERMISSION_DENIED
+} UmUserManagerError;
+
+#define UM_USER_MANAGER_ERROR um_user_manager_error_quark ()
+
+GQuark um_user_manager_error_quark (void);
+
+GType um_user_manager_get_type (void);
+
+UmUserManager * um_user_manager_ref_default (void);
+
+gboolean um_user_manager_no_service (UmUserManager *manager);
+
+GSList * um_user_manager_list_users (UmUserManager *manager);
+UmUser * um_user_manager_get_user (UmUserManager *manager,
+ const char *user_name);
+UmUser * um_user_manager_get_user_by_id (UmUserManager *manager,
+ uid_t uid);
+
+void um_user_manager_create_user (UmUserManager *manager,
+ const char *user_name,
+ const char *real_name,
+ gint account_type,
+ GAsyncReadyCallback done,
+ gpointer user_data,
+ GDestroyNotify destroy);
+gboolean um_user_manager_create_user_finish (UmUserManager *manager,
+ GAsyncResult *result,
+ UmUser **user,
+ GError **error);
+void um_user_manager_delete_user (UmUserManager *manager,
+ UmUser *user,
+ gboolean remove_files,
+ GAsyncReadyCallback done,
+ gpointer user_data,
+ GDestroyNotify destroy);
+gboolean um_user_manager_delete_user_finish (UmUserManager *manager,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __UM_USER_MANAGER__ */
diff --git a/panels/user-accounts/um-user-module.c b/panels/user-accounts/um-user-module.c
new file mode 100644
index 0000000..d96ed40
--- /dev/null
+++ b/panels/user-accounts/um-user-module.c
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include <config.h>
+
+#include "um-user-panel.h"
+
+#include <glib/gi18n.h>
+
+void
+g_io_module_load (GIOModule *module)
+{
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ /* register the panel */
+ um_user_panel_register (module);
+}
+
+void
+g_io_module_unload (GIOModule *module)
+{
+}
diff --git a/panels/user-accounts/um-user-panel.c b/panels/user-accounts/um-user-panel.c
new file mode 100644
index 0000000..4bb3d6e
--- /dev/null
+++ b/panels/user-accounts/um-user-panel.c
@@ -0,0 +1,1286 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include "um-user-panel.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <polkit/polkit.h>
+#include <dbus/dbus-glib-bindings.h>
+
+#ifdef HAVE_CHEESE
+#include <gst/gst.h>
+#endif /* HAVE_CHEESE */
+
+#include "marshal.h"
+
+#include "um-user.h"
+#include "um-user-manager.h"
+
+#include "um-strength-bar.h"
+#include "um-editable-button.h"
+#include "um-editable-entry.h"
+#include "um-editable-combo.h"
+#include "um-lockbutton.h"
+
+#include "um-account-dialog.h"
+#include "um-language-dialog.h"
+#include "um-login-options.h"
+#include "um-password-dialog.h"
+#include "um-photo-dialog.h"
+#include "um-fingerprint-dialog.h"
+#include "um-utils.h"
+#include "gdm-languages.h"
+
+G_DEFINE_DYNAMIC_TYPE (UmUserPanel, um_user_panel, CC_TYPE_PANEL)
+
+#define UM_USER_PANEL_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), UM_TYPE_USER_PANEL, UmUserPanelPrivate))
+
+struct _UmUserPanelPrivate {
+ UmUserManager *um;
+ GtkBuilder *builder;
+
+ GtkWidget *notebook;
+ GtkWidget *lock_button;
+ PolkitPermission *permission;
+ GtkWidget *language_chooser;
+
+ UmAccountDialog *account_dialog;
+ UmPasswordDialog *password_dialog;
+ UmPhotoDialog *photo_dialog;
+ UmLoginOptions *login_options;
+
+ PolkitAuthority *authority;
+};
+
+static GtkWidget *
+get_widget (UmUserPanelPrivate *d, const char *name)
+{
+ return (GtkWidget *)gtk_builder_get_object (d->builder, name);
+}
+
+enum {
+ USER_COL,
+ FACE_COL,
+ NAME_COL,
+ USER_ROW_COL,
+ TITLE_COL,
+ HEADING_ROW_COL,
+ SORT_KEY_COL,
+ NUM_USER_LIST_COLS
+};
+
+static UmUser *
+get_selected_user (UmUserPanelPrivate *d)
+{
+ GtkTreeView *tv;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ UmUser *user;
+
+ tv = (GtkTreeView *)get_widget (d, "list-treeview");
+ store = (GtkListStore *)gtk_tree_view_get_model (tv);
+ selection = gtk_tree_view_get_selection (tv);
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_tree_model_get (model, &iter, USER_COL, &user, -1);
+ return user;
+ }
+
+ return NULL;
+}
+
+static void
+user_added (UmUserManager *um, UmUser *user, UmUserPanelPrivate *d)
+{
+ GtkWidget *widget;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GtkTreeIter dummy;
+ GdkPixbuf *pixbuf;
+ gchar *text;
+ GtkTreeSelection *selection;
+ gint sort_key;
+
+ g_debug ("user added: %d %s\n", um_user_get_uid (user), um_user_get_real_name (user));
+ widget = get_widget (d, "list-treeview");
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+ store = GTK_LIST_STORE (model);
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+
+ pixbuf = um_user_render_icon (user, TRUE, 48);
+ text = g_strdup_printf ("<b>%s</b>\n<i>%s</i>",
+ um_user_get_display_name (user),
+ um_account_type_get_name (um_user_get_account_type (user)));
+
+ if (um_user_get_uid (user) == getuid ()) {
+ sort_key = 1;
+ }
+ else {
+ sort_key = 3;
+ }
+ gtk_list_store_append (store, &iter);
+
+ gtk_list_store_set (store, &iter,
+ USER_COL, user,
+ FACE_COL, pixbuf,
+ NAME_COL, text,
+ USER_ROW_COL, TRUE,
+ TITLE_COL, NULL,
+ HEADING_ROW_COL, FALSE,
+ SORT_KEY_COL, sort_key,
+ -1);
+ g_object_unref (pixbuf);
+ g_free (text);
+
+ if (sort_key == 1 &&
+ !gtk_tree_selection_get_selected (selection, &model, &dummy)) {
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+}
+
+static void
+get_previous_user_row (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *prev)
+{
+ GtkTreePath *path;
+ UmUser *user;
+
+ path = gtk_tree_model_get_path (model, iter);
+ while (gtk_tree_path_prev (path)) {
+ gtk_tree_model_get_iter (model, prev, path);
+ gtk_tree_model_get (model, prev, USER_COL, &user, -1);
+ if (user) {
+ g_object_unref (user);
+ break;
+ }
+ }
+ gtk_tree_path_free (path);
+}
+
+static gboolean
+get_next_user_row (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *next)
+{
+ UmUser *user;
+
+ *next = *iter;
+ while (gtk_tree_model_iter_next (model, next)) {
+ gtk_tree_model_get (model, next, USER_COL, &user, -1);
+ if (user) {
+ g_object_unref (user);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+user_removed (UmUserManager *um, UmUser *user, UmUserPanelPrivate *d)
+{
+ GtkTreeView *tv;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkListStore *store;
+ GtkTreeIter iter, next;
+ UmUser *u;
+
+ g_debug ("user removed: %s\n", um_user_get_user_name (user));
+ tv = (GtkTreeView *)get_widget (d, "list-treeview");
+ selection = gtk_tree_view_get_selection (tv);
+ model = gtk_tree_view_get_model (tv);
+ store = GTK_LIST_STORE (model);
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ do {
+ gtk_tree_model_get (model, &iter, USER_COL, &u, -1);
+
+ if (u != NULL) {
+ if (um_user_get_uid (user) == um_user_get_uid (u)) {
+ if (!get_next_user_row (model, &iter, &next))
+ get_previous_user_row (model, &iter, &next);
+ gtk_list_store_remove (store, &iter);
+ gtk_tree_selection_select_iter (selection, &next);
+ g_object_unref (u);
+ break;
+ }
+ g_object_unref (u);
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+ }
+}
+
+static void show_user (UmUser *user, UmUserPanelPrivate *d);
+
+static void
+user_changed (UmUserManager *um, UmUser *user, UmUserPanelPrivate *d)
+{
+ GtkTreeView *tv;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ UmUser *current;
+ GdkPixbuf *pixbuf;
+ char *text;
+
+ tv = (GtkTreeView *)get_widget (d, "list-treeview");
+ model = gtk_tree_view_get_model (tv);
+ selection = gtk_tree_view_get_selection (tv);
+
+ gtk_tree_model_get_iter_first (model, &iter);
+ do {
+ gtk_tree_model_get (model, &iter, USER_COL, ¤t, -1);
+ if (current == user) {
+ pixbuf = um_user_render_icon (user, TRUE, 48);
+ text = g_strdup_printf ("<b>%s</b>\n<i>%s</i>",
+ um_user_get_display_name (user),
+ um_account_type_get_name (um_user_get_account_type (user)));
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+ USER_COL, user,
+ FACE_COL, pixbuf,
+ NAME_COL, text,
+ -1);
+ g_object_unref (pixbuf);
+ g_free (text);
+ g_object_unref (current);
+
+ break;
+ }
+ if (current)
+ g_object_unref (current);
+
+ } while (gtk_tree_model_iter_next (model, &iter));
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_tree_model_get (model, &iter, USER_COL, ¤t, -1);
+
+ if (current == user) {
+ show_user (user, d);
+ }
+ if (current)
+ g_object_unref (current);
+ }
+}
+
+static void
+select_created_user (UmUser *user, UmUserPanelPrivate *d)
+{
+ GtkTreeView *tv;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ UmUser *current;
+ GtkTreePath *path;
+
+ tv = (GtkTreeView *)get_widget (d, "list-treeview");
+ model = gtk_tree_view_get_model (tv);
+ selection = gtk_tree_view_get_selection (tv);
+
+ gtk_tree_model_get_iter_first (model, &iter);
+ do {
+ gtk_tree_model_get (model, &iter, USER_COL, ¤t, -1);
+ if (user == current) {
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_view_scroll_to_cell (tv, path, NULL, FALSE, 0.0, 0.0);
+ gtk_tree_selection_select_path (selection, path);
+ gtk_tree_path_free (path);
+ break;
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+}
+
+static void
+add_user (GtkButton *button, UmUserPanelPrivate *d)
+{
+ um_account_dialog_show (d->account_dialog,
+ GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ (UserCreatedCallback)select_created_user, d);
+}
+
+static void
+delete_user_done (UmUserManager *manager,
+ GAsyncResult *res,
+ UmUserPanelPrivate *d)
+{
+ GError *error;
+
+ error = NULL;
+ if (!um_user_manager_delete_user_finish (manager, res, &error)) {
+ if (!g_error_matches (error, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_PERMISSION_DENIED)) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("Failed to delete user"));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", error->message);
+
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+ gtk_window_present (GTK_WINDOW (dialog));
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+delete_user_response (GtkWidget *dialog,
+ gint response_id,
+ UmUserPanelPrivate *d)
+{
+ UmUser *user;
+ gboolean remove_files;
+
+ gtk_widget_destroy (dialog);
+
+ if (response_id == GTK_RESPONSE_CANCEL) {
+ return;
+ }
+ else if (response_id == GTK_RESPONSE_NO) {
+ remove_files = TRUE;
+ }
+ else {
+ remove_files = FALSE;
+ }
+
+ user = get_selected_user (d);
+
+ um_user_manager_delete_user (d->um,
+ user,
+ remove_files,
+ (GAsyncReadyCallback)delete_user_done,
+ d,
+ NULL);
+
+ g_object_unref (user);
+}
+
+static void
+delete_user (GtkButton *button, UmUserPanelPrivate *d)
+{
+ UmUser *user;
+ GtkWidget *dialog;
+
+ user = get_selected_user (d);
+ if (user == NULL) {
+ return;
+ }
+ else if (um_user_get_uid (user) == getuid ()) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ 0,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE,
+ _("You cannot delete your own account."));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+ }
+ else if (um_user_is_logged_in (user)) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ 0,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE,
+ _("%s is still logged in"),
+ um_user_get_real_name (user));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("Deleting a user while they are logged in can leave the system in an inconsistent state."));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+ }
+ else {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ 0,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("Do you want to keep %s's files?"),
+ um_user_get_real_name (user));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("It is possible to keep the home directory, mail spool and temporary files around when deleting a user account."));
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("_Delete Files"), GTK_RESPONSE_NO,
+ _("_Keep Files"), GTK_RESPONSE_YES,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ NULL);
+
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users");
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (delete_user_response), d);
+ }
+
+ g_signal_connect (dialog, "close",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+
+}
+
+static const gchar *
+get_password_mode_text (UmUser *user)
+{
+ const gchar *text;
+
+ if (um_user_get_locked (user)) {
+ text = C_("Password mode", "Account disabled");
+ }
+ else {
+ switch (um_user_get_password_mode (user)) {
+ case UM_PASSWORD_MODE_REGULAR:
+ /* five bullets */
+ text = "\xe2\x80\xa2\xe2\x80\xa2\xe2\x80\xa2\xe2\x80\xa2\xe2\x80\xa2";
+ break;
+ case UM_PASSWORD_MODE_SET_AT_LOGIN:
+ text = C_("Password mode", "To be set at next login");
+ break;
+ case UM_PASSWORD_MODE_NONE:
+ text = C_("Password mode", "None");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ return text;
+}
+
+static void
+show_user (UmUser *user, UmUserPanelPrivate *d)
+{
+ GtkWidget *image;
+ GtkWidget *label;
+ GtkWidget *label2;
+ GtkWidget *label3;
+ GdkPixbuf *pixbuf;
+ gchar *lang;
+ GtkWidget *widget;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ pixbuf = um_user_render_icon (user, FALSE, 48);
+ image = get_widget (d, "user-icon-image");
+ gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+ image = get_widget (d, "user-icon-image2");
+ gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+ g_object_unref (pixbuf);
+
+ um_photo_dialog_set_user (d->photo_dialog, user);
+
+ widget = get_widget (d, "full-name-entry");
+ um_editable_entry_set_text (UM_EDITABLE_ENTRY (widget), um_user_get_real_name (user));
+ gtk_widget_set_tooltip_text (widget, um_user_get_user_name (user));
+
+ widget = get_widget (d, "account-type-combo");
+ um_editable_combo_set_active (UM_EDITABLE_COMBO (widget), um_user_get_account_type (user));
+
+ widget = get_widget (d, "account-password-button");
+ um_editable_button_set_text (UM_EDITABLE_BUTTON (widget), get_password_mode_text (user));
+
+ widget = get_widget (d, "account-email-entry");
+ um_editable_entry_set_text (UM_EDITABLE_ENTRY (widget), um_user_get_email (user));
+
+ widget = get_widget (d, "account-language-combo");
+ model = um_editable_combo_get_model (UM_EDITABLE_COMBO (widget));
+ um_add_user_languages (model);
+
+ lang = g_strdup (um_user_get_language (user));
+ if (!lang)
+ lang = um_get_current_language ();
+ um_get_iter_for_language (model, lang, &iter);
+ um_editable_combo_set_active_iter (UM_EDITABLE_COMBO (widget), &iter);
+ g_free (lang);
+
+ label = get_widget (d, "account-location-entry");
+ um_editable_entry_set_text (UM_EDITABLE_ENTRY (label), um_user_get_location (user));
+
+ widget = get_widget (d, "account-fingerprint-notebook");
+ label = get_widget (d, "account-fingerprint-label");
+ label2 = get_widget (d, "account-fingerprint-value-label");
+ label3 = get_widget (d, "account-fingerprint-button-label");
+ if (um_user_get_uid (user) != getuid() ||
+ !set_fingerprint_label (label2, label3)) {
+ gtk_widget_hide (label);
+ gtk_widget_hide (widget);
+ } else {
+ gtk_widget_show (label);
+ gtk_widget_show (widget);
+ }
+}
+
+static void lockbutton_changed (UmLockButton *button, gpointer data);
+
+static void
+selected_user_changed (GtkTreeSelection *selection, UmUserPanelPrivate *d)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ UmUser *user;
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_tree_model_get (model, &iter, USER_COL, &user, -1);
+ show_user (user, d);
+ lockbutton_changed (UM_LOCK_BUTTON (d->lock_button), d);
+ g_object_unref (user);
+ }
+}
+
+static void
+change_name_done (GtkWidget *entry,
+ UmUserPanelPrivate *d)
+{
+ const gchar *text;
+ UmUser *user;
+
+ user = get_selected_user (d);
+
+ text = um_editable_entry_get_text (UM_EDITABLE_ENTRY (entry));
+
+ if (g_strcmp0 (text, um_user_get_location (user)) != 0) {
+ um_user_set_real_name (user, text);
+ }
+}
+
+static void
+account_type_changed (UmEditableCombo *combo,
+ UmUserPanelPrivate *d)
+{
+ UmUser *user;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gint account_type;
+
+ user = get_selected_user (d);
+ model = um_editable_combo_get_model (combo);
+ um_editable_combo_get_active_iter (combo, &iter);
+ gtk_tree_model_get (model, &iter, 1, &account_type, -1);
+
+ if (account_type != um_user_get_account_type (user)) {
+ um_user_set_account_type (user, account_type);
+ }
+}
+
+static void
+language_response (GtkDialog *dialog,
+ gint response_id,
+ UmUserPanelPrivate *d)
+{
+ GtkWidget *combo;
+ UmUser *user;
+ gchar *lang;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ user = get_selected_user (d);
+ combo = get_widget (d, "account-language-combo");
+ model = um_editable_combo_get_model (UM_EDITABLE_COMBO (combo));
+
+ if (response_id == GTK_RESPONSE_OK) {
+ lang = um_language_chooser_get_language (GTK_WIDGET (dialog));
+ um_user_set_language (user, lang);
+ }
+ else {
+ lang = g_strdup (um_user_get_language (user));
+ if (!lang)
+ lang = um_get_current_language ();
+ }
+ um_get_iter_for_language (model, lang, &iter);
+ um_editable_combo_set_active_iter (UM_EDITABLE_COMBO (combo), &iter);
+ g_free (lang);
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ gtk_widget_set_sensitive (combo, TRUE);
+}
+
+static gboolean
+finish_language_chooser (UmUserPanelPrivate *d)
+{
+ GtkWidget *combo;
+
+ combo = get_widget (d, "account-language-combo");
+ d->language_chooser = um_language_chooser_new ();
+ gtk_window_set_transient_for (GTK_WINDOW (d->language_chooser),
+ GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)));
+ g_signal_connect (d->language_chooser, "response",
+ G_CALLBACK (language_response), d);
+ g_signal_connect (d->language_chooser, "delete-event",
+ G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+
+ gdk_window_set_cursor (gtk_widget_get_window (gtk_widget_get_toplevel (d->notebook)), NULL);
+ gtk_window_present (GTK_WINDOW (d->language_chooser));
+ gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE);
+
+ return FALSE;
+}
+
+static void
+language_changed (UmEditableCombo *combo,
+ UmUserPanelPrivate *d)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *lang;
+ UmUser *user;
+ GdkCursor *cursor;
+
+ if (!um_editable_combo_get_active_iter (combo, &iter))
+ return;
+
+ user = get_selected_user (d);
+ model = um_editable_combo_get_model (combo);
+
+ gtk_tree_model_get (model, &iter, 0, &lang, -1);
+ if (lang) {
+ if (g_strcmp0 (lang, um_user_get_language (user)) != 0) {
+ um_user_set_language (user, lang);
+ }
+ g_free (lang);
+ return;
+ }
+
+ if (d->language_chooser) {
+ gtk_window_present (GTK_WINDOW (d->language_chooser));
+ gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE);
+ return;
+ }
+
+ cursor = gdk_cursor_new (GDK_WATCH);
+ gdk_window_set_cursor (gtk_widget_get_window (gtk_widget_get_toplevel (d->notebook)),
+ cursor);
+ gdk_cursor_unref (cursor);
+
+ g_idle_add ((GSourceFunc)finish_language_chooser, d);
+}
+
+static void
+change_password (GtkButton *button, UmUserPanelPrivate *d)
+{
+ UmUser *user;
+
+ user = get_selected_user (d);
+
+ um_password_dialog_set_user (d->password_dialog, user);
+ um_password_dialog_show (d->password_dialog,
+ GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)));
+
+ g_object_unref (user);
+}
+
+static void
+change_email_done (UmEditableEntry *e,
+ UmUserPanelPrivate *d)
+{
+ const gchar *text;
+ UmUser *user;
+
+ user = get_selected_user (d);
+
+ text = um_editable_entry_get_text (e);
+
+ if (g_strcmp0 (text, um_user_get_email (user)) != 0) {
+ um_user_set_email (user, text);
+ }
+}
+
+static void
+change_location_done (GtkWidget *entry,
+ UmUserPanelPrivate *d)
+{
+ const gchar *text;
+ UmUser *user;
+
+ user = get_selected_user (d);
+
+ text = um_editable_entry_get_text (UM_EDITABLE_ENTRY (entry));
+
+ if (g_strcmp0 (text, um_user_get_location (user)) != 0) {
+ um_user_set_location (user, text);
+ }
+}
+
+static void
+change_fingerprint (GtkButton *button, UmUserPanelPrivate *d)
+{
+ GtkWidget *label, *label2;
+ UmUser *user;
+
+ user = get_selected_user (d);
+ g_assert (g_strcmp0 (g_get_user_name (), um_user_get_user_name (user)) == 0);
+
+ label = get_widget (d, "account-fingerprint-value-label");
+ label2 = get_widget (d, "account-fingerprint-button-label");
+ fingerprint_button_clicked (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)), label, label2, user);
+ g_object_unref (user);
+}
+
+static gint
+sort_users (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer data)
+{
+ UmUser *ua, *ub;
+ gint sa, sb;
+ gint result;
+
+ gtk_tree_model_get (model, a, USER_COL, &ua, SORT_KEY_COL, &sa, -1);
+ gtk_tree_model_get (model, b, USER_COL, &ub, SORT_KEY_COL, &sb, -1);
+
+ if (sa < sb) {
+ result = -1;
+ }
+ else if (sa > sb) {
+ result = 1;
+ }
+ else {
+ result = um_user_collate (ua, ub);
+ }
+
+ if (ua) {
+ g_object_unref (ua);
+ }
+ if (ub) {
+ g_object_unref (ub);
+ }
+
+ return result;
+}
+
+static gboolean
+dont_select_headings (GtkTreeSelection *selection,
+ GtkTreeModel *model,
+ GtkTreePath *path,
+ gboolean selected,
+ gpointer data)
+{
+ GtkTreeIter iter;
+ gboolean is_user;
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter, USER_ROW_COL, &is_user, -1);
+
+ return is_user;
+}
+
+static void
+users_loaded (UmUserManager *manager,
+ UmUserPanelPrivate *d)
+{
+ GSList *list, *l;
+ UmUser *user;
+ GtkWidget *dialog;
+
+ if (um_user_manager_no_service (d->um)) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_OTHER,
+ GTK_BUTTONS_CLOSE,
+ _("Failed to contact the accounts service"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("Please make sure that the AccountService is installed and enabled."));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gtk_main_quit), NULL);
+ gtk_widget_show (dialog);
+ }
+
+ list = um_user_manager_list_users (d->um);
+ g_debug ("Got %d users\n", g_slist_length (list));
+
+ g_signal_connect (d->um, "user-changed", G_CALLBACK (user_changed), d);
+
+ for (l = list; l; l = l->next) {
+ user = l->data;
+ g_debug ("adding user %s\n", um_user_get_real_name (user));
+ user_added (d->um, user, d);
+ }
+ g_slist_free (list);
+
+ g_signal_connect (d->um, "user-added", G_CALLBACK (user_added), d);
+ g_signal_connect (d->um, "user-removed", G_CALLBACK (user_removed), d);
+}
+
+static void
+add_unlock_tooltip (GtkWidget *button)
+{
+ const gchar *names[3];
+ GIcon *icon;
+
+ names[0] = "changes-prevent-symbolic";
+ names[1] = "changes-prevent";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+ setup_tooltip_with_embedded_icon (button,
+ _("To make changes,\nclick the * icon first"),
+ "*",
+ icon);
+ g_object_unref (icon);
+ g_signal_connect (button, "button-release-event",
+ G_CALLBACK (show_tooltip_now), NULL);
+}
+
+static void
+remove_unlock_tooltip (GtkWidget *button)
+{
+ setup_tooltip_with_embedded_icon (button, NULL, NULL, NULL);
+ g_signal_handlers_disconnect_by_func (button,
+ G_CALLBACK (show_tooltip_now), NULL);
+}
+
+static void
+lockbutton_changed (UmLockButton *button,
+ gpointer data)
+{
+ UmUserPanelPrivate *d = data;
+ gboolean is_authorized;
+ gboolean self_selected;
+ UmUser *user;
+ GtkWidget *widget;
+
+ user = get_selected_user (d);
+ if (!user) {
+ return;
+ }
+
+ is_authorized = g_permission_get_allowed (G_PERMISSION (d->permission));
+ self_selected = um_user_get_uid (user) == geteuid ();
+
+ widget = get_widget (d, "add-user-button");
+ gtk_widget_set_sensitive (widget, is_authorized);
+ if (is_authorized) {
+ setup_tooltip_with_embedded_icon (widget, _("Create a user"), NULL, NULL);
+ }
+ else {
+ const gchar *names[3];
+ GIcon *icon;
+
+ names[0] = "changes-prevent-symbolic";
+ names[1] = "changes-prevent";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+ setup_tooltip_with_embedded_icon (widget,
+ _("To create a user,\nclick the * icon first"),
+ "*",
+ icon);
+ g_object_unref (icon);
+ }
+
+ widget = get_widget (d, "delete-user-button");
+ gtk_widget_set_sensitive (widget, is_authorized && !self_selected);
+ if (is_authorized) {
+ setup_tooltip_with_embedded_icon (widget, _("Delete the selected user"), NULL, NULL);
+ }
+ else {
+ const gchar *names[3];
+ GIcon *icon;
+
+ names[0] = "changes-prevent-symbolic";
+ names[1] = "changes-prevent";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+
+ setup_tooltip_with_embedded_icon (widget,
+ _("To delete the selected user,\nclick the * icon first"),
+ "*",
+ icon);
+ g_object_unref (icon);
+ }
+
+ if (is_authorized) {
+ um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-type-combo")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "account-type-combo"));
+ }
+ else {
+ um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-type-combo")), FALSE);
+ add_unlock_tooltip (get_widget (d, "account-type-combo"));
+ }
+
+ if (is_authorized || self_selected) {
+ gtk_widget_show (get_widget (d, "user-icon-button"));
+ gtk_widget_hide (get_widget (d, "user-icon-nonbutton"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "full-name-entry")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "full-name-entry"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "account-email-entry")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "account-email-entry"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "account-location-entry")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "account-location-entry"));
+
+ um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-language-combo")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "account-language-combo"));
+
+ um_editable_button_set_editable (UM_EDITABLE_BUTTON (get_widget (d, "account-password-button")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "account-password-button"));
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (get_widget (d, "account-fingerprint-notebook")), 1);
+ }
+ else {
+ gtk_widget_hide (get_widget (d, "user-icon-button"));
+ gtk_widget_show (get_widget (d, "user-icon-nonbutton"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "full-name-entry")), FALSE);
+ add_unlock_tooltip (get_widget (d, "full-name-entry"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "account-email-entry")), FALSE);
+ add_unlock_tooltip (get_widget (d, "account-email-entry"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "account-location-entry")), FALSE);
+ add_unlock_tooltip (get_widget (d, "account-location-entry"));
+
+ um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-language-combo")), FALSE);
+ add_unlock_tooltip (get_widget (d, "account-language-combo"));
+
+ um_editable_button_set_editable (UM_EDITABLE_BUTTON (get_widget (d, "account-password-button")), FALSE);
+ add_unlock_tooltip (get_widget (d, "account-password-button"));
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (get_widget (d, "account-fingerprint-notebook")), 0);
+ }
+
+ um_password_dialog_set_privileged (d->password_dialog, is_authorized);
+}
+
+static gboolean
+match_user (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer search_data)
+{
+ UmUser *user;
+ const gchar *name;
+ gchar *normalized_key = NULL;
+ gchar *normalized_name = NULL;
+ gchar *case_normalized_key = NULL;
+ gchar *case_normalized_name = NULL;
+ gchar *p;
+ gboolean result = TRUE;
+ gint i;
+
+ gtk_tree_model_get (model, iter, USER_COL, &user, -1);
+
+ if (!user) {
+ goto out;
+ }
+
+ normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
+ if (!normalized_key) {
+ goto out;
+ }
+
+ case_normalized_key = g_utf8_casefold (normalized_key, -1);
+
+ for (i = 0; i < 2; i++) {
+ if (i == 0) {
+ name = um_user_get_real_name (user);
+ }
+ else {
+ name = um_user_get_user_name (user);
+ }
+ g_free (normalized_name);
+ normalized_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
+ if (normalized_name) {
+ g_free (case_normalized_name);
+ case_normalized_name = g_utf8_casefold (normalized_name, -1);
+ p = strstr (case_normalized_name, case_normalized_key);
+
+ /* poor man's \b */
+ if (p == case_normalized_name || (p && p[-1] == ' ')) {
+ result = FALSE;
+ break;
+ }
+ }
+ }
+
+ out:
+ if (user) {
+ g_object_unref (user);
+ }
+ g_free (normalized_key);
+ g_free (case_normalized_key);
+ g_free (normalized_name);
+ g_free (case_normalized_name);
+
+ return result;
+}
+
+static void
+setup_main_window (UmUserPanelPrivate *d)
+{
+ GtkWidget *userlist;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ GtkTreeSelection *selection;
+ GtkWidget *button;
+ GtkTreeIter iter;
+ gint expander_size;
+ GtkWidget *box;
+ gchar *title;
+ GIcon *icon;
+ const gchar *names[3];
+
+ userlist = get_widget (d, "list-treeview");
+ store = gtk_list_store_new (NUM_USER_LIST_COLS,
+ UM_TYPE_USER,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN,
+ G_TYPE_INT);
+ model = (GtkTreeModel *)store;
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model), sort_users, NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (userlist), model);
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (userlist), USER_COL);
+ gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (userlist),
+ match_user, NULL, NULL);
+
+ g_signal_connect (d->um, "users-loaded", G_CALLBACK (users_loaded), d);
+
+ gtk_widget_style_get (userlist, "expander-size", &expander_size, NULL);
+ gtk_tree_view_set_level_indentation (GTK_TREE_VIEW (userlist), - (expander_size + 6));
+
+ title = g_strdup_printf ("<small><span foreground=\"#555555\">%s</span></small>", _("My Account"));
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ TITLE_COL, title,
+ HEADING_ROW_COL, TRUE,
+ SORT_KEY_COL, 0,
+ -1);
+ g_free (title);
+
+ title = g_strdup_printf ("<small><span foreground=\"#555555\">%s</span></small>", _("Other Accounts"));
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ TITLE_COL, title,
+ HEADING_ROW_COL, TRUE,
+ SORT_KEY_COL, 2,
+ -1);
+ g_free (title);
+
+ column = gtk_tree_view_column_new ();
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, FALSE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "pixbuf", FACE_COL);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "visible", USER_ROW_COL);
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, "width-chars", 18, NULL);
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "markup", NAME_COL);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "visible", USER_ROW_COL);
+ cell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "markup", TITLE_COL);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "visible", HEADING_ROW_COL);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (userlist), column);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (userlist));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+ g_signal_connect (selection, "changed", G_CALLBACK (selected_user_changed), d);
+ gtk_tree_selection_set_select_function (selection, dont_select_headings, NULL, NULL);
+
+ button = get_widget (d, "add-user-button");
+ g_signal_connect (button, "clicked", G_CALLBACK (add_user), d);
+
+ button = get_widget (d, "delete-user-button");
+ g_signal_connect (button, "clicked", G_CALLBACK (delete_user), d);
+
+ button = get_widget (d, "user-icon-nonbutton");
+ add_unlock_tooltip (button);
+
+ button = get_widget (d, "full-name-entry");
+ g_signal_connect (button, "editing-done", G_CALLBACK (change_name_done), d);
+
+ button = get_widget (d, "account-type-combo");
+ g_signal_connect (button, "editing-done", G_CALLBACK (account_type_changed), d);
+
+ button = get_widget (d, "account-password-button");
+ g_signal_connect (button, "start-editing", G_CALLBACK (change_password), d);
+
+ button = get_widget (d, "account-email-entry");
+ g_signal_connect (button, "editing-done", G_CALLBACK (change_email_done), d);
+
+ button = get_widget (d, "account-language-combo");
+ g_signal_connect (button, "editing-done", G_CALLBACK (language_changed), d);
+
+ button = get_widget (d, "account-location-entry");
+ g_signal_connect (button, "editing-done", G_CALLBACK (change_location_done), d);
+
+ button = get_widget (d, "account-fingerprint-button");
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (change_fingerprint), d);
+
+ d->permission = polkit_permission_new_sync ("org.freedesktop.accounts.user-administration", NULL, NULL, NULL);
+ button = um_lock_button_new (d->permission);
+ gtk_widget_set_margin_top (button, 12);
+ gtk_widget_show (button);
+ box = get_widget (d, "userlist-vbox");
+ gtk_box_pack_end (GTK_BOX (box), button, FALSE, FALSE, 0);
+ g_signal_connect (button, "changed",
+ G_CALLBACK (lockbutton_changed), d);
+ lockbutton_changed (UM_LOCK_BUTTON (button), d);
+ d->lock_button = button;
+
+ button = get_widget (d, "add-user-button");
+ names[0] = "changes-prevent-symbolic";
+ names[1] = "changes-prevent";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+ setup_tooltip_with_embedded_icon (button,
+ _("To create a user,\nclick the * icon first"),
+ "*",
+ icon);
+ button = get_widget (d, "delete-user-button");
+ setup_tooltip_with_embedded_icon (button,
+ _("To delete the selected user,\nclick the * icon first"),
+ "*",
+ icon);
+ g_object_unref (icon);
+}
+
+static void
+um_user_panel_init (UmUserPanel *self)
+{
+ UmUserPanelPrivate *d;
+ GError *error;
+ volatile GType type;
+ const gchar *filename;
+ GtkWidget *button;
+
+ dbus_g_object_register_marshaller (fprintd_marshal_VOID__STRING_BOOLEAN,
+ G_TYPE_NONE, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INVALID);
+
+ d = self->priv = UM_USER_PANEL_PRIVATE (self);
+
+ /* register types that the builder might need */
+ type = um_strength_bar_get_type ();
+ type = um_editable_button_get_type ();
+ type = um_editable_entry_get_type ();
+ type = um_editable_combo_get_type ();
+
+ d->builder = gtk_builder_new ();
+ d->um = um_user_manager_ref_default ();
+ d->authority = polkit_authority_get_sync (NULL, NULL);
+
+ filename = UIDIR "/user-accounts-dialog.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
+ filename = "../data/user-accounts-dialog.ui";
+ }
+ error = NULL;
+ if (!gtk_builder_add_from_file (d->builder, filename, &error)) {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ setup_main_window (d);
+ d->login_options = um_login_options_new (d->builder);
+ d->account_dialog = um_account_dialog_new ();
+ d->password_dialog = um_password_dialog_new ();
+ button = get_widget (d, "user-icon-button");
+ d->photo_dialog = um_photo_dialog_new (button);
+ d->notebook = get_widget (d, "top-level-notebook");
+ gtk_widget_reparent (d->notebook, GTK_WIDGET (self));
+}
+
+static void
+um_user_panel_dispose (GObject *object)
+{
+ UmUserPanelPrivate *priv = UM_USER_PANEL (object)->priv;
+
+ if (priv->um) {
+ g_object_unref (priv->um);
+ priv->um = NULL;
+ }
+ if (priv->builder) {
+ g_object_unref (priv->builder);
+ priv->builder = NULL;
+ }
+ if (priv->account_dialog) {
+ um_account_dialog_free (priv->account_dialog);
+ priv->account_dialog = NULL;
+ }
+ if (priv->password_dialog) {
+ um_password_dialog_free (priv->password_dialog);
+ priv->password_dialog = NULL;
+ }
+ if (priv->photo_dialog) {
+ um_photo_dialog_free (priv->photo_dialog);
+ priv->photo_dialog = NULL;
+ }
+ if (priv->login_options) {
+ um_login_options_free (priv->login_options);
+ priv->login_options = NULL;
+ }
+ if (priv->authority) {
+ g_object_unref (priv->authority);
+ priv->authority = NULL;
+ }
+
+ G_OBJECT_CLASS (um_user_panel_parent_class)->dispose (object);
+}
+
+static void
+um_user_panel_class_init (UmUserPanelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = um_user_panel_dispose;
+
+ g_type_class_add_private (klass, sizeof (UmUserPanelPrivate));
+}
+
+static void
+um_user_panel_class_finalize (UmUserPanelClass *klass)
+{
+}
+
+void
+um_user_panel_register (GIOModule *module)
+{
+ um_user_panel_register_type (G_TYPE_MODULE (module));
+ g_io_extension_point_implement (CC_SHELL_PANEL_EXTENSION_POINT,
+ UM_TYPE_USER_PANEL, "user-accounts", 0);
+}
diff --git a/panels/user-accounts/um-user-panel.h b/panels/user-accounts/um-user-panel.h
new file mode 100644
index 0000000..4f5aec0
--- /dev/null
+++ b/panels/user-accounts/um-user-panel.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef _UM_USER_PANEL_H
+#define _UM_USER_PANEL_H
+
+#include <libgnome-control-center/cc-panel.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_USER_PANEL um_user_panel_get_type()
+
+#define UM_USER_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_USER_PANEL, UmUserPanel))
+#define UM_USER_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_USER_PANEL, UmUserPanelClass))
+#define UM_IS_USER_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_USER_PANEL))
+#define UM_IS_USER_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_USER_PANEL))
+#define UM_USER_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_USER_PANEL, UmUserPanelClass))
+
+typedef struct _UmUserPanel UmUserPanel;
+typedef struct _UmUserPanelClass UmUserPanelClass;
+typedef struct _UmUserPanelPrivate UmUserPanelPrivate;
+
+struct _UmUserPanel
+{
+ CcPanel parent;
+
+ UmUserPanelPrivate *priv;
+};
+
+struct _UmUserPanelClass
+{
+ CcPanelClass parent_class;
+};
+
+GType um_user_panel_get_type (void) G_GNUC_CONST;
+
+void um_user_panel_register (GIOModule *module);
+
+G_END_DECLS
+
+#endif /* _UM_USER_PANEL_H */
diff --git a/panels/user-accounts/um-user.c b/panels/user-accounts/um-user.c
new file mode 100644
index 0000000..ce92b4e
--- /dev/null
+++ b/panels/user-accounts/um-user.c
@@ -0,0 +1,1093 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2004-2005 James M. Cape <jcape ignore-your tv>.
+ * Copyright (C) 2007-2008 William Jon McCann <mccann jhu edu>
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define _XOPEN_SOURCE
+
+#include "config.h"
+
+#include <float.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include <gio/gunixoutputstream.h>
+
+#include <dbus/dbus-glib.h>
+
+#include "um-user.h"
+#include "um-account-type.h"
+#include "um-utils.h"
+
+
+ #define UM_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_USER, UmUserClass))
+ #define UM_IS_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_USER))
+#define UM_USER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), UM_TYPE_USER, UmUserClass))
+
+#define MAX_FILE_SIZE 65536
+
+typedef struct {
+ uid_t uid;
+ gchar *user_name;
+ gchar *real_name;
+ gint account_type;
+ gint password_mode;
+ gchar *password_hint;
+ gchar *email;
+ gchar *language;
+ gchar *location;
+ guint64 login_frequency;
+ gchar *icon_file;
+ gboolean locked;
+ gboolean automatic_login;
+} UserProperties;
+
+static void
+collect_props (const gchar *key,
+ const GValue *value,
+ UserProperties *props)
+{
+ gboolean handled = TRUE;
+
+ if (strcmp (key, "Uid") == 0) {
+ props->uid = g_value_get_uint64 (value);
+ }
+ else if (strcmp (key, "UserName") == 0) {
+ props->user_name = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "RealName") == 0) {
+ props->real_name = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "AccountType") == 0) {
+ props->account_type = g_value_get_int (value);
+ }
+ else if (strcmp (key, "Email") == 0) {
+ props->email = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "Language") == 0) {
+ props->language = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "Location") == 0) {
+ props->location = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "LoginFrequency") == 0) {
+ props->login_frequency = g_value_get_uint64 (value);
+ }
+ else if (strcmp (key, "IconFile") == 0) {
+ props->icon_file = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "Locked") == 0) {
+ props->locked = g_value_get_boolean (value);
+ }
+ else if (strcmp (key, "AutomaticLogin") == 0) {
+ props->automatic_login = g_value_get_boolean (value);
+ }
+ else if (strcmp (key, "PasswordMode") == 0) {
+ props->password_mode = g_value_get_int (value);
+ }
+ else if (strcmp (key, "PasswordHint") == 0) {
+ props->password_hint = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "HomeDirectory") == 0) {
+ /* ignore */
+ }
+ else if (strcmp (key, "Shell") == 0) {
+ /* ignore */
+ }
+ else {
+ handled = FALSE;
+ }
+
+ if (!handled)
+ g_debug ("unhandled property %s", key);
+}
+
+static void
+user_properties_free (UserProperties *props)
+{
+ g_free (props->user_name);
+ g_free (props->real_name);
+ g_free (props->password_hint);
+ g_free (props->email);
+ g_free (props->language);
+ g_free (props->location);
+ g_free (props->icon_file);
+ g_free (props);
+}
+
+static UserProperties *
+user_properties_get (DBusGConnection *bus,
+ const gchar *object_path)
+{
+ UserProperties *props;
+ GError *error;
+ DBusGProxy *proxy;
+ GHashTable *hash_table;
+
+ props = g_new0 (UserProperties, 1);
+
+ proxy = dbus_g_proxy_new_for_name (bus,
+ "org.freedesktop.Accounts",
+ object_path,
+ "org.freedesktop.DBus.Properties");
+ error = NULL;
+ if (!dbus_g_proxy_call (proxy,
+ "GetAll",
+ &error,
+ G_TYPE_STRING,
+ "org.freedesktop.Accounts.User",
+ G_TYPE_INVALID,
+ dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE),
+ &hash_table,
+ G_TYPE_INVALID)) {
+ g_debug ("Error calling GetAll() when retrieving properties for %s: %s", object_path, error->message);
+ g_error_free (error);
+ g_free (props);
+ props = NULL;
+ goto out;
+ }
+ g_hash_table_foreach (hash_table, (GHFunc) collect_props, props);
+ g_hash_table_unref (hash_table);
+
+ out:
+ g_object_unref (proxy);
+ return props;
+}
+
+
+struct _UmUser {
+ GObject parent;
+
+ DBusGConnection *bus;
+ DBusGProxy *proxy;
+ gchar *object_path;
+
+ UserProperties *props;
+
+ gchar *display_name;
+};
+
+typedef struct _UmUserClass
+{
+ GObjectClass parent_class;
+} UmUserClass;
+
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void um_user_finalize (GObject *object);
+
+G_DEFINE_TYPE (UmUser, um_user, G_TYPE_OBJECT)
+
+static void
+um_user_class_init (UmUserClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->finalize = um_user_finalize;
+
+ signals[CHANGED] = g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+
+static void
+um_user_init (UmUser *user)
+{
+}
+
+static void
+um_user_finalize (GObject *object)
+{
+ UmUser *user;
+
+ user = UM_USER (object);
+
+ dbus_g_connection_unref (user->bus);
+ g_free (user->object_path);
+
+ if (user->proxy != NULL)
+ g_object_unref (user->proxy);
+
+ if (user->props != NULL)
+ user_properties_free (user->props);
+
+ (*G_OBJECT_CLASS (um_user_parent_class)->finalize) (object);
+}
+
+uid_t
+um_user_get_uid (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), -1);
+
+ return user->props->uid;
+}
+
+const gchar *
+um_user_get_real_name (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), NULL);
+
+ return user->props->real_name;
+}
+
+const gchar *
+um_user_get_display_name (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), NULL);
+
+ return user->display_name ? user->display_name
+ : user->props->real_name;
+}
+
+const gchar *
+um_user_get_user_name (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), NULL);
+
+ return user->props->user_name;
+}
+
+gint
+um_user_get_account_type (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), UM_ACCOUNT_TYPE_STANDARD);
+
+ return user->props->account_type;
+}
+
+gulong
+um_user_get_login_frequency (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), 0);
+
+ return user->props->login_frequency;
+}
+
+gint
+um_user_collate (UmUser *user1,
+ UmUser *user2)
+{
+ const char *str1;
+ const char *str2;
+ gulong num1;
+ gulong num2;
+
+ g_return_val_if_fail (UM_IS_USER (user1), 0);
+ g_return_val_if_fail (UM_IS_USER (user2), 0);
+
+ num1 = user1->props->login_frequency;
+ num2 = user2->props->login_frequency;
+ if (num1 > num2) {
+ return -1;
+ }
+
+ if (num1 < num2) {
+ return 1;
+ }
+
+ /* if login frequency is equal try names */
+ if (user1->props->real_name != NULL) {
+ str1 = user1->props->real_name;
+ } else {
+ str1 = user1->props->user_name;
+ }
+
+ if (user2->props->real_name != NULL) {
+ str2 = user2->props->real_name;
+ } else {
+ str2 = user2->props->user_name;
+ }
+
+ if (str1 == NULL && str2 != NULL) {
+ return -1;
+ }
+
+ if (str1 != NULL && str2 == NULL) {
+ return 1;
+ }
+
+ if (str1 == NULL && str2 == NULL) {
+ return 0;
+ }
+
+ return g_utf8_collate (str1, str2);
+}
+
+static gboolean
+check_user_file (const char *filename,
+ gssize max_file_size)
+{
+ struct stat fileinfo;
+
+ if (max_file_size < 0) {
+ max_file_size = G_MAXSIZE;
+ }
+
+ /* Exists/Readable? */
+ if (stat (filename, &fileinfo) < 0) {
+ g_debug ("File does not exist");
+ return FALSE;
+ }
+
+ /* Is a regular file */
+ if (G_UNLIKELY (!S_ISREG (fileinfo.st_mode))) {
+ g_debug ("File is not a regular file");
+ return FALSE;
+ }
+
+ /* Size is kosher? */
+ if (G_UNLIKELY (fileinfo.st_size > max_file_size)) {
+ g_debug ("File is too large");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static cairo_surface_t *
+surface_from_pixbuf (GdkPixbuf *pixbuf)
+{
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ surface = cairo_image_surface_create (gdk_pixbuf_get_has_alpha (pixbuf) ?
+ CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+ cr = cairo_create (surface);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ return surface;
+}
+
+/**
+ * go_cairo_convert_data_to_pixbuf:
+ * @src: a pointer to pixel data in cairo format
+ * @dst: a pointer to pixel data in pixbuf format
+ * @width: image width
+ * @height: image height
+ * @rowstride: data rowstride
+ *
+ * Converts the pixel data stored in @src in CAIRO_FORMAT_ARGB32 cairo format
+ * to GDK_COLORSPACE_RGB pixbuf format and move them
+ * to @dst. If @src == @dst, pixel are converted in place.
+ **/
+
+static void
+go_cairo_convert_data_to_pixbuf (unsigned char *dst,
+ unsigned char const *src,
+ int width,
+ int height,
+ int rowstride)
+{
+ int i,j;
+ unsigned int t;
+ unsigned char a, b, c;
+
+ g_return_if_fail (dst != NULL);
+
+#define MULT(d,c,a,t) G_STMT_START { t = (a)? c * 255 / a: 0; d = t;} G_STMT_END
+
+ if (src == dst || src == NULL) {
+ for (i = 0; i < height; i++) {
+ for (j = 0; j < width; j++) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ MULT(a, dst[2], dst[3], t);
+ MULT(b, dst[1], dst[3], t);
+ MULT(c, dst[0], dst[3], t);
+ dst[0] = a;
+ dst[1] = b;
+ dst[2] = c;
+#else
+ MULT(a, dst[1], dst[0], t);
+ MULT(b, dst[2], dst[0], t);
+ MULT(c, dst[3], dst[0], t);
+ dst[3] = dst[0];
+ dst[0] = a;
+ dst[1] = b;
+ dst[2] = c;
+#endif
+ dst += 4;
+ }
+ dst += rowstride - width * 4;
+ }
+ } else {
+ for (i = 0; i < height; i++) {
+ for (j = 0; j < width; j++) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ MULT(dst[0], src[2], src[3], t);
+ MULT(dst[1], src[1], src[3], t);
+ MULT(dst[2], src[0], src[3], t);
+ dst[3] = src[3];
+#else
+ MULT(dst[0], src[1], src[0], t);
+ MULT(dst[1], src[2], src[0], t);
+ MULT(dst[2], src[3], src[0], t);
+ dst[3] = src[0];
+#endif
+ src += 4;
+ dst += 4;
+ }
+ src += rowstride - width * 4;
+ dst += rowstride - width * 4;
+ }
+ }
+#undef MULT
+}
+
+static void
+cairo_to_pixbuf (guint8 *src_data,
+ GdkPixbuf *dst_pixbuf)
+{
+ unsigned char *src;
+ unsigned char *dst;
+ guint w;
+ guint h;
+ guint rowstride;
+
+ w = gdk_pixbuf_get_width (dst_pixbuf);
+ h = gdk_pixbuf_get_height (dst_pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (dst_pixbuf);
+
+ dst = gdk_pixbuf_get_pixels (dst_pixbuf);
+ src = src_data;
+
+ go_cairo_convert_data_to_pixbuf (dst, src, w, h, rowstride);
+}
+
+static GdkPixbuf *
+frame_pixbuf (GdkPixbuf *source)
+{
+ GdkPixbuf *dest;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ guint w;
+ guint h;
+ guint rowstride;
+ int frame_width;
+ double radius;
+ guint8 *data;
+
+ frame_width = 2;
+
+ w = gdk_pixbuf_get_width (source) + frame_width * 2;
+ h = gdk_pixbuf_get_height (source) + frame_width * 2;
+ radius = w / 10;
+
+ dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ w,
+ h);
+ rowstride = gdk_pixbuf_get_rowstride (dest);
+
+
+ data = g_new0 (guint8, h * rowstride);
+
+ surface = cairo_image_surface_create_for_data (data,
+ CAIRO_FORMAT_ARGB32,
+ w,
+ h,
+ rowstride);
+ cr = cairo_create (surface);
+ cairo_surface_destroy (surface);
+
+ /* set up image */
+ cairo_rectangle (cr, 0, 0, w, h);
+ cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0);
+ cairo_fill (cr);
+
+ rounded_rectangle (cr, 1.0, 0.5, 0.5, radius, w - 1, h - 1);
+ cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.3);
+ cairo_fill_preserve (cr);
+
+ surface = surface_from_pixbuf (source);
+ cairo_set_source_surface (cr, surface, frame_width, frame_width);
+ cairo_fill (cr);
+ cairo_surface_destroy (surface);
+
+ cairo_to_pixbuf (data, dest);
+
+ cairo_destroy (cr);
+ g_free (data);
+
+ return dest;
+}
+
+GdkPixbuf *
+um_user_render_icon (UmUser *user,
+ gboolean with_frame,
+ gint icon_size)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *framed;
+ gboolean res;
+ GError *error;
+
+ g_return_val_if_fail (UM_IS_USER (user), NULL);
+ g_return_val_if_fail (icon_size > 12, NULL);
+
+ pixbuf = NULL;
+ if (user->props->icon_file) {
+ res = check_user_file (user->props->icon_file,
+ MAX_FILE_SIZE);
+ if (res) {
+ pixbuf = gdk_pixbuf_new_from_file_at_size (user->props->icon_file,
+ icon_size,
+ icon_size,
+ NULL);
+ }
+ else {
+ pixbuf = NULL;
+ }
+ }
+
+ if (pixbuf != NULL) {
+ goto out;
+ }
+
+ error = NULL;
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+
+ "avatar-default",
+ icon_size,
+ GTK_ICON_LOOKUP_FORCE_SIZE,
+ &error);
+ if (error) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ out:
+
+ if (pixbuf != NULL && with_frame) {
+ framed = frame_pixbuf (pixbuf);
+ if (framed != NULL) {
+ g_object_unref (pixbuf);
+ pixbuf = framed;
+ }
+ }
+
+ return pixbuf;
+}
+
+const gchar *
+um_user_get_email (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), NULL);
+
+ return user->props->email;
+}
+
+const gchar *
+um_user_get_language (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), NULL);
+
+ if (*user->props->language == '\0')
+ return NULL;
+ return user->props->language;
+}
+
+const gchar *
+um_user_get_location (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), NULL);
+
+ return user->props->location;
+}
+
+gint
+um_user_get_password_mode (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), UM_PASSWORD_MODE_NONE);
+
+ return user->props->password_mode;
+}
+
+const char *
+um_user_get_password_hint (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), NULL);
+
+ return user->props->password_hint;
+}
+
+const char *
+um_user_get_icon_file (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), NULL);
+
+ return user->props->icon_file;
+}
+
+gboolean
+um_user_get_locked (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), FALSE);
+
+ return user->props->locked;
+}
+
+gboolean
+um_user_get_automatic_login (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), FALSE);
+
+ return user->props->automatic_login;
+}
+
+const gchar *
+um_user_get_object_path (UmUser *user)
+{
+ g_return_val_if_fail (UM_IS_USER (user), NULL);
+
+ return user->object_path;
+}
+
+static gboolean
+update_info (UmUser *user)
+{
+ UserProperties *props;
+
+ props = user_properties_get (user->bus, user->object_path);
+ if (props != NULL) {
+ if (user->props != NULL)
+ user_properties_free (user->props);
+ user->props = props;
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+static void
+changed_handler (DBusGProxy *proxy,
+ gpointer *data)
+{
+ UmUser *user = UM_USER (data);
+
+ if (update_info (user)) {
+ if (user->display_name != NULL) {
+ um_user_show_full_display_name (user);
+ }
+
+ g_signal_emit (user, signals[CHANGED], 0);
+ }
+}
+
+UmUser *
+um_user_new_from_object_path (const gchar *object_path)
+{
+ UmUser *user;
+ GError *error;
+
+ user = (UmUser *)g_object_new (UM_TYPE_USER, NULL);
+ user->object_path = g_strdup (object_path);
+
+ error = NULL;
+ user->bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+ if (user->bus == NULL) {
+ g_warning ("Couldn't connect to system bus: %s", error->message);
+ goto error;
+ }
+
+ user->proxy = dbus_g_proxy_new_for_name (user->bus,
+ "org.freedesktop.Accounts",
+ user->object_path,
+ "org.freedesktop.Accounts.User");
+ dbus_g_proxy_set_default_timeout (user->proxy, INT_MAX);
+ dbus_g_proxy_add_signal (user->proxy, "Changed", G_TYPE_INVALID);
+
+ dbus_g_proxy_connect_signal (user->proxy, "Changed",
+ G_CALLBACK (changed_handler), user, NULL);
+
+ if (!update_info (user))
+ goto error;
+
+ return user;
+
+ error:
+ g_object_unref (user);
+ return NULL;
+}
+
+void
+um_user_set_email (UmUser *user,
+ const gchar *email)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetEmail",
+ &error,
+ G_TYPE_STRING, email,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetEmail call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_language (UmUser *user,
+ const gchar *language)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetLanguage",
+ &error,
+ G_TYPE_STRING, language,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetLanguage call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_location (UmUser *user,
+ const gchar *location)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetLocation",
+ &error,
+ G_TYPE_STRING, location,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetLocation call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_user_name (UmUser *user,
+ const gchar *user_name)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetUserName",
+ &error,
+ G_TYPE_STRING, user_name,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetUserName call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_real_name (UmUser *user,
+ const gchar *real_name)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetRealName",
+ &error,
+ G_TYPE_STRING, real_name,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetRealName call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_icon_file (UmUser *user,
+ const gchar *icon_file)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetIconFile",
+ &error,
+ G_TYPE_STRING, icon_file,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetIconFile call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_icon_data (UmUser *user,
+ GdkPixbuf *pixbuf)
+{
+ gchar *path;
+ gint fd;
+ GOutputStream *stream;
+ GError *error;
+
+ path = g_build_filename (g_get_tmp_dir (), "usericonXXXXXX", NULL);
+ fd = g_mkstemp (path);
+
+ if (fd == -1) {
+ g_warning ("failed to create temporary file for image data");
+ g_free (path);
+ return;
+ }
+
+ stream = g_unix_output_stream_new (fd, TRUE);
+
+ error = NULL;
+ if (!gdk_pixbuf_save_to_stream (pixbuf, stream, "png", NULL, &error, NULL)) {
+ g_warning ("failed to save image: %s", error->message);
+ g_error_free (error);
+ g_object_unref (stream);
+ return;
+ }
+
+ g_object_unref (stream);
+
+ um_user_set_icon_file (user, path);
+
+ /* if we ever make the dbus call async, the g_remove call needs
+ * to wait for its completion
+ */
+ g_remove (path);
+
+ g_free (path);
+}
+
+void
+um_user_set_account_type (UmUser *user,
+ gint account_type)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetAccountType",
+ &error,
+ G_TYPE_INT, account_type,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetAccountType call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+static gchar
+salt_char (GRand *rand)
+{
+ gchar salt[] = "ABCDEFGHIJKLMNOPQRSTUVXYZ"
+ "abcdefghijklmnopqrstuvxyz"
+ "./0123456789";
+
+ return salt[g_rand_int_range (rand, 0, G_N_ELEMENTS (salt))];
+}
+
+static gchar *
+make_crypted (const gchar *plain)
+{
+ GString *salt;
+ gchar *result;
+ GRand *rand;
+ gint i;
+
+ rand = g_rand_new ();
+ salt = g_string_sized_new (21);
+
+ /* SHA 256 */
+ g_string_append (salt, "$6$");
+ for (i = 0; i < 16; i++) {
+ g_string_append_c (salt, salt_char (rand));
+ }
+ g_string_append_c (salt, '$');
+
+ result = g_strdup (crypt (plain, salt->str));
+
+ g_string_free (salt, TRUE);
+ g_rand_free (rand);
+
+ return result;
+}
+
+void
+um_user_set_password (UmUser *user,
+ gint password_mode,
+ const gchar *password,
+ const gchar *hint)
+{
+ GError *error = NULL;
+ gchar *crypted;
+
+ if (password_mode == 0) {
+ crypted = make_crypted (password);
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetPassword",
+ &error,
+ G_TYPE_STRING, crypted,
+ G_TYPE_STRING, hint,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetPassword call failed: %s", error->message);
+ g_error_free (error);
+ }
+ memset (crypted, 0, strlen (crypted));
+ g_free (crypted);
+ }
+ else if (password_mode == 3 || password_mode == 4) {
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetLocked",
+ &error,
+ G_TYPE_BOOLEAN, (password_mode == 3),
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetLocked call failed: %s", error->message);
+ g_error_free (error);
+ }
+ }
+ else {
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetPasswordMode",
+ &error,
+ G_TYPE_INT, password_mode,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetPasswordMode call failed: %s", error->message);
+ g_error_free (error);
+ }
+ }
+}
+
+gboolean
+um_user_is_logged_in (UmUser *user)
+{
+ DBusGProxy *proxy;
+ GPtrArray *array;
+ GError *error;
+ gint n_sessions;
+
+ proxy = dbus_g_proxy_new_for_name (user->bus,
+ "org.freedesktop.ConsoleKit",
+ "/org/freedesktop/ConsoleKit/Manager",
+ "org.freedesktop.ConsoleKit.Manager");
+
+ array = NULL;
+ error = NULL;
+ if (!dbus_g_proxy_call (proxy,
+ "GetSessionsForUnixUser",
+ &error,
+ G_TYPE_UINT, um_user_get_uid (user),
+ G_TYPE_INVALID,
+ dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &array,
+ G_TYPE_INVALID)) {
+ g_warning ("GetSessionsForUnixUser failed: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ n_sessions = array->len;
+
+ g_ptr_array_foreach (array, (GFunc)g_free, NULL);
+ g_ptr_array_free (array, TRUE);
+
+ g_object_unref (proxy);
+
+ return n_sessions > 0;
+}
+
+
+void
+um_user_set_automatic_login (UmUser *user,
+ gboolean enabled)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetAutomaticLogin",
+ &error,
+ G_TYPE_BOOLEAN, enabled,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetAutomaticLogin call failed: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+void
+um_user_show_full_display_name (UmUser *user)
+{
+ char *uniq_name;
+
+ g_return_if_fail (UM_IS_USER (user));
+
+ if (user->props->real_name != NULL) {
+ uniq_name = g_strdup_printf ("%s (%s)",
+ user->props->real_name,
+ user->props->user_name);
+ } else {
+ uniq_name = NULL;
+ }
+
+ if (uniq_name && g_strcmp0 (uniq_name, user->display_name) != 0) {
+ g_free (user->display_name);
+ user->display_name = uniq_name;
+ }
+ else {
+ g_free (uniq_name);
+ }
+}
+
+void
+um_user_show_short_display_name (UmUser *user)
+{
+ g_return_if_fail (UM_IS_USER (user));
+
+ if (user->display_name) {
+ g_free (user->display_name);
+ user->display_name = NULL;
+ }
+}
+
diff --git a/panels/user-accounts/um-user.h b/panels/user-accounts/um-user.h
new file mode 100644
index 0000000..43a88b0
--- /dev/null
+++ b/panels/user-accounts/um-user.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2004-2005 James M. Cape <jcape ignore-your tv>.
+ * Copyright (C) 2007-2008 William Jon McCann <mccann jhu edu>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * Facade object for user data, owned by UmUserManager
+ */
+
+#ifndef __UM_USER__
+#define __UM_USER__
+
+#include <sys/types.h>
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "um-account-type.h"
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_USER (um_user_get_type ())
+#define UM_USER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), UM_TYPE_USER, UmUser))
+#define UM_IS_USER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), UM_TYPE_USER))
+
+typedef enum {
+ UM_PASSWORD_MODE_REGULAR,
+ UM_PASSWORD_MODE_SET_AT_LOGIN,
+ UM_PASSWORD_MODE_NONE,
+ UM_PASSWORD_MODE_DISABLED,
+ UM_PASSWORD_MODE_ENABLED
+} UmPasswordMode;
+
+typedef struct _UmUser UmUser;
+
+GType um_user_get_type (void) G_GNUC_CONST;
+
+UmUser *um_user_new_from_object_path (const gchar *path);
+const gchar *um_user_get_object_path (UmUser *user);
+
+uid_t um_user_get_uid (UmUser *user);
+const gchar *um_user_get_user_name (UmUser *user);
+const gchar *um_user_get_real_name (UmUser *user);
+const gchar *um_user_get_display_name (UmUser *user);
+gint um_user_get_account_type (UmUser *user);
+const gchar *um_user_get_email (UmUser *user);
+const gchar *um_user_get_language (UmUser *user);
+const gchar *um_user_get_location (UmUser *user);
+const gchar *um_user_get_home_directory (UmUser *user);
+const gchar *um_user_get_shell (UmUser *user);
+gulong um_user_get_login_frequency (UmUser *user);
+gint um_user_get_password_mode (UmUser *user);
+const gchar *um_user_get_password_hint (UmUser *user);
+const gchar *um_user_get_icon_file (UmUser *user);
+gboolean um_user_get_locked (UmUser *user);
+gboolean um_user_get_automatic_login (UmUser *user);
+
+void um_user_set_user_name (UmUser *user,
+ const gchar *user_name);
+void um_user_set_real_name (UmUser *user,
+ const gchar *real_name);
+void um_user_set_email (UmUser *user,
+ const gchar *email);
+void um_user_set_language (UmUser *user,
+ const gchar *language);
+void um_user_set_location (UmUser *user,
+ const gchar *location);
+void um_user_set_icon_file (UmUser *user,
+ const gchar *filename);
+void um_user_set_icon_data (UmUser *user,
+ GdkPixbuf *pixbuf);
+void um_user_set_account_type (UmUser *user,
+ gint account_type);
+void um_user_set_automatic_login (UmUser *user,
+ gboolean enabled);
+void um_user_set_password (UmUser *user,
+ int password_mode,
+ const gchar *plain,
+ const gchar *password_hint);
+gboolean um_user_is_logged_in (UmUser *user);
+
+GdkPixbuf *um_user_render_icon (UmUser *user,
+ gboolean framed,
+ gint icon_size);
+gint um_user_collate (UmUser *user1,
+ UmUser *user2);
+
+void um_user_show_short_display_name (UmUser *user);
+void um_user_show_full_display_name (UmUser *user);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-utils.c b/panels/user-accounts/um-utils.c
new file mode 100644
index 0000000..86a8add
--- /dev/null
+++ b/panels/user-accounts/um-utils.c
@@ -0,0 +1,382 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "um-utils.h"
+
+typedef struct {
+ gchar *text;
+ gchar *placeholder_str;
+ GIcon *icon;
+ gunichar placeholder;
+ gulong query_id;
+} IconShapeData;
+
+static IconShapeData *
+icon_shape_data_new (const gchar *text,
+ const gchar *placeholder,
+ GIcon *icon)
+{
+ IconShapeData *data;
+
+ data = g_new0 (IconShapeData, 1);
+
+ data->text = g_strdup (text);
+ data->placeholder_str = g_strdup (placeholder);
+ data->placeholder = g_utf8_get_char_validated (placeholder, -1);
+ data->icon = g_object_ref (icon);
+
+ return data;
+}
+
+static void
+icon_shape_data_free (gpointer user_data)
+{
+ IconShapeData *data = user_data;
+
+ g_free (data->text);
+ g_free (data->placeholder_str);
+ g_object_unref (data->icon);
+ g_free (data);
+}
+
+static void
+icon_shape_renderer (cairo_t *cr,
+ PangoAttrShape *attr,
+ gboolean do_path,
+ gpointer user_data)
+{
+ IconShapeData *data = user_data;
+ gdouble x, y;
+
+ cairo_get_current_point (cr, &x, &y);
+ if (GPOINTER_TO_UINT (attr->data) == data->placeholder) {
+ gdouble ascent;
+ gdouble height;
+ gdouble width;
+ GdkPixbuf *pixbuf;
+ GtkIconInfo *info;
+
+ ascent = pango_units_to_double (attr->ink_rect.y);
+ height = pango_units_to_double (attr->ink_rect.height);
+ width = pango_units_to_double (attr->ink_rect.width);
+ info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (),
+ data->icon,
+ (gint)height,
+ GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_USE_BUILTIN);
+ pixbuf = gtk_icon_info_load_icon (info, NULL);
+ gtk_icon_info_free (info);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_reset_clip (cr);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y + ascent);
+ cairo_paint (cr);
+ g_object_unref (pixbuf);
+ }
+}
+
+static PangoAttrList *
+create_shape_attr_list_for_layout (PangoLayout *layout,
+ IconShapeData *data)
+{
+ PangoAttrList *attrs;
+ PangoFontMetrics *metrics;
+ gint ascent, descent;
+ PangoRectangle ink_rect, logical_rect;
+ const gchar *p;
+ const gchar *text;
+ gint placeholder_len;
+
+ /* Get font metrics and prepare fancy shape size */
+ metrics = pango_context_get_metrics (pango_layout_get_context (layout),
+ pango_layout_get_font_description (layout),
+ NULL);
+ ascent = pango_font_metrics_get_ascent (metrics);
+ descent = pango_font_metrics_get_descent (metrics);
+ pango_font_metrics_unref (metrics);
+
+ logical_rect.x = 0;
+ logical_rect.y = - ascent;
+ logical_rect.width = ascent + descent;
+ logical_rect.height = ascent + descent;
+
+ ink_rect = logical_rect;
+
+ attrs = pango_attr_list_new ();
+ text = pango_layout_get_text (layout);
+ placeholder_len = strlen (data->placeholder_str);
+ for (p = text; (p = strstr (p, data->placeholder_str)); p += placeholder_len) {
+ PangoAttribute *attr;
+
+ attr = pango_attr_shape_new_with_data (&ink_rect,
+ &logical_rect,
+ GUINT_TO_POINTER (g_utf8_get_char (p)),
+ NULL, NULL);
+
+ attr->start_index = p - text;
+ attr->end_index = attr->start_index + placeholder_len;
+
+ pango_attr_list_insert (attrs, attr);
+ }
+
+ return attrs;
+}
+
+static gboolean
+query_unlock_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tooltip,
+ GtkTooltip *tooltip,
+ gpointer user_data)
+{
+ GtkWidget *label;
+ PangoLayout *layout;
+ PangoAttrList *attrs;
+ IconShapeData *data;
+
+ data = g_object_get_data (G_OBJECT (widget), "icon-shape-data");
+ label = g_object_get_data (G_OBJECT (widget), "tooltip-label");
+ if (label == NULL) {
+ label = gtk_label_new (data->text);
+ g_object_ref_sink (label);
+ g_object_set_data_full (G_OBJECT (widget),
+ "tooltip-label", label, g_object_unref);
+ }
+
+ layout = gtk_label_get_layout (GTK_LABEL (label));
+ pango_cairo_context_set_shape_renderer (pango_layout_get_context (layout),
+ icon_shape_renderer,
+ data, NULL);
+
+ attrs = create_shape_attr_list_for_layout (layout, data);
+ gtk_label_set_attributes (GTK_LABEL (label), attrs);
+ pango_attr_list_unref (attrs);
+
+ gtk_tooltip_set_custom (tooltip, label);
+
+ return TRUE;
+}
+
+void
+setup_tooltip_with_embedded_icon (GtkWidget *widget,
+ const gchar *text,
+ const gchar *placeholder,
+ GIcon *icon)
+{
+ IconShapeData *data;
+
+ data = g_object_get_data (G_OBJECT (widget), "icon-shape-data");
+ if (data) {
+ gtk_widget_set_has_tooltip (widget, FALSE);
+ g_signal_handler_disconnect (widget, data->query_id);
+ g_object_set_data (G_OBJECT (widget), "icon-shape-data", NULL);
+ g_object_set_data (G_OBJECT (widget), "tooltip-label", NULL);
+ }
+
+ if (!placeholder) {
+ gtk_widget_set_tooltip_text (widget, text);
+ return;
+ }
+
+ data = icon_shape_data_new (text, placeholder, icon);
+ g_object_set_data_full (G_OBJECT (widget),
+ "icon-shape-data",
+ data,
+ icon_shape_data_free);
+
+ gtk_widget_set_has_tooltip (widget, TRUE);
+ data->query_id = g_signal_connect (widget, "query-tooltip",
+ G_CALLBACK (query_unlock_tooltip), NULL);
+
+}
+
+gboolean
+show_tooltip_now (GtkWidget *widget,
+ GdkEvent *event)
+{
+ GtkSettings *settings;
+ gint timeout;
+
+ settings = gtk_widget_get_settings (widget);
+
+ g_object_get (settings, "gtk-tooltip-timeout", &timeout, NULL);
+ g_object_set (settings, "gtk-tooltip-timeout", 1, NULL);
+ gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (widget));
+ g_object_set (settings, "gtk-tooltip-timeout", timeout, NULL);
+
+ return FALSE;
+}
+
+static gboolean
+query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_mode,
+ GtkTooltip *tooltip,
+ gpointer user_data)
+{
+ gchar *tip;
+
+ if (GTK_ENTRY_ICON_SECONDARY == gtk_entry_get_icon_at_pos (GTK_ENTRY (widget), x, y)) {
+ tip = gtk_entry_get_icon_tooltip_text (GTK_ENTRY (widget),
+ GTK_ENTRY_ICON_SECONDARY);
+ gtk_tooltip_set_text (tooltip, tip);
+ g_free (tip);
+
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+static void
+icon_released (GtkEntry *entry,
+ GtkEntryIconPosition pos,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ GtkSettings *settings;
+ gint timeout;
+
+ settings = gtk_widget_get_settings (GTK_WIDGET (entry));
+
+ g_object_get (settings, "gtk-tooltip-timeout", &timeout, NULL);
+ g_object_set (settings, "gtk-tooltip-timeout", 1, NULL);
+ gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (GTK_WIDGET (entry)));
+ g_object_set (settings, "gtk-tooltip-timeout", timeout, NULL);
+}
+
+
+void
+set_entry_validation_error (GtkEntry *entry,
+ const gchar *text)
+{
+ g_object_set (entry, "caps-lock-warning", FALSE, NULL);
+ gtk_entry_set_icon_from_stock (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ GTK_STOCK_DIALOG_ERROR);
+ gtk_entry_set_icon_activatable (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ TRUE);
+ g_signal_connect (entry, "icon-release",
+ G_CALLBACK (icon_released), FALSE);
+ g_signal_connect (entry, "query-tooltip",
+ G_CALLBACK (query_tooltip), NULL);
+ g_object_set (entry, "has-tooltip", TRUE, NULL);
+ gtk_entry_set_icon_tooltip_text (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ text);
+}
+
+void
+clear_entry_validation_error (GtkEntry *entry)
+{
+ gboolean warning;
+
+ g_object_get (entry, "caps-lock-warning", &warning, NULL);
+
+ if (warning)
+ return;
+
+ g_object_set (entry, "has-tooltip", FALSE, NULL);
+ gtk_entry_set_icon_from_pixbuf (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ NULL);
+ g_object_set (entry, "caps-lock-warning", TRUE, NULL);
+}
+
+void
+popup_menu_below_button (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ GtkWidget *button)
+{
+ GtkRequisition menu_req;
+ GtkTextDirection direction;
+ GtkAllocation allocation;
+
+ gtk_widget_size_request (GTK_WIDGET (menu), &menu_req);
+
+ direction = gtk_widget_get_direction (button);
+
+ gdk_window_get_origin (gtk_widget_get_window (button), x, y);
+ gtk_widget_get_allocation (button, &allocation);
+ *x += allocation.x;
+ *y += allocation.y + allocation.height;
+
+ if (direction == GTK_TEXT_DIR_LTR)
+ *x += MAX (allocation.width - menu_req.width, 0);
+ else if (menu_req.width > allocation.width)
+ *x -= menu_req.width - allocation.width;
+
+ *push_in = TRUE;
+}
+
+void
+rounded_rectangle (cairo_t *cr,
+ gdouble aspect,
+ gdouble x,
+ gdouble y,
+ gdouble corner_radius,
+ gdouble width,
+ gdouble height)
+{
+ gdouble radius;
+ gdouble degrees;
+
+ radius = corner_radius / aspect;
+ degrees = G_PI / 180.0;
+
+ cairo_new_sub_path (cr);
+ cairo_arc (cr,
+ x + width - radius,
+ y + radius,
+ radius,
+ -90 * degrees,
+ 0 * degrees);
+ cairo_arc (cr,
+ x + width - radius,
+ y + height - radius,
+ radius,
+ 0 * degrees,
+ 90 * degrees);
+ cairo_arc (cr,
+ x + radius,
+ y + height - radius,
+ radius,
+ 90 * degrees,
+ 180 * degrees);
+ cairo_arc (cr,
+ x + radius,
+ y + radius,
+ radius,
+ 180 * degrees,
+ 270 * degrees);
+ cairo_close_path (cr);
+}
+
diff --git a/panels/user-accounts/um-utils.h b/panels/user-accounts/um-utils.h
new file mode 100644
index 0000000..d6a227e
--- /dev/null
+++ b/panels/user-accounts/um-utils.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * 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 3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __UM_UTILS_H__
+#define __UM_UTILS_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void setup_tooltip_with_embedded_icon (GtkWidget *widget,
+ const gchar *text,
+ const gchar *placeholder,
+ GIcon *icon);
+gboolean show_tooltip_now (GtkWidget *widget,
+ GdkEvent *event);
+
+void set_entry_validation_error (GtkEntry *entry,
+ const gchar *text);
+void clear_entry_validation_error (GtkEntry *entry);
+
+void popup_menu_below_button (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ GtkWidget *button);
+
+void rounded_rectangle (cairo_t *cr,
+ gdouble aspect,
+ gdouble x,
+ gdouble y,
+ gdouble corner_radius,
+ gdouble width,
+ gdouble height);
+
+G_END_DECLS
+
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e3cdea4..186dbf2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -64,6 +64,25 @@ panels/sound/data/gnome-sound-panel.desktop.in.in
panels/sound/data/sounds/gnome-sounds-default.xml.in.in
panels/universal-access/gnome-universal-access-panel.desktop.in.in
[type: gettext/glade]panels/universal-access/uap.ui
+panels/user-accounts/gdm-languages.c
+panels/user-accounts/run-passwd.c
+panels/user-accounts/um-account-dialog.c
+panels/user-accounts/um-account-type.c
+panels/user-accounts/um-fingerprint-dialog.c
+panels/user-accounts/um-language-dialog.c
+panels/user-accounts/um-lockbutton.c
+panels/user-accounts/um-login-options.c
+panels/user-accounts/um-password-dialog.c
+panels/user-accounts/um-photo-dialog.c
+panels/user-accounts/um-user-manager.c
+panels/user-accounts/um-user-panel.c
+panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in
+[type: gettext/glade]panels/user-accounts/data/account-dialog.ui
+[type: gettext/glade]panels/user-accounts/data/language-chooser.ui
+[type: gettext/glade]panels/user-accounts/data/password-dialog.ui
+[type: gettext/glade]panels/user-accounts/data/photo-dialog.ui
+[type: gettext/glade]panels/user-accounts/data/user-accounts-dialog.ui
+[type: gettext/glade]panels/user-accounts/data/account-fingerprint.ui
shell/control-center.c
shell/gnome-control-center.desktop.in.in
shell/gnomecc.directory.in
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index cb6a3a1..93aeda3 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -10,6 +10,8 @@ panels/keybindings/gnome-keybindings-panel.desktop.in
panels/keyboard/gnome-keyboard-panel.desktop.in
panels/sound/data/gnome-sound-panel.desktop.in
panels/sound/data/sounds/gnome-sounds-default.xml.in
+panels/user-accounts/data/gnome-user-accounts-panel.desktop.in
+panels/user-accounts/fingerprint-strings.h
capplets/localization/localization.desktop.in
capplets/mouse/gnome-settings-mouse.desktop.in
panels/network/gnome-network-panel.desktop.in
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]