diff options
author | Sarfraaz Ahmed <asarfraaz@novell.com> | 2005-05-21 16:55:07 +0800 |
---|---|---|
committer | Ahmed Sarfraaz <sarfraaz@src.gnome.org> | 2005-05-21 16:55:07 +0800 |
commit | f3074e998b560938b0c904aabfb317d5764e71d6 (patch) | |
tree | cd6147ea3094b7cd10f3361a5e387677831f2c3f | |
parent | cedf3ee1ff16d652986fa86509ec2d4fb5ac0f58 (diff) | |
download | gsoc2013-evolution-f3074e998b560938b0c904aabfb317d5764e71d6.tar.gz gsoc2013-evolution-f3074e998b560938b0c904aabfb317d5764e71d6.tar.zst gsoc2013-evolution-f3074e998b560938b0c904aabfb317d5764e71d6.zip |
Added a bunch of files to create exchange plugins for UI changes
2005-05-21 Sarfraaz Ahmed <asarfraaz@novell.com>
Added a bunch of files to create exchange plugins for UI changes
* exchange-change-password.[ch]/.glade
* exchange-delegates.[ch]/.glade
* exchange-delegate-user.[ch]
* exchange-folder-size.[ch]
* exchange-folder-tree.glade
svn path=/trunk/; revision=29398
12 files changed, 2921 insertions, 0 deletions
diff --git a/plugins/exchange-account-setup/ChangeLog b/plugins/exchange-account-setup/ChangeLog index c3d909b3f2..3dfd7ec4f5 100644 --- a/plugins/exchange-account-setup/ChangeLog +++ b/plugins/exchange-account-setup/ChangeLog @@ -1,3 +1,12 @@ +2005-05-21 Sarfraaz Ahmed <asarfraaz@novell.com> + + Added a bunch of files to create exchange plugins for UI changes + * exchange-change-password.[ch]/.glade + * exchange-delegates.[ch]/.glade + * exchange-delegate-user.[ch] + * exchange-folder-size.[ch] + * exchange-folder-tree.glade + 2005-05-16 Not Zed <NotZed@Ximian.com> * exchange-account-setup.c: moved e-error to e-util diff --git a/plugins/exchange-account-setup/exchange-change-password.c b/plugins/exchange-account-setup/exchange-change-password.c new file mode 100644 index 0000000000..0e1e13265b --- /dev/null +++ b/plugins/exchange-account-setup/exchange-change-password.c @@ -0,0 +1,134 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* Copyright (C) 2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + */ + +/* exchange-change-password: Change Password code */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "exchange-change-password.h" +#include <exchange-account.h> +#include <e2k-utils.h> + +#include <glade/glade-xml.h> +#include <gtk/gtkdialog.h> +#include <gtk/gtkentry.h> +#include <gtk/gtklabel.h> + +#define FILENAME EVOLUTION_GLADEDIR "/exchange-change-password.glade" +#define ROOTNODE "pass_dialog" +#define STARTNODE "pass_vbox" + +static void +entry_changed (GtkEntry *entry, gpointer user_data) +{ + GladeXML *xml = user_data; + GtkEntry *new_entry, *confirm_entry; + GtkWidget *ok_button; + const char *text; + + new_entry = GTK_ENTRY (glade_xml_get_widget (xml, "new_pass_entry")); + confirm_entry = GTK_ENTRY (glade_xml_get_widget (xml, "confirm_pass_entry")); + ok_button = glade_xml_get_widget (xml, "okbutton1"); + + text = gtk_entry_get_text (new_entry); + if (!text || !*text) { + gtk_widget_set_sensitive (ok_button, FALSE); + return; + } + + text = gtk_entry_get_text (confirm_entry); + if (!text || !*text) { + gtk_widget_set_sensitive (ok_button, FALSE); + return; + } + + gtk_widget_set_sensitive (ok_button, TRUE); +} + +/** + * exchange_get_new_password: + * @existing_password: The user's current password + * @voluntary: %TRUE if the user has chosen "Change Password", + * %FALSE if their old password has expired. + * + * Prompt the user for a new password. + */ +char * +exchange_get_new_password (const char *existing_password, gboolean voluntary) +{ + GladeXML *xml; + GtkWidget *top_widget; + GtkEntry *cur_entry, *new_entry, *confirm_entry; + GtkResponseType response; + GtkLabel *top_label; + char *new_pass; + + xml = glade_xml_new (FILENAME, ROOTNODE, NULL); + top_widget = glade_xml_get_widget (xml, ROOTNODE); + + cur_entry = GTK_ENTRY (glade_xml_get_widget (xml, "current_pass_entry")); + new_entry = GTK_ENTRY (glade_xml_get_widget (xml, "new_pass_entry")); + g_signal_connect (new_entry, "changed", + G_CALLBACK (entry_changed), xml); + confirm_entry = GTK_ENTRY (glade_xml_get_widget (xml, "confirm_pass_entry")); + g_signal_connect (confirm_entry, "changed", + G_CALLBACK (entry_changed), xml); + entry_changed (NULL, xml); + + top_label = GTK_LABEL (glade_xml_get_widget (xml, "pass_label")); + if (voluntary) + gtk_widget_hide (GTK_WIDGET (top_label)); + +run_dialog_again: + response = gtk_dialog_run (GTK_DIALOG (top_widget)); + if (response == GTK_RESPONSE_OK) { + const char *cur_pass, *new_pass1, *new_pass2; + + cur_pass = gtk_entry_get_text (cur_entry); + new_pass1 = gtk_entry_get_text (new_entry); + new_pass2 = gtk_entry_get_text (confirm_entry); + + if (existing_password) { + if (strcmp (cur_pass, existing_password) != 0) { + /* User entered a wrong existing + * password. Prompt him again. + */ + gtk_label_set_text (top_label, _("The current password does not match the existing password for your account. Please enter the correct password")); + gtk_widget_show (GTK_WIDGET (top_label)); + goto run_dialog_again; + } + } + + if (strcmp (new_pass1, new_pass2) != 0) { + gtk_label_set_text (top_label, _("The two passwords do not match. Please re-enter the passwords.")); + gtk_widget_show (GTK_WIDGET (top_label)); + goto run_dialog_again; + } + + new_pass = g_strdup (new_pass1); + } else + new_pass = NULL; + + gtk_widget_destroy (top_widget); + g_object_unref (xml); + + return new_pass; +} diff --git a/plugins/exchange-account-setup/exchange-change-password.glade b/plugins/exchange-account-setup/exchange-change-password.glade new file mode 100644 index 0000000000..fa49da6270 --- /dev/null +++ b/plugins/exchange-account-setup/exchange-change-password.glade @@ -0,0 +1,243 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="pass_dialog">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Change Password</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="has_separator">True</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_SPREAD</property>
+
+ <child>
+ <widget class="GtkButton" id="cancelbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="okbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="pass_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Your current password has expired. Please change your password now.</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.52</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">6</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+
+ <child>
+ <widget class="GtkLabel" id="current_pass_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Current Password:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="new_pass_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">New Password:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="confirm_pass_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Confirm Password:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="new_pass_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">False</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <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>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="confirm_pass_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">False</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <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>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="current_pass_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">False</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_padding">12</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/plugins/exchange-account-setup/exchange-change-password.h b/plugins/exchange-account-setup/exchange-change-password.h new file mode 100644 index 0000000000..155fff897b --- /dev/null +++ b/plugins/exchange-account-setup/exchange-change-password.h @@ -0,0 +1,21 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* Copyright (C) 2004 Novell, Inc. */ + +#ifndef __EXCHANGE_CHANGE_PASSWORD_H__ +#define __EXCHANGE_CHANGE_PASSWORD_H__ + +#include <exchange/exchange-types.h> + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +char *exchange_get_new_password (const char *existing_password, + gboolean voluntary); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EXCHANGE_CHANGE_PASSWORD_H__ */ diff --git a/plugins/exchange-account-setup/exchange-delegates-user.c b/plugins/exchange-account-setup/exchange-delegates-user.c new file mode 100644 index 0000000000..38166747f4 --- /dev/null +++ b/plugins/exchange-account-setup/exchange-delegates-user.c @@ -0,0 +1,332 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* Copyright (C) 2002-2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + */ + +/* ExchangeDelegatesUser: A single delegate */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "exchange-delegates-user.h" + +#include "e2k-global-catalog.h" +#include "e2k-marshal.h" +#include "e2k-sid.h" +#include "e2k-utils.h" + +#include <e-util/e-dialog-utils.h> +#include <e-util/e-dialog-widgets.h> +#include <e-util/e-gtk-utils.h> +#include <glade/glade.h> +#include <gtk/gtkbox.h> +#include <gtk/gtkdialog.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkmenushell.h> +#include <gtk/gtkstock.h> +#include <gtk/gtktogglebutton.h> + +#undef GTK_DISABLE_DEPRECATED +#include <gtk/gtkoptionmenu.h> + +#include <string.h> + +#define EXCHANGE_DELEGATES_USER_SEPARATOR -2 +#define EXCHANGE_DELEGATES_USER_CUSTOM -3 +/* Can't use E2K_PERMISSIONS_ROLE_CUSTOM, because it's -1, which + * means "end of list" to e_dialog_option_menu_get/set + */ + +static const int exchange_perm_map[] = { + E2K_PERMISSIONS_ROLE_NONE, + E2K_PERMISSIONS_ROLE_REVIEWER, + E2K_PERMISSIONS_ROLE_AUTHOR, + E2K_PERMISSIONS_ROLE_EDITOR, + + EXCHANGE_DELEGATES_USER_SEPARATOR, + EXCHANGE_DELEGATES_USER_CUSTOM, + + -1 +}; + +const char *exchange_delegates_user_folder_names[] = { + "calendar", "tasks", "inbox", "contacts" +}; +static const char *widget_names[] = { + "calendar_perms", "task_perms", "inbox_perms", "contact_perms", +}; + + +enum { + EDITED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0 }; + +#define PARENT_TYPE G_TYPE_OBJECT +static GObjectClass *parent_class = NULL; + +static void +finalize (GObject *object) +{ + ExchangeDelegatesUser *user = EXCHANGE_DELEGATES_USER (object); + + if (user->display_name) + g_free (user->display_name); + if (user->dn) + g_free (user->dn); + if (user->entryid) + g_byte_array_free (user->entryid, TRUE); + if (user->sid) + g_object_unref (user->sid); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +class_init (GObjectClass *object_class) +{ + parent_class = g_type_class_ref (PARENT_TYPE); + + object_class->finalize = finalize; + + /* signals */ + signals[EDITED] = + g_signal_new ("edited", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ExchangeDelegatesUserClass, edited), + NULL, NULL, + e2k_marshal_NONE__NONE, + G_TYPE_NONE, 0); +} + +E2K_MAKE_TYPE (exchange_delegates_user, ExchangeDelegatesUser, class_init, NULL, PARENT_TYPE) + +static inline gboolean +is_delegate_role (E2kPermissionsRole role) +{ + return (role == E2K_PERMISSIONS_ROLE_NONE || + role == E2K_PERMISSIONS_ROLE_REVIEWER || + role == E2K_PERMISSIONS_ROLE_AUTHOR || + role == E2K_PERMISSIONS_ROLE_EDITOR); +} + +static void +set_perms (GtkWidget *omenu, E2kPermissionsRole role) +{ + if (!is_delegate_role (role)) { + GtkWidget *menu, *item; + + menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (omenu)); + + item = gtk_menu_item_new (); + gtk_widget_set_sensitive (item, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + item = gtk_menu_item_new_with_label (_("Custom")); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + gtk_widget_show_all (menu); + + role = EXCHANGE_DELEGATES_USER_CUSTOM; + } + + e_dialog_option_menu_set (omenu, role, exchange_perm_map); +} + +static void +parent_window_destroyed (gpointer dialog, GObject *where_parent_window_was) +{ + gtk_dialog_response (dialog, GTK_RESPONSE_CANCEL); +} + +/** + * exchange_delegates_user_edit: + * @user: a delegate + * @parent_window: parent window for the editor dialog + * + * Brings up a dialog to edit @user's permissions as a delegate. + * An %edited signal will be emitted if anything changed. + * + * Return value: %TRUE for "OK", %FALSE for "Cancel". + **/ +gboolean +exchange_delegates_user_edit (ExchangeDelegatesUser *user, + GtkWidget *parent_window) +{ + GladeXML *xml; + GtkWidget *dialog, *table, *label, *menu, *check; + char *title; + int button, i; + E2kPermissionsRole role; + gboolean modified; + + g_return_val_if_fail (EXCHANGE_IS_DELEGATES_USER (user), FALSE); + g_return_val_if_fail (E2K_IS_SID (user->sid), FALSE); + + /* Grab the Glade widgets */ + xml = glade_xml_new ( + CONNECTOR_GLADEDIR "/exchange-delegates.glade", + "delegate_permissions", PACKAGE); + g_return_val_if_fail (xml, FALSE); + + title = g_strdup_printf (_("Permissions for %s"), user->display_name); + + dialog = glade_xml_get_widget (xml, "delegate_permissions"); + gtk_window_set_title (GTK_WINDOW (dialog), title); + e_dialog_set_transient_for (GTK_WINDOW (dialog), parent_window); + + table = glade_xml_get_widget (xml, "toplevel_table"); + gtk_widget_reparent (table, GTK_DIALOG (dialog)->vbox); + gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 6); + + label = glade_xml_get_widget (xml, "delegate_label"); + gtk_label_set_text (GTK_LABEL (label), title); + g_free (title); + + /* Set up the permissions */ + for (i = 0; i < EXCHANGE_DELEGATES_LAST; i++) { + menu = glade_xml_get_widget (xml, widget_names[i]); + set_perms (menu, user->role[i]); + } + check = glade_xml_get_widget (xml, "see_private_checkbox"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), + user->see_private); + + /* Run the dialog, while watching its parent. */ + g_object_weak_ref (G_OBJECT (parent_window), + parent_window_destroyed, dialog); + g_object_add_weak_pointer (G_OBJECT (parent_window), + (void **)&parent_window); + button = gtk_dialog_run (GTK_DIALOG (dialog)); + if (parent_window) { + g_object_remove_weak_pointer (G_OBJECT (parent_window), + (void **)&parent_window); + g_object_weak_unref (G_OBJECT (parent_window), + parent_window_destroyed, dialog); + } + + if (button != GTK_RESPONSE_OK) { + gtk_widget_destroy (dialog); + return FALSE; + } + + /* And update */ + modified = FALSE; + for (i = 0; i < EXCHANGE_DELEGATES_LAST; i++) { + menu = glade_xml_get_widget (xml, widget_names[i]); + role = e_dialog_option_menu_get (menu, exchange_perm_map); + + if (is_delegate_role (user->role[i]) && + user->role[i] != role) { + user->role[i] = role; + modified = TRUE; + } + } + check = glade_xml_get_widget (xml, "see_private_checkbox"); + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check)) != + user->see_private) { + user->see_private = !user->see_private; + modified = TRUE; + } + + g_object_unref (xml); + gtk_widget_destroy (dialog); + + if (modified) + g_signal_emit (user, signals[EDITED], 0); + + return TRUE; +} + +/** + * exchange_delegates_user_new: + * @display_name: the delegate's (UTF8) display name + * + * Return value: a new delegate user with default permissions (but + * with most of the internal data blank). + **/ +ExchangeDelegatesUser * +exchange_delegates_user_new (const char *display_name) +{ + ExchangeDelegatesUser *user; + int i; + + user = g_object_new (EXCHANGE_TYPE_DELEGATES_USER, NULL); + user->display_name = g_strdup (display_name); + + for (i = 0; i < EXCHANGE_DELEGATES_LAST; i++) { + if (i == EXCHANGE_DELEGATES_CALENDAR || + i == EXCHANGE_DELEGATES_TASKS) + user->role[i] = E2K_PERMISSIONS_ROLE_EDITOR; + else + user->role[i] = E2K_PERMISSIONS_ROLE_NONE; + } + + return user; +} + +/** + * exchange_delegates_user_new_from_gc: + * @gc: the global catalog object + * @email: email address of the new delegate + * @creator_entryid: The value of the PR_CREATOR_ENTRYID property + * on the LocalFreebusy file. + * + * Return value: a new delegate user with default permissions and + * internal data filled in from the global catalog. + **/ +ExchangeDelegatesUser * +exchange_delegates_user_new_from_gc (E2kGlobalCatalog *gc, + const char *email, + GByteArray *creator_entryid) +{ + E2kGlobalCatalogStatus status; + E2kGlobalCatalogEntry *entry; + ExchangeDelegatesUser *user; + guint8 *p; + + status = e2k_global_catalog_lookup ( + gc, NULL, /* FIXME: cancellable */ + E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL, email, + (E2K_GLOBAL_CATALOG_LOOKUP_SID | + E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN), + &entry); + if (status != E2K_GLOBAL_CATALOG_OK) + return NULL; + + user = exchange_delegates_user_new (e2k_sid_get_display_name (entry->sid)); + user->dn = g_strdup (entry->dn); + user->sid = entry->sid; + g_object_ref (user->sid); + + user->entryid = g_byte_array_new (); + p = creator_entryid->data + creator_entryid->len - 2; + while (p > creator_entryid->data && *p) + p--; + g_byte_array_append (user->entryid, creator_entryid->data, + p - creator_entryid->data + 1); + g_byte_array_append (user->entryid, entry->legacy_exchange_dn, + strlen (entry->legacy_exchange_dn)); + g_byte_array_append (user->entryid, "", 1); + + return user; +} diff --git a/plugins/exchange-account-setup/exchange-delegates-user.h b/plugins/exchange-account-setup/exchange-delegates-user.h new file mode 100644 index 0000000000..2b5b291e13 --- /dev/null +++ b/plugins/exchange-account-setup/exchange-delegates-user.h @@ -0,0 +1,70 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* Copyright (C) 2002-2004 Novell, Inc. */ + +#ifndef __EXCHANGE_DELEGATES_USER_H__ +#define __EXCHANGE_DELEGATES_USER_H__ + +#include <exchange-types.h> +#include <e2k-security-descriptor.h> +#include <gtk/gtkwidget.h> + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#define EXCHANGE_TYPE_DELEGATES_USER (exchange_delegates_user_get_type ()) +#define EXCHANGE_DELEGATES_USER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXCHANGE_TYPE_DELEGATES_USER, ExchangeDelegatesUser)) +#define EXCHANGE_DELEGATES_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EXCHANGE_TYPE_DELEGATES_USER, ExchangeDelegatesUserClass)) +#define EXCHANGE_IS_DELEGATES_USER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXCHANGE_TYPE_DELEGATES_USER)) +#define EXCHANGE_IS_DELEGATES_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EXCHANGE_TYPE_DELEGATES_USER)) + + +typedef struct _ExchangeDelegatesUser ExchangeDelegatesUser; +typedef struct _ExchangeDelegatesUserPrivate ExchangeDelegatesUserPrivate; +typedef struct _ExchangeDelegatesUserClass ExchangeDelegatesUserClass; + +enum { + EXCHANGE_DELEGATES_CALENDAR, + EXCHANGE_DELEGATES_TASKS, + EXCHANGE_DELEGATES_INBOX, + EXCHANGE_DELEGATES_CONTACTS, + EXCHANGE_DELEGATES_LAST +}; + +struct _ExchangeDelegatesUser { + GObject parent; + + char *display_name, *dn; + GByteArray *entryid; + + E2kSid *sid; + E2kPermissionsRole role[EXCHANGE_DELEGATES_LAST]; + gboolean see_private; +}; + +struct _ExchangeDelegatesUserClass { + GObjectClass parent_class; + + /* signals */ + void (*edited) (ExchangeDelegatesUser *, gpointer); +}; + + + +GType exchange_delegates_user_get_type (void); + +ExchangeDelegatesUser *exchange_delegates_user_new (const char *display_name); +ExchangeDelegatesUser *exchange_delegates_user_new_from_gc (E2kGlobalCatalog *gc, + const char *email, + GByteArray *creator_entryid); + +gboolean exchange_delegates_user_edit (ExchangeDelegatesUser *user, + GtkWidget *parent_window); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EXCHANGE_DELEGATES_USER_H__ */ diff --git a/plugins/exchange-account-setup/exchange-delegates.c b/plugins/exchange-account-setup/exchange-delegates.c new file mode 100644 index 0000000000..c868eb64b9 --- /dev/null +++ b/plugins/exchange-account-setup/exchange-delegates.c @@ -0,0 +1,972 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* Copyright (C) 2002-2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + */ + +/* ExchangeDelegates: Exchange delegate handling. + * + * FIXME: make this instant-apply + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "exchange-delegates.h" +#include "exchange-delegates-user.h" +#include "exchange-account.h" +#include "e2k-propnames.h" +#include "e2k-security-descriptor.h" +#include "e2k-sid.h" +#include "e2k-uri.h" +#include "e2k-user-dialog.h" +#include "e2k-utils.h" + +#include <e-util/e-dialog-utils.h> +#include <glade/glade-xml.h> +#include <gtk/gtkbox.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtktreeview.h> + +typedef struct { + const char *uri; + E2kSecurityDescriptor *sd; + gboolean changed; +} ExchangeDelegatesFolder; + +typedef struct { + ExchangeAccount *account; + char *self_dn; + + GladeXML *xml; + GtkWidget *dialog, *parent; + + GtkListStore *model; + GtkWidget *table; + + GByteArray *creator_entryid; + GPtrArray *users, *added_users, *removed_users; + gboolean loaded_folders; + ExchangeDelegatesFolder folder[EXCHANGE_DELEGATES_LAST]; + ExchangeDelegatesFolder freebusy_folder; +} ExchangeDelegates; + +extern const char *exchange_delegates_user_folder_names[]; + +const char *exchange_localfreebusy_path = "NON_IPM_SUBTREE/Freebusy%20Data/LocalFreebusy.EML"; + +static void set_perms_for_user (ExchangeDelegatesUser *user, gpointer user_data); + +static void +set_sd_for_href (ExchangeDelegates *delegates, + const char *href, + E2kSecurityDescriptor *sd) +{ + int i; + + for (i = 0; i < EXCHANGE_DELEGATES_LAST; i++) { + if (!delegates->folder[i].uri) + continue; + + if (!strcmp (href, delegates->folder[i].uri)) { + delegates->folder[i].sd = sd; + return; + } + } + + /* else, it's the freebusy folder */ + delegates->freebusy_folder.uri = g_strdup (href); + delegates->freebusy_folder.sd = sd; +} + +/* Given an array of ExchangeDelegatesUser containing display names + * and entryids, and an array of E2kSecurityDescriptors containing + * SIDs (which contain display names), add SIDs to the delegates. In + * the easy case, we can just match the SIDs up with their + * corresponding user by display name. However, there are two things + * that can go wrong: + * + * 1. Some users may have been removed from the SDs + * 2. Two users may have the same display name + * + * In both cases, we fall back to using the GC. + */ +static gboolean +fill_in_sids (ExchangeDelegates *delegates) +{ + int u, u2, sd, needed_sids; + ExchangeDelegatesUser *user, *user2; + GList *sids, *s; + E2kSid *sid; + E2kGlobalCatalog *gc; + E2kGlobalCatalogStatus status; + E2kGlobalCatalogEntry *entry; + gboolean ok = TRUE; + + needed_sids = 0; + + /* Mark users with duplicate names and count the number of + * non-duplicate names. + */ + for (u = 0; u < delegates->users->len; u++) { + user = delegates->users->pdata[u]; + if (user->sid == (E2kSid *)-1) + continue; + for (u2 = u + 1; u2 < delegates->users->len; u2++) { + user2 = delegates->users->pdata[u2]; + if (!strcmp (user->display_name, user2->display_name)) + user->sid = user2->sid = (E2kSid *)-1; + } + if (!user->sid) + needed_sids++; + } + + /* Scan security descriptors trying to match SIDs until we're + * not expecting to find any more. + */ + for (sd = 0; sd < EXCHANGE_DELEGATES_LAST && needed_sids; sd++) { + sids = e2k_security_descriptor_get_sids (delegates->folder[sd].sd); + for (s = sids; s && needed_sids; s = s->next) { + sid = s->data; + for (u = 0; u < delegates->users->len; u++) { + user = delegates->users->pdata[u]; + if (user->sid) + continue; + if (!strcmp (user->display_name, + e2k_sid_get_display_name (sid))) { + user->sid = sid; + g_object_ref (sid); + needed_sids--; + } + } + } + g_list_free (sids); + } + + /* Now for each user whose SID hasn't yet been found, look it up. */ + gc = exchange_account_get_global_catalog (delegates->account); + for (u = 0; u < delegates->users->len; u++) { + user = delegates->users->pdata[u]; + if (user->sid && user->sid != (E2kSid *)-1) + continue; + + status = e2k_global_catalog_lookup ( + gc, NULL, /* FIXME: cancellable */ + E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN, + e2k_entryid_to_dn (user->entryid), + E2K_GLOBAL_CATALOG_LOOKUP_SID, &entry); + if (status != E2K_GLOBAL_CATALOG_OK) { + user->sid = NULL; + ok = FALSE; + continue; + } + user->sid = entry->sid; + g_object_ref (user->sid); + e2k_global_catalog_entry_free (gc, entry); + } + + return ok; +} + +static const char *sd_props[] = { + E2K_PR_EXCHANGE_SD_BINARY, + E2K_PR_EXCHANGE_SD_XML +}; +static const int n_sd_props = sizeof (sd_props) / sizeof (sd_props[0]); + +/* Read the folder security descriptors and match them up with the + * list of delegates. + */ +static gboolean +get_folder_security (ExchangeDelegates *delegates) +{ + GPtrArray *hrefs; + E2kContext *ctx; + E2kHTTPStatus status; + E2kResultIter *iter; + E2kResult *result; + xmlNode *xml_form; + GByteArray *binary_form; + ExchangeDelegatesUser *user; + guint32 perms; + int i, u; + + /* If we've been here before, just return the success or + * failure result from last time. + */ + if (delegates->freebusy_folder.uri) + return delegates->loaded_folders; + + if (!exchange_account_get_global_catalog (delegates->account)) { + e_notice (delegates->table, GTK_MESSAGE_ERROR, + _("No Global Catalog server configured for this account.\nUnable to edit delegates.")); + return FALSE; + } + + ctx = exchange_account_get_context (delegates->account); + + hrefs = g_ptr_array_new (); + for (i = 0; i < EXCHANGE_DELEGATES_LAST; i++) { + delegates->folder[i].uri = exchange_account_get_standard_uri ( + delegates->account, exchange_delegates_user_folder_names[i]); + if (delegates->folder[i].uri) { + g_ptr_array_add (hrefs, (char *)e2k_uri_relative ( + delegates->account->home_uri, + delegates->folder[i].uri)); + } + } + g_ptr_array_add (hrefs, (char *)exchange_localfreebusy_path); + + iter = e2k_context_bpropfind_start ( + ctx, NULL, delegates->account->home_uri, + (const char **)hrefs->pdata, hrefs->len, + sd_props, n_sd_props); + g_ptr_array_free (hrefs, TRUE); + + while ((result = e2k_result_iter_next (iter))) { + xml_form = e2k_properties_get_prop (result->props, + E2K_PR_EXCHANGE_SD_XML); + binary_form = e2k_properties_get_prop (result->props, + E2K_PR_EXCHANGE_SD_BINARY); + + if (xml_form && binary_form) { + set_sd_for_href (delegates, result->href, + e2k_security_descriptor_new (xml_form, binary_form)); + } + } + status = e2k_result_iter_free (iter); + + if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) { + e_notice (delegates->table, GTK_MESSAGE_ERROR, + _("Could not read folder permissions.\nUnable to edit delegates.")); + return FALSE; + } + + if (!fill_in_sids (delegates)) { + delegates->loaded_folders = FALSE; + e_notice (delegates->table, GTK_MESSAGE_ERROR, + _("Could not determine folder permissions for delegates.\nUnable to edit delegates.")); + return FALSE; + } + + /* Fill in delegate structures from the security descriptors */ + for (i = 0; i < EXCHANGE_DELEGATES_LAST; i++) { + for (u = 0; u < delegates->users->len; u++) { + user = delegates->users->pdata[u]; + perms = e2k_security_descriptor_get_permissions ( + delegates->folder[i].sd, user->sid); + user->role[i] = e2k_permissions_role_find (perms); + } + } + + delegates->loaded_folders = TRUE; + return TRUE; +} + + +static const char *delegation_props[] = { + PR_DELEGATES_DISPLAY_NAMES, + PR_DELEGATES_ENTRYIDS, + PR_DELEGATES_SEE_PRIVATE, + PR_CREATOR_ENTRYID +}; +static const int n_delegation_props = sizeof (delegation_props) / sizeof (delegation_props[0]); + +/* Fetch the list of delegates from the freebusy message. */ +static gboolean +get_user_list (ExchangeDelegates *delegates) +{ + E2kContext *ctx; + E2kResultIter *iter; + E2kResult *result; + GPtrArray *display_names, *entryids, *privflags; + GByteArray *entryid; + ExchangeDelegatesUser *user; + int i; + + ctx = exchange_account_get_context (delegates->account); + iter = e2k_context_bpropfind_start (ctx, NULL, + delegates->account->home_uri, + &exchange_localfreebusy_path, 1, + delegation_props, n_delegation_props); + result = e2k_result_iter_next (iter); + if (!result || !E2K_HTTP_STATUS_IS_SUCCESSFUL (result->status)) { + e2k_result_iter_free (iter); + return FALSE; + } + + delegates->users = g_ptr_array_new (); + delegates->added_users = g_ptr_array_new (); + delegates->removed_users = g_ptr_array_new (); + + display_names = e2k_properties_get_prop (result->props, PR_DELEGATES_DISPLAY_NAMES); + entryids = e2k_properties_get_prop (result->props, PR_DELEGATES_ENTRYIDS); + privflags = e2k_properties_get_prop (result->props, PR_DELEGATES_SEE_PRIVATE); + + entryid = e2k_properties_get_prop (result->props, PR_CREATOR_ENTRYID); + delegates->creator_entryid = g_byte_array_new (); + g_byte_array_append (delegates->creator_entryid, entryid->data, entryid->len); + + if (!display_names || !entryids || !privflags) { + e2k_result_iter_free (iter); + return TRUE; + } + + for (i = 0; i < display_names->len && i < entryids->len && i < privflags->len; i++) { + user = exchange_delegates_user_new (display_names->pdata[i]); + user->see_private = privflags->pdata[i] && atoi (privflags->pdata[i]); + entryid = entryids->pdata[i]; + user->entryid = g_byte_array_new (); + g_byte_array_append (user->entryid, entryid->data, entryid->len); + + g_signal_connect (user, "edited", G_CALLBACK (set_perms_for_user), delegates); + + g_ptr_array_add (delegates->users, user); + } + + e2k_result_iter_free (iter); + return TRUE; +} + +/* Add or remove a delegate. Everyone must be in one of three states: + * 1. only in users (because they started and ended there) + * 2. in users and added_users (because they weren't in + * users to begin with, but got added) + * 3. only in removed_users (because they were in users to + * begin with and got removed). + * If you're added and then removed, or removed and then added, you have + * to end up in state 1. That's what this is for. + */ +static void +add_remove_user (ExchangeDelegatesUser *user, + GPtrArray *to_array, GPtrArray *from_array) +{ + ExchangeDelegatesUser *match; + int i; + + for (i = 0; i < from_array->len; i++) { + match = from_array->pdata[i]; + if (e2k_sid_binary_sid_equal (e2k_sid_get_binary_sid (match->sid), + e2k_sid_get_binary_sid (user->sid))) { + g_ptr_array_remove_index_fast (from_array, i); + g_object_unref (match); + return; + } + } + + g_ptr_array_add (to_array, user); + g_object_ref (user); +} + +static void +set_perms_for_user (ExchangeDelegatesUser *user, gpointer user_data) +{ + ExchangeDelegates *delegates = user_data; + int i, role; + guint32 perms; + + for (i = 0; i < EXCHANGE_DELEGATES_LAST; i++) { + perms = e2k_permissions_role_get_perms (user->role[i]); + e2k_security_descriptor_set_permissions (delegates->folder[i].sd, + user->sid, perms); + } + role = user->role[EXCHANGE_DELEGATES_CALENDAR]; + if (role == E2K_PERMISSIONS_ROLE_AUTHOR) + role = E2K_PERMISSIONS_ROLE_EDITOR; + perms = e2k_permissions_role_get_perms (role); + e2k_security_descriptor_set_permissions (delegates->freebusy_folder.sd, + user->sid, perms); +} + +static void +add_button_clicked_cb (GtkWidget *widget, gpointer data) +{ + ExchangeDelegates *delegates = data; + E2kGlobalCatalog *gc; + GtkWidget *dialog, *parent_window; + const char *delegate_exchange_dn; + char *email; + ExchangeDelegatesUser *user, *match; + int response, u; + GtkTreeIter iter; + + if (!get_folder_security (delegates)) + return; + + gc = exchange_account_get_global_catalog (delegates->account); + + parent_window = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW); + dialog = e2k_user_dialog_new (parent_window, + _("Delegate To:"), _("Delegate To")); + response = gtk_dialog_run (GTK_DIALOG (dialog)); + if (response != GTK_RESPONSE_OK) { + gtk_widget_destroy (dialog); + return; + } + email = e2k_user_dialog_get_user (E2K_USER_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + if (email == NULL) + return; + + user = exchange_delegates_user_new_from_gc (gc, email, + delegates->creator_entryid); + if (!user) { + e_notice (parent_window, GTK_MESSAGE_ERROR, + _("Could not make %s a delegate"), email); + g_free (email); + return; + } + g_free (email); + + delegate_exchange_dn = e2k_entryid_to_dn (user->entryid); + if (delegate_exchange_dn && !g_ascii_strcasecmp (delegate_exchange_dn, delegates->account->legacy_exchange_dn)) { + g_object_unref (user); + e_notice (parent_window, GTK_MESSAGE_ERROR, + _("You cannot make yourself your own delegate")); + return; + } + + for (u = 0; u < delegates->users->len; u++) { + match = delegates->users->pdata[u]; + if (e2k_sid_binary_sid_equal (e2k_sid_get_binary_sid (user->sid), + e2k_sid_get_binary_sid (match->sid))) { + e_notice (parent_window, GTK_MESSAGE_INFO, + _("%s is already a delegate"), + user->display_name); + g_object_unref (user); + exchange_delegates_user_edit (match, parent_window); + return; + } + } + + if (!exchange_delegates_user_edit (user, parent_window)) { + g_object_unref (user); + return; + } + set_perms_for_user (user, delegates); + g_signal_connect (user, "edited", + G_CALLBACK (set_perms_for_user), delegates); + + add_remove_user (user, delegates->added_users, delegates->removed_users); + g_ptr_array_add (delegates->users, user); + + /* Add the user to the table */ + gtk_list_store_append (delegates->model, &iter); + gtk_list_store_set (delegates->model, &iter, + 0, user->display_name, + -1); +} + +static int +get_selected_row (GtkWidget *tree_view, GtkTreeIter *iter) +{ + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreePath *path; + int *indices, row; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + if (!gtk_tree_selection_get_selected (selection, &model, iter)) + return -1; + + path = gtk_tree_model_get_path (model, iter); + indices = gtk_tree_path_get_indices (path); + row = indices[0]; + gtk_tree_path_free (path); + + return row; +} + +static void +edit_button_clicked_cb (GtkWidget *widget, gpointer data) +{ + ExchangeDelegates *delegates = data; + GtkWidget *parent_window; + GtkTreeIter iter; + int row; + + if (!get_folder_security (delegates)) + return; + + row = get_selected_row (delegates->table, &iter); + g_return_if_fail (row >= 0 && row < delegates->users->len); + + parent_window = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW); + exchange_delegates_user_edit (delegates->users->pdata[row], + parent_window); +} + +static gboolean +table_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + ExchangeDelegates *delegates = data; + GtkWidget *parent_window; + GtkTreeIter iter; + int row; + + if (event->type != GDK_2BUTTON_PRESS) + return FALSE; + + row = get_selected_row (delegates->table, &iter); + if (row < 0 || row >= delegates->users->len) + return FALSE; + + if (!get_folder_security (delegates)) + return FALSE; + + parent_window = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW); + exchange_delegates_user_edit (delegates->users->pdata[row], + parent_window); + return TRUE; +} + +static void +remove_button_clicked_cb (GtkWidget *widget, gpointer data) +{ + ExchangeDelegates *delegates = data; + ExchangeDelegatesUser *user; + GtkWidget *dialog; + int row, btn, i; + GtkTreeIter iter; + + if (!get_folder_security (delegates)) + return; + + row = get_selected_row (delegates->table, &iter); + g_return_if_fail (row >= 0 && row < delegates->users->len); + + user = delegates->users->pdata[row]; + + dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + _("Remove the delegate %s?"), + user->display_name); + e_dialog_set_transient_for (GTK_WINDOW (dialog), widget); + + btn = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + if (btn != GTK_RESPONSE_YES) + return; + + add_remove_user (user, delegates->removed_users, delegates->added_users); + + for (i = 0; i < EXCHANGE_DELEGATES_LAST; i++) { + e2k_security_descriptor_remove_sid (delegates->folder[i].sd, + user->sid); + } + e2k_security_descriptor_remove_sid (delegates->freebusy_folder.sd, + user->sid); + + /* Remove the user from the table */ + gtk_list_store_remove (delegates->model, &iter); + g_ptr_array_remove_index (delegates->users, row); + g_object_unref (user); +} + + +static gboolean +proppatch_sd (E2kContext *ctx, ExchangeDelegatesFolder *folder) +{ + GByteArray *binsd; + E2kProperties *props; + const char *href = ""; + E2kResultIter *iter; + E2kResult *result; + E2kHTTPStatus status; + + binsd = e2k_security_descriptor_to_binary (folder->sd); + if (!binsd) + return FALSE; + + props = e2k_properties_new (); + e2k_properties_set_binary (props, E2K_PR_EXCHANGE_SD_BINARY, binsd); + + iter = e2k_context_bproppatch_start (ctx, NULL, folder->uri, + &href, 1, props, FALSE); + e2k_properties_free (props); + + result = e2k_result_iter_next (iter); + if (result) { + status = result->status; + e2k_result_iter_free (iter); + } else + status = e2k_result_iter_free (iter); + + return E2K_HTTP_STATUS_IS_SUCCESSFUL (status); +} + +static gboolean +get_user_dn (E2kGlobalCatalog *gc, ExchangeDelegatesUser *user) +{ + E2kGlobalCatalogEntry *entry; + E2kGlobalCatalogStatus status; + const char *exchange_dn; + + exchange_dn = e2k_entryid_to_dn (user->entryid); + status = e2k_global_catalog_lookup ( + gc, NULL, /* FIXME: cancellable */ + E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN, + exchange_dn, 0, &entry); + if (status != E2K_GLOBAL_CATALOG_OK) + return FALSE; + + user->dn = g_strdup (entry->dn); + e2k_global_catalog_entry_free (gc, entry); + return TRUE; +} + +static void +delegates_apply (ExchangeDelegates *delegates) +{ + ExchangeDelegatesUser *user; + E2kGlobalCatalog *gc; + E2kContext *ctx; + GPtrArray *display_names, *entryids, *privflags; + GByteArray *entryid_dup; + char *error = NULL; + E2kProperties *props; + int i, status; + + if (!delegates->loaded_folders) + return; + + /* We can't do this atomically/transactionally, so we need to + * make sure that if we fail at any step, things are still in + * a semi-consistent state. So we do: + * + * 1. Remove old delegates from AD + * 2. Update LocalFreebusy.EML (the canonical list of delegates) + * 3. Add new delegates to AD + * 4. Update security descriptors + * + * If step 1 fails, nothing is changed. + * + * If step 2 fails, delegates who should have been removed + * will have been removed from AD but nothing else, so they + * will still show up as being delegates and the user can try + * to remove them again later. + * + * If step 3 fails, delegates who should have been added will + * not be in AD, but will be listed as delegates, so the user + * can remove them and try adding them again later. + * + * If step 4 fails, the user can still correct the folder + * permissions by hand. + */ + + gc = exchange_account_get_global_catalog (delegates->account); + if (!gc) { + error = g_strdup (_("Could not access Active Directory")); + goto done; + } + + if ((delegates->removed_users || delegates->added_users) && !delegates->self_dn) { + E2kGlobalCatalog *gc; + E2kGlobalCatalogStatus status; + E2kGlobalCatalogEntry *entry; + + gc = exchange_account_get_global_catalog (delegates->account); + status = e2k_global_catalog_lookup ( + gc, NULL, /* FIXME: cancellable */ + E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN, + delegates->account->legacy_exchange_dn, 0, &entry); + if (status != E2K_GLOBAL_CATALOG_OK) { + error = g_strdup (_("Could not find self in Active Directory")); + goto done; + } + + delegates->self_dn = g_strdup (entry->dn); + e2k_global_catalog_entry_free (gc, entry); + } + + /* 1. Remove old delegates from AD */ + while (delegates->removed_users && delegates->removed_users->len) { + user = delegates->removed_users->pdata[0]; + if (!user->dn && !get_user_dn (gc, user)) { + error = g_strdup_printf ( + _("Could not find delegate %s in Active Directory"), + user->display_name); + goto done; + } + + /* FIXME: cancellable */ + status = e2k_global_catalog_remove_delegate (gc, NULL, + delegates->self_dn, + user->dn); + if (status != E2K_GLOBAL_CATALOG_OK && + status != E2K_GLOBAL_CATALOG_NO_DATA) { + error = g_strdup_printf ( + _("Could not remove delegate %s"), + user->display_name); + goto done; + } + + g_object_unref (user); + g_ptr_array_remove_index_fast (delegates->removed_users, 0); + } + + /* 2. Update LocalFreebusy.EML */ + ctx = exchange_account_get_context (delegates->account); + + if (delegates->users->len) { + display_names = g_ptr_array_new (); + entryids = g_ptr_array_new (); + privflags = g_ptr_array_new (); + + for (i = 0; i < delegates->users->len; i++) { + user = delegates->users->pdata[i]; + g_ptr_array_add (display_names, g_strdup (user->display_name)); + entryid_dup = g_byte_array_new (); + g_byte_array_append (entryid_dup, user->entryid->data, + user->entryid->len); + g_ptr_array_add (entryids, entryid_dup); + g_ptr_array_add (privflags, g_strdup_printf ("%d", user->see_private)); + } + + props = e2k_properties_new (); + e2k_properties_set_string_array ( + props, PR_DELEGATES_DISPLAY_NAMES, display_names); + e2k_properties_set_binary_array ( + props, PR_DELEGATES_ENTRYIDS, entryids); + e2k_properties_set_int_array ( + props, PR_DELEGATES_SEE_PRIVATE, privflags); + } else if (delegates->removed_users) { + props = e2k_properties_new (); + e2k_properties_remove (props, PR_DELEGATES_DISPLAY_NAMES); + e2k_properties_remove (props, PR_DELEGATES_ENTRYIDS); + e2k_properties_remove (props, PR_DELEGATES_SEE_PRIVATE); + } else + props = NULL; + + if (props) { + E2kResultIter *iter; + E2kResult *result; + + iter = e2k_context_bproppatch_start ( + ctx, NULL, delegates->account->home_uri, + &exchange_localfreebusy_path, 1, + props, FALSE); + e2k_properties_free (props); + + result = e2k_result_iter_next (iter); + if (result) { + status = result->status; + e2k_result_iter_free (iter); + } else + status = e2k_result_iter_free (iter); + + if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) { + error = g_strdup (_("Could not update list of delegates.")); + goto done; + } + } + + /* 3. Add new delegates to AD */ + while (delegates->added_users && delegates->added_users->len) { + user = delegates->added_users->pdata[0]; + /* An added user must have come from the GC so + * we know user->dn is set. + */ + /* FIXME: cancellable */ + status = e2k_global_catalog_add_delegate (gc, NULL, + delegates->self_dn, + user->dn); + if (status != E2K_GLOBAL_CATALOG_OK && + status != E2K_GLOBAL_CATALOG_EXISTS) { + error = g_strdup_printf ( + _("Could not add delegate %s"), + user->display_name); + goto done; + } + g_ptr_array_remove_index_fast (delegates->added_users, 0); + g_object_unref (user); + } + + /* 4. Update security descriptors */ + for (i = 0; i < EXCHANGE_DELEGATES_LAST; i++) + proppatch_sd (ctx, &delegates->folder[i]); + proppatch_sd (ctx, &delegates->freebusy_folder); + + done: + if (error) { + e_notice (delegates->table, GTK_MESSAGE_ERROR, + _("Failed to update delegates:\n%s"), error); + g_free (error); + } +} + +static void parent_destroyed (gpointer user_data, GObject *ex_parent); + +static void +delegates_destroy (ExchangeDelegates *delegates) +{ + int i; + + g_object_unref (delegates->account); + + if (delegates->parent) { + g_object_weak_unref (G_OBJECT (delegates->parent), + parent_destroyed, delegates); + } + if (delegates->dialog) + gtk_widget_destroy (delegates->dialog); + + if (delegates->model) + g_object_unref (delegates->model); + + if (delegates->self_dn) + g_free (delegates->self_dn); + if (delegates->creator_entryid) + g_byte_array_free (delegates->creator_entryid, TRUE); + + if (delegates->users) { + for (i = 0; i < delegates->users->len; i++) + g_object_unref (delegates->users->pdata[i]); + g_ptr_array_free (delegates->users, TRUE); + } + if (delegates->added_users) { + for (i = 0; i < delegates->added_users->len; i++) + g_object_unref (delegates->added_users->pdata[i]); + g_ptr_array_free (delegates->added_users, TRUE); + } + if (delegates->removed_users) { + for (i = 0; i < delegates->removed_users->len; i++) + g_object_unref (delegates->removed_users->pdata[i]); + g_ptr_array_free (delegates->removed_users, TRUE); + } + + for (i = 0; i < EXCHANGE_DELEGATES_LAST; i++) { + if (delegates->folder[i].sd) + g_object_unref (delegates->folder[i].sd); + } + if (delegates->freebusy_folder.sd) + g_object_unref (delegates->freebusy_folder.sd); + if (delegates->freebusy_folder.uri) + g_free ((char *)delegates->freebusy_folder.uri); + + if (delegates->xml) + g_object_unref (delegates->xml); + + g_free (delegates); +} + + +static void +dialog_response (GtkDialog *dialog, int response, gpointer user_data) +{ + ExchangeDelegates *delegates = user_data; + + if (response == GTK_RESPONSE_OK) + delegates_apply (delegates); + delegates_destroy (delegates); +} + +static void +parent_destroyed (gpointer user_data, GObject *ex_parent) +{ + ExchangeDelegates *delegates = user_data; + + gtk_widget_destroy (delegates->dialog); + delegates_destroy (delegates); +} + +void +exchange_delegates (ExchangeAccount *account, GtkWidget *parent) +{ + ExchangeDelegates *delegates; + GtkWidget *button; + ExchangeDelegatesUser *user; + GtkTreeViewColumn *column; + GtkTreeIter iter; + int i; + + g_return_if_fail (GTK_IS_WIDGET (parent)); + g_return_if_fail (EXCHANGE_IS_ACCOUNT (account)); + + delegates = g_new0 (ExchangeDelegates, 1); + delegates->account = g_object_ref (account); + + delegates->xml = glade_xml_new (CONNECTOR_GLADEDIR "/exchange-delegates.glade", NULL, NULL); + g_return_if_fail (delegates->xml != NULL); + + delegates->dialog = glade_xml_get_widget (delegates->xml, "delegates"); + g_return_if_fail (delegates->dialog != NULL); + + g_signal_connect (delegates->dialog, "response", + G_CALLBACK (dialog_response), delegates); + + e_dialog_set_transient_for (GTK_WINDOW (delegates->dialog), parent); + delegates->parent = parent; + g_object_weak_ref (G_OBJECT (parent), parent_destroyed, delegates); + + /* Set up the buttons */ + button = glade_xml_get_widget (delegates->xml, "add_button"); + g_signal_connect (button, "clicked", + G_CALLBACK (add_button_clicked_cb), delegates); + button = glade_xml_get_widget (delegates->xml, "edit_button"); + g_signal_connect (button, "clicked", + G_CALLBACK (edit_button_clicked_cb), delegates); + button = glade_xml_get_widget (delegates->xml, "remove_button"); + g_signal_connect (button, "clicked", + G_CALLBACK (remove_button_clicked_cb), delegates); + + /* Set up the table */ + delegates->model = gtk_list_store_new (1, G_TYPE_STRING); + delegates->table = glade_xml_get_widget (delegates->xml, "delegates_table"); + column = gtk_tree_view_column_new_with_attributes ( + _("Name"), gtk_cell_renderer_text_new (), "text", 0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (delegates->table), + column); + gtk_tree_view_set_model (GTK_TREE_VIEW (delegates->table), + GTK_TREE_MODEL (delegates->model)); + + /* Get list of delegate users */ + if (get_user_list (delegates)) { + for (i = 0; i < delegates->users->len; i++) { + user = delegates->users->pdata[i]; + + gtk_list_store_append (delegates->model, &iter); + gtk_list_store_set (delegates->model, &iter, + 0, user->display_name, + -1); + } + g_signal_connect (delegates->table, + "button_press_event", + G_CALLBACK (table_click_cb), delegates); + } else { + button = glade_xml_get_widget (delegates->xml, "add_button"); + gtk_widget_set_sensitive (button, FALSE); + button = glade_xml_get_widget (delegates->xml, "edit_button"); + gtk_widget_set_sensitive (button, FALSE); + button = glade_xml_get_widget (delegates->xml, "remove_button"); + gtk_widget_set_sensitive (button, FALSE); + + gtk_list_store_append (delegates->model, &iter); + gtk_list_store_set (delegates->model, &iter, + 0, _("Error reading delegates list."), + -1); + } + + gtk_widget_show (delegates->dialog); +} diff --git a/plugins/exchange-account-setup/exchange-delegates.glade b/plugins/exchange-account-setup/exchange-delegates.glade new file mode 100644 index 0000000000..4b2e0e7695 --- /dev/null +++ b/plugins/exchange-account-setup/exchange-delegates.glade @@ -0,0 +1,628 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> +<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> + +<glade-interface> + +<widget class="GtkDialog" id="delegates"> + <property name="visible">True</property> + <property name="title" translatable="yes">Delegates</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_NONE</property> + <property name="modal">False</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">False</property> + <property name="decorated">True</property> + <property name="skip_taskbar_hint">False</property> + <property name="skip_pager_hint">False</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> + <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> + <property name="has_separator">False</property> + + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + + <child> + <widget class="GtkButton" id="cancelbutton1"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="response_id">-6</property> + </widget> + </child> + + <child> + <widget class="GtkButton" id="okbutton1"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-ok</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="response_id">-5</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + + <child> + <widget class="GtkVBox" id="vbox2"> + <property name="border_width">6</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes">These users will be able to send mail on your behalf +and access your folders with the permissions you give them.</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="delegate_hbox"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkTreeView" id="delegates_table"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">True</property> + <property name="rules_hint">False</property> + <property name="reorderable">False</property> + <property name="enable_search">False</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkVButtonBox" id="vbuttonbox1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_START</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkButton" id="add_button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-add</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + </widget> + </child> + + <child> + <widget class="GtkButton" id="edit_button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Edit</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + </widget> + </child> + + <child> + <widget class="GtkButton" id="remove_button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-remove</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + </child> +</widget> + +<widget class="GtkDialog" id="delegate_permissions"> + <property name="title" translatable="yes">Delegate Permissions</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_NONE</property> + <property name="modal">False</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">False</property> + <property name="decorated">True</property> + <property name="skip_taskbar_hint">False</property> + <property name="skip_pager_hint">False</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> + <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> + <property name="has_separator">False</property> + + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox2"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area2"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + + <child> + <widget class="GtkButton" id="cancelbutton2"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="response_id">-6</property> + </widget> + </child> + + <child> + <widget class="GtkButton" id="okbutton2"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-ok</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="response_id">-5</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + + <child> + <widget class="GtkVBox" id="vbox3"> + <property name="border_width">12</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + + <child> + <widget class="GtkLabel" id="delegate_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Permissions for</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkTable" id="folders_table"> + <property name="visible">True</property> + <property name="n_rows">4</property> + <property name="n_columns">2</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + + <child> + <widget class="GtkLabel" id="calendar_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">C_alendar:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">calendar_perms</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="task_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Tasks:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">task_perms</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="inbox_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Inbox:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">inbox_perms</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="contact_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Co_ntacts:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">contact_perms</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkOptionMenu" id="calendar_perms"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="history">0</property> + + <child internal-child="menu"> + <widget class="GtkMenu" id="convertwidget1"> + <property name="visible">True</property> + + <child> + <widget class="GtkMenuItem" id="convertwidget2"> + <property name="visible">True</property> + <property name="label" translatable="yes">None</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget3"> + <property name="visible">True</property> + <property name="label" translatable="yes">Reviewer (read-only)</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget4"> + <property name="visible">True</property> + <property name="label" translatable="yes">Author (read, create)</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget5"> + <property name="visible">True</property> + <property name="label" translatable="yes">Editor (read, create, edit)</property> + <property name="use_underline">True</property> + </widget> + </child> + </widget> + </child> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkOptionMenu" id="task_perms"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="history">0</property> + + <child internal-child="menu"> + <widget class="GtkMenu" id="convertwidget6"> + <property name="visible">True</property> + + <child> + <widget class="GtkMenuItem" id="convertwidget7"> + <property name="visible">True</property> + <property name="label" translatable="yes">None</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget8"> + <property name="visible">True</property> + <property name="label" translatable="yes">Reviewer (read-only)</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget9"> + <property name="visible">True</property> + <property name="label" translatable="yes">Author (read, create)</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget10"> + <property name="visible">True</property> + <property name="label" translatable="yes">Editor (read, create, edit)</property> + <property name="use_underline">True</property> + </widget> + </child> + </widget> + </child> + </widget> + <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> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkOptionMenu" id="inbox_perms"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="history">0</property> + + <child internal-child="menu"> + <widget class="GtkMenu" id="convertwidget11"> + <property name="visible">True</property> + + <child> + <widget class="GtkMenuItem" id="convertwidget12"> + <property name="visible">True</property> + <property name="label" translatable="yes">None</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget13"> + <property name="visible">True</property> + <property name="label" translatable="yes">Reviewer (read-only)</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget14"> + <property name="visible">True</property> + <property name="label" translatable="yes">Author (read, create)</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget15"> + <property name="visible">True</property> + <property name="label" translatable="yes">Editor (read, create, edit)</property> + <property name="use_underline">True</property> + </widget> + </child> + </widget> + </child> + </widget> + <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> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkOptionMenu" id="contact_perms"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="history">0</property> + + <child internal-child="menu"> + <widget class="GtkMenu" id="convertwidget16"> + <property name="visible">True</property> + + <child> + <widget class="GtkMenuItem" id="convertwidget17"> + <property name="visible">True</property> + <property name="label" translatable="yes">None</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget18"> + <property name="visible">True</property> + <property name="label" translatable="yes">Reviewer (read-only)</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget19"> + <property name="visible">True</property> + <property name="label" translatable="yes">Author (read, create)</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="convertwidget20"> + <property name="visible">True</property> + <property name="label" translatable="yes">Editor (read, create, edit)</property> + <property name="use_underline">True</property> + </widget> + </child> + </widget> + </child> + </widget> + <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> + <property name="y_options"></property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkCheckButton" id="see_private_checkbox"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Delegate can see private items</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + </child> +</widget> + +</glade-interface> diff --git a/plugins/exchange-account-setup/exchange-delegates.h b/plugins/exchange-account-setup/exchange-delegates.h new file mode 100644 index 0000000000..be8f4f89e3 --- /dev/null +++ b/plugins/exchange-account-setup/exchange-delegates.h @@ -0,0 +1,21 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* Copyright (C) 2002-2004 Novell, Inc. */ + +#ifndef __EXCHANGE_DELEGATES_H__ +#define __EXCHANGE_DELEGATES_H__ + +#include <exchange-types.h> +#include <gtk/gtkwidget.h> + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +void exchange_delegates (ExchangeAccount *account, GtkWidget *parent); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EXCHANGE_DELEGATES_CONTROL_H__ */ diff --git a/plugins/exchange-account-setup/exchange-folder-size.c b/plugins/exchange-account-setup/exchange-folder-size.c new file mode 100644 index 0000000000..694155f54d --- /dev/null +++ b/plugins/exchange-account-setup/exchange-folder-size.c @@ -0,0 +1,343 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* Copyright (C) 2002-2004 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + */ + +/* ExchangeFolderSize: Display the folder tree with the folder sizes */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <e-util/e-dialog-utils.h> +#include <glade/glade-xml.h> +#include <gtk/gtkbox.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtktreeview.h> + +#include "exchange-hierarchy-webdav.h" +#include "e-folder-exchange.h" +#include "exchange-folder-size.h" + +#define PARENT_TYPE G_TYPE_OBJECT +static GObjectClass *parent_class = NULL; + +typedef struct { + char *folder_name; + gdouble folder_size; +} folder_info; + + +struct _ExchangeFolderSizePrivate { + + GHashTable *table; + GtkListStore *model; + GHashTable *row_refs; +}; + +enum { + COLUMN_NAME, + COLUMN_SIZE, + NUM_COLUMNS +}; + +static gboolean +free_fsize_table (gpointer key, gpointer value, gpointer data) +{ + folder_info *f_info = (folder_info *) value; + + g_free (key); + g_free (f_info->folder_name); + g_free (f_info); + return TRUE; +} + +static gboolean +free_row_refs (gpointer key, gpointer value, gpointer user_data) +{ + g_free (key); + gtk_tree_row_reference_free (value); + return TRUE; +} + +static void +finalize (GObject *object) +{ + ExchangeFolderSize *fsize = EXCHANGE_FOLDER_SIZE (object); + + g_hash_table_foreach_remove (fsize->priv->table, free_fsize_table, NULL); + g_hash_table_destroy (fsize->priv->table); + g_hash_table_foreach_remove (fsize->priv->row_refs, free_row_refs, NULL); + g_hash_table_destroy (fsize->priv->row_refs); + if (fsize->priv->model) + g_object_unref (fsize->priv->model); + g_free (fsize->priv); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +dispose (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +class_init (GObjectClass *object_class) +{ + parent_class = g_type_class_ref (PARENT_TYPE); + + /* override virtual methods */ + object_class->dispose = dispose; + object_class->finalize = finalize; + +} + +static void +init (GObject *object) +{ + ExchangeFolderSize *fsize = EXCHANGE_FOLDER_SIZE (object); + + fsize->priv = g_new0 (ExchangeFolderSizePrivate, 1); + fsize->priv->table = g_hash_table_new (g_str_hash, g_str_equal); + fsize->priv->model = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_DOUBLE); + fsize->priv->row_refs = g_hash_table_new (g_str_hash, g_str_equal); +} + +E2K_MAKE_TYPE (exchange_folder_size, ExchangeFolderSize, class_init, init, PARENT_TYPE) + +/** + * exchange_folder_size_new: + * @display_name: the delegate's (UTF8) display name + * + * Return value: a foldersize object with the table initialized + **/ +ExchangeFolderSize * +exchange_folder_size_new (void) +{ + ExchangeFolderSize *fsize; + + fsize = g_object_new (EXCHANGE_TYPE_FOLDER_SIZE, NULL); + + return fsize; +} + +void +exchange_folder_size_update (ExchangeFolderSize *fsize, + const char *folder_name, + gdouble folder_size) +{ + folder_info *f_info, *cached_info; + ExchangeFolderSizePrivate *priv; + GHashTable *folder_size_table; + GtkTreeRowReference *row; + GtkTreeIter iter; + GtkTreePath *path; + + g_return_if_fail (EXCHANGE_IS_FOLDER_SIZE (fsize)); + + priv = fsize->priv; + folder_size_table = priv->table; + + cached_info = g_hash_table_lookup (folder_size_table, folder_name); + if (cached_info) { + if (cached_info->folder_size == folder_size) { + return; + } else { + cached_info->folder_size = folder_size; + row = g_hash_table_lookup (priv->row_refs, folder_name); + path = gtk_tree_row_reference_get_path (row); + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (fsize->priv->model), &iter, path)) { + gtk_list_store_set (fsize->priv->model, &iter, + COLUMN_NAME, cached_info->folder_name, + COLUMN_SIZE, cached_info->folder_size, + -1); + } + gtk_tree_path_free (path); + return; + } + } else { + f_info = g_new0(folder_info, 1); + f_info->folder_name = g_strdup (folder_name); + f_info->folder_size = folder_size; + g_hash_table_insert (folder_size_table, f_info->folder_name, f_info); + + gtk_list_store_append (fsize->priv->model, &iter); + gtk_list_store_set (fsize->priv->model, &iter, + COLUMN_NAME, f_info->folder_name, + COLUMN_SIZE, f_info->folder_size, + -1); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (fsize->priv->model), &iter); + row = gtk_tree_row_reference_new (GTK_TREE_MODEL (fsize->priv->model), path); + gtk_tree_path_free (path); + + g_hash_table_insert (fsize->priv->row_refs, g_strdup (folder_name), row); + } +} + +void +exchange_folder_size_remove (ExchangeFolderSize *fsize, + const char *folder_name) +{ + ExchangeFolderSizePrivate *priv; + GHashTable *folder_size_table; + folder_info *cached_info; + GtkTreeRowReference *row; + GtkTreeIter iter; + GtkTreePath *path; + + g_return_if_fail (EXCHANGE_IS_FOLDER_SIZE (fsize)); + g_return_if_fail (folder_name != NULL); + + priv = fsize->priv; + folder_size_table = priv->table; + + cached_info = g_hash_table_lookup (folder_size_table, folder_name); + if (cached_info) { + row = g_hash_table_lookup (priv->row_refs, folder_name); + path = gtk_tree_row_reference_get_path (row); + g_hash_table_remove (folder_size_table, folder_name); + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (fsize->priv->model), &iter, path)) { + gtk_list_store_remove (fsize->priv->model, &iter); + } + g_hash_table_remove (priv->row_refs, row); + gtk_tree_path_free (path); + } +} + +gdouble +exchange_folder_size_get (ExchangeFolderSize *fsize, + const char *folder_name) +{ + ExchangeFolderSizePrivate *priv; + GHashTable *folder_size_table; + folder_info *cached_info; + + g_return_val_if_fail (EXCHANGE_IS_FOLDER_SIZE (fsize), -1); + + priv = fsize->priv; + folder_size_table = priv->table; + + cached_info = g_hash_table_lookup (folder_size_table, folder_name); + if (cached_info) { + return cached_info->folder_size; + } + return -1; +} + +static void +format_size_func (GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + GtkCellRendererText *cell = (GtkCellRendererText *)renderer; + gdouble folder_size; + char * new_text; + + gtk_tree_model_get(model, iter, COLUMN_SIZE, &folder_size, -1); + + if (folder_size) + new_text = g_strdup_printf ("%.2f", folder_size); + else + new_text = g_strdup ("0"); + + g_object_set (cell, "text", new_text, NULL); + g_free (new_text); +} + +static void +parent_destroyed (gpointer dialog, GObject *ex_parent) +{ + gtk_dialog_response (dialog, GTK_RESPONSE_CANCEL); +} + +void +exchange_folder_size_display (EFolder *folder, GtkWidget *parent) +{ + ExchangeFolderSizePrivate *priv; + ExchangeFolderSize *fsize; + ExchangeHierarchy *hier; + GtkTreeViewColumn *column; + GtkTreeSortable *sortable; + GtkCellRenderer *cell; + GHashTable *folder_size_table; + GladeXML *xml; + GtkWidget *dialog, *table; + GList *l; + char *col_name; + int response; + + g_return_if_fail (GTK_IS_WIDGET (parent)); + + hier = e_folder_exchange_get_hierarchy (folder); + if (!hier) + return; + /* FIXME: This should be a more generic query and not just + specifically for webdav */ + fsize = exchange_hierarchy_webdav_get_folder_size (EXCHANGE_HIERARCHY_WEBDAV (hier)); + if (!fsize) + return; + priv = fsize->priv; + folder_size_table = priv->table; + + if (!g_hash_table_size (folder_size_table)) + return; + + xml = glade_xml_new (CONNECTOR_GLADEDIR "/exchange-folder-tree.glade", NULL, NULL); + g_return_if_fail (xml != NULL); + dialog = glade_xml_get_widget (xml, "folder_tree"); + table = glade_xml_get_widget (xml, "folder_treeview"); + + e_dialog_set_transient_for (GTK_WINDOW (dialog), parent); + /* fsize->parent = parent; */ + g_object_weak_ref (G_OBJECT (parent), parent_destroyed, dialog); + + /* Set up the table */ + sortable = GTK_TREE_SORTABLE (priv->model); + gtk_tree_sortable_set_sort_column_id (sortable, COLUMN_SIZE, GTK_SORT_DESCENDING); + + column = gtk_tree_view_column_new_with_attributes ( + _("Folder Name"), gtk_cell_renderer_text_new (), "text", COLUMN_NAME, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (table), + column); + + col_name = g_strdup_printf ("%s (KB)", _("Folder Size")); + column = gtk_tree_view_column_new_with_attributes ( + col_name, gtk_cell_renderer_text_new (), "text", COLUMN_SIZE, NULL); + g_free (col_name); + + l = gtk_tree_view_column_get_cell_renderers (column); + cell = (GtkCellRenderer *)l->data; + gtk_tree_view_column_set_cell_data_func (column, cell, format_size_func, NULL, NULL ); + g_list_free (l); + + gtk_tree_view_append_column (GTK_TREE_VIEW (table), + column); + gtk_tree_view_set_model (GTK_TREE_VIEW (table), + GTK_TREE_MODEL (priv->model)); + response = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + g_object_unref (xml); +} diff --git a/plugins/exchange-account-setup/exchange-folder-size.h b/plugins/exchange-account-setup/exchange-folder-size.h new file mode 100644 index 0000000000..2d673902a1 --- /dev/null +++ b/plugins/exchange-account-setup/exchange-folder-size.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* Copyright (C) 2002-2004 Novell, Inc. */ + +#ifndef __EXCHANGE_FOLDER_SIZE_H__ +#define __EXCHANGE_FOLDER_SIZE_H__ + +#include <exchange-types.h> +#include <e2k-security-descriptor.h> +#include <e-folder.h> +#include <gtk/gtkwidget.h> + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#define EXCHANGE_TYPE_FOLDER_SIZE (exchange_folder_size_get_type ()) +#define EXCHANGE_FOLDER_SIZE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXCHANGE_TYPE_FOLDER_SIZE, ExchangeFolderSize)) +#define EXCHANGE_FOLDER_SIZE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EXCHANGE_TYPE_FOLDER_SIZE, ExchangeFolderSizeClass)) +#define EXCHANGE_IS_FOLDER_SIZE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXCHANGE_TYPE_FOLDER_SIZE)) +#define EXCHANGE_IS_FOLDER_SIZE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EXCHANGE_TYPE_FOLDER_SIZE)) + + +typedef struct _ExchangeFolderSize ExchangeFolderSize; +typedef struct _ExchangeFolderSizePrivate ExchangeFolderSizePrivate; +typedef struct _ExchangeFolderSizeClass ExchangeFolderSizeClass; + +struct _ExchangeFolderSize { + GObject parent; + + ExchangeFolderSizePrivate *priv; +}; + +struct _ExchangeFolderSizeClass { + GObjectClass parent_class; + +}; + +GType exchange_folder_size_get_type (void); + +ExchangeFolderSize *exchange_folder_size_new (void); + +void exchange_folder_size_update (ExchangeFolderSize *fsize, + const char *folder_name, + gdouble folder_size); +void exchange_folder_size_remove (ExchangeFolderSize *fsize, const char *folder_name); + +gdouble exchange_folder_size_get (ExchangeFolderSize *fsize, const char *folder_name); + +void exchange_folder_size_display (EFolder *folder, GtkWidget *parent); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EXCHANGE_FOLDER_SIZE_H__ */ diff --git a/plugins/exchange-account-setup/exchange-folder-tree.glade b/plugins/exchange-account-setup/exchange-folder-tree.glade new file mode 100644 index 0000000000..0505681a7b --- /dev/null +++ b/plugins/exchange-account-setup/exchange-folder-tree.glade @@ -0,0 +1,92 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> +<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> + +<glade-interface> + +<widget class="GtkDialog" id="folder_tree"> + <property name="visible">True</property> + <property name="title" translatable="yes">Exchange Folder Tree</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_NONE</property> + <property name="modal">False</property> + <property name="default_width">250</property> + <property name="default_height">300</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">False</property> + <property name="has_separator">True</property> + + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + + <child> + <widget class="GtkButton" id="okbutton1"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-ok</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="response_id">-5</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="folder_tree_hbox"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property> + <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + + <child> + <widget class="GtkTreeView" id="folder_treeview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">True</property> + <property name="rules_hint">False</property> + <property name="reorderable">False</property> + <property name="enable_search">True</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + </child> +</widget> + +</glade-interface> |