changeset 173:45da59a32019 multiaccounts

trying to make a dialog per conversation
author mikanbako <maoutwo@gmail.com>
date Thu, 31 Jul 2008 19:30:24 +0900
parents 3c5fc8d7b506
children 002538feba6e
files pidgin-twitter.c pidgin-twitter.h
diffstat 2 files changed, 459 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/pidgin-twitter.c	Thu Jul 31 17:22:02 2008 +0900
+++ b/pidgin-twitter.c	Thu Jul 31 19:30:24 2008 +0900
@@ -762,6 +762,17 @@
 
 }
 
+static void
+notify_that_input_screen_name(PurpleConversation *conv)
+{
+    PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
+    GtkWidget *box = gtkconv->toolbar;
+    GtkWidget *pref_button = g_object_get_data(G_OBJECT(box),
+                                               PLUGIN_ID "-pref_button");
+   
+    gtk_button_set_label(GTK_BUTTON(pref_button), ACCOUNT_PREF_BUTTON_NOTICE);
+}
+
 /***********************/
 /* intrinsic functions */
 /***********************/
@@ -771,8 +782,17 @@
 {
     int utflen, bytes;
     gboolean twitter_ac = FALSE, wassr_ac = FALSE, identica_ac = FALSE;
+    PurpleConversation *conv = purple_find_conversation_with_account(
+                                           PURPLE_CONV_TYPE_ANY,
+                                           recipient,
+                                           account);
     twitter_debug("called\n");
 
+    if(conv && !strcmp(EMPTY, service_account_get_string(conv,
+                                                         OPT_SCREEN_NAME,
+                                                         EMPTY)))
+        notify_that_input_screen_name(conv);
+
     twitter_ac = is_twitter_account(account, recipient);
     wassr_ac   = is_wassr_account(account, recipient);
     identica_ac = is_identica_account(account, recipient);
@@ -1181,7 +1201,7 @@
 detach_from_conv(PurpleConversation *conv, gpointer null)
 {
     PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
-    GtkWidget *box, *counter = NULL, *sep = NULL;
+    GtkWidget *box, *counter = NULL, *sep = NULL, *pref_button = NULL;
 
     g_signal_handlers_disconnect_by_func(G_OBJECT(gtkconv->entry_buffer),
                                          (GFunc) insert_text_cb, conv);
@@ -1190,6 +1210,17 @@
 
     box = gtkconv->toolbar;
 
+    /* remove account preference button */
+    pref_button = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-pref_button");
+    if(pref_button) {
+        g_signal_handlers_disconnect_by_func(G_OBJECT(pref_button),
+            (GFunc) open_account_preference_window,
+            purple_conversation_get_account(conv)->username);
+        gtk_container_remove(GTK_CONTAINER(box), pref_button);
+        g_object_unref(pref_button);
+        g_object_set_data(G_OBJECT(box), PLUGIN_ID "-pref_button", NULL);
+    }
+
     /* remove counter */
     counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter");
     if(counter) {
@@ -1319,8 +1350,11 @@
         ~PURPLE_CONNECTION_HTML);
 
     /* check if the counter is enabled */
-    if(!purple_prefs_get_bool(OPT_COUNTER))
+    if(!purple_prefs_get_bool(OPT_COUNTER)) {
+        append_account_preference_button(gtkconv);
+        gtk_widget_queue_draw(pidgin_conv_get_window(gtkconv)->window);
         return;
+    }
 
     /* get counter object */
     counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter");
@@ -1346,10 +1380,29 @@
     g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "delete_range",
                      G_CALLBACK(delete_text_cb), conv);
 
+    append_account_preference_button(gtkconv);
+
     /* redraw window */
     gtk_widget_queue_draw(pidgin_conv_get_window(gtkconv)->window);
 }
 
+static void
+append_account_preference_button(PidginConversation *gtkconv)
+{
+    GtkWidget *pref_button;
+    GtkWidget *box = gtkconv->toolbar;
+
+    pref_button = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-pref_button");
+    g_return_if_fail(pref_button == NULL);
+
+    pref_button = gtk_button_new_with_label(ACCOUNT_PREF_BUTTON_NORMAL);
+    g_signal_connect(G_OBJECT(pref_button), "clicked",
+        G_CALLBACK(open_account_preference_window), gtkconv);
+    gtk_box_pack_end(GTK_BOX(box), pref_button, FALSE, FALSE, 0);
+    gtk_widget_show_all(pref_button);
+    g_object_set_data(G_OBJECT(box), PLUGIN_ID "-pref_button", pref_button);
+}
+
 static gboolean
 is_twitter_account(PurpleAccount *account, const char *name)
 {
@@ -2932,6 +2985,386 @@
     return notebook;
 }
 
+static void
+open_account_preference_window(GtkWidget *pref_button, gpointer gtkconv_ptr)
+{
+    PidginConversation *gtkconv = (PidginConversation *) gtkconv_ptr;
+    PurpleConversation *conv = gtkconv->active_conv;
+    GtkWidget *dialog;
+    GtkWidget *notebook = gtk_notebook_new();
+    gchar *title;
+
+    title = g_strdup_printf("\"%s\" Account Preference", conv->title);
+
+    dialog = gtk_dialog_new_with_buttons(
+                 title,
+                 GTK_WINDOW(pidgin_conv_get_window(gtkconv)->window),
+                 0, /* this dialog is destroyed by our signal hander */
+                 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+                 NULL);
+    g_free(title);
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
+
+    /* This signal handler is to close the dialog
+     * when its parent notebook or window closed.
+     * NOTE : GTK_DIALOG_DESTROY_WITH_PARENT was not used
+     * because the dialog do not close when its parent notebook closed.
+     */
+    g_signal_connect(G_OBJECT(pref_button),
+                     "destroy",
+                     G_CALLBACK(destroyed_pref_button_cb),
+                     dialog);
+    /* This signal handler is to enable called_widged. */
+    g_signal_connect(dialog,
+                     "response",
+                     G_CALLBACK(closed_account_preference_window_cb),
+                     pref_button);
+
+    /* Screen Name */
+    build_screen_name_preference_form(conv, notebook);
+    /* API Base Post */
+    if(is_twitter_conv(conv))
+        build_twitter_api_preference_form(conv, notebook);
+
+    gtk_button_set_label(GTK_BUTTON(pref_button), ACCOUNT_PREF_BUTTON_NORMAL);
+    gtk_widget_set_sensitive(pref_button, FALSE);
+    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), notebook);
+    gtk_widget_show_all(GTK_WIDGET(dialog));
+}
+
+static void
+build_screen_name_preference_form(PurpleConversation *conv,
+                                  GtkWidget *notebook)
+{
+    GtkWidget *tab_label, *child, *line, *container, *widget;
+
+    child = gtk_vbox_new(FALSE, 0);
+
+    /********************/
+    /* screen name form */
+    /********************/
+    line = gtk_hbox_new(TRUE, 0);
+
+    /* label */
+    widget = gtk_label_new("screen name");
+    container = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(container), widget);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(container), 0, 0, 20, 0);
+    gtk_box_pack_start_defaults(GTK_BOX(line), container);
+
+    /* entry */
+    widget = gtk_entry_new();
+    gtk_entry_set_text(GTK_ENTRY(widget),
+                       service_account_get_string(conv,
+                                                  OPT_SCREEN_NAME,
+                                                  ""));
+    gtk_entry_set_activates_default(GTK_ENTRY(widget), TRUE);
+    g_object_set_data(G_OBJECT(widget),
+                      PLUGIN_ID "-account_pref-key",
+                      OPT_SCREEN_NAME);
+    g_signal_connect(G_OBJECT(widget),
+                     "changed",
+                     G_CALLBACK(account_preference_text_changed_cb),
+                     conv);
+    container = gtk_alignment_new(1.0, 0.5, 1.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(container), widget);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(container), 0, 0, 0, 20);
+    gtk_box_pack_start_defaults(GTK_BOX(line), container);
+
+    gtk_box_pack_start_defaults(GTK_BOX(child), line);
+
+    /**************************************************/
+    /* the explanation why a screen name is necessary */
+    /**************************************************/
+    line = gtk_hbox_new(FALSE, 0);
+    widget = gtk_label_new("If you do not input your screen name,");
+    container = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(container), widget);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(container), 20, 0, 20, 0);
+    gtk_box_pack_start(GTK_BOX(line), container, FALSE, FALSE, 0);
+    gtk_box_pack_start(GTK_BOX(child), line, FALSE, FALSE, 0);
+
+    line = gtk_hbox_new(FALSE, 0);
+    widget = gtk_label_new("you cannot see your icon.");
+    container = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(container), widget);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(container), 0, 0, 20, 0);
+    gtk_box_pack_start(GTK_BOX(line), line, FALSE, FALSE, 0);
+    gtk_box_pack_start(GTK_BOX(child), container, FALSE, FALSE, 0);
+
+    /***************/
+    /* create page */
+    /***************/
+    tab_label = gtk_label_new("Screen Name");
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), child, tab_label);
+}
+
+static void
+build_twitter_api_preference_form(PurpleConversation *conv,
+                                  GtkWidget *notebook)
+{
+    GtkWidget *tab_label, *child, *line, *container, *widget;
+    GtkObject *adjust;
+    int value;
+
+    child = gtk_vbox_new(FALSE, 0);
+
+    /*****************************/
+    /* Checkbox about to use API */
+    /*****************************/
+
+    line = gtk_hbox_new(TRUE, 0);
+    widget = gtk_check_button_new_with_label("Get/post statuses via API");
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
+                                 service_account_get_bool(conv,
+                                                          OPT_API_BASE_POST,
+                                                          FALSE));
+    g_object_set_data(G_OBJECT(widget),
+                      PLUGIN_ID "-account_pref-key",
+                      OPT_API_BASE_POST);
+    g_signal_connect(G_OBJECT(widget),
+                     "toggled",
+                     G_CALLBACK(account_preference_bool_toggled_cb),
+                     conv);
+    container = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(container), widget);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(container), 0, 0, 20, 0);
+    gtk_box_pack_start_defaults(GTK_BOX(line), container);
+
+    gtk_box_pack_start_defaults(GTK_BOX(child), line);
+
+    /*****************/
+    /* Password Form */
+    /*****************/
+
+    /* label */
+    line = gtk_hbox_new(TRUE, 0);
+    widget = gtk_label_new("Password");
+    container = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(container), widget);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(container), 0, 0, 20, 0);
+    gtk_box_pack_start_defaults(GTK_BOX(line), container);
+
+    /* entry */
+    widget = gtk_entry_new();
+    gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
+    gtk_entry_set_activates_default(GTK_ENTRY(widget), TRUE);
+    if (gtk_entry_get_invisible_char(GTK_ENTRY(widget)) == '*')
+        gtk_entry_set_invisible_char(GTK_ENTRY(widget), PIDGIN_INVISIBLE_CHAR);
+    gtk_entry_set_text(GTK_ENTRY(widget),
+                       service_account_get_string(conv,
+                                                  OPT_API_BASE_PASSWORD,
+                                                  ""));
+    g_object_set_data(G_OBJECT(widget),
+                      PLUGIN_ID "-account_pref-key",
+                      OPT_API_BASE_PASSWORD);
+    g_signal_connect(G_OBJECT(widget),
+                     "changed",
+                     G_CALLBACK(account_preference_text_changed_cb),
+                     conv);
+    container = gtk_alignment_new(1.0, 0.5, 1.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(container), widget);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(container), 0, 0, 0, 20);
+    gtk_box_pack_start_defaults(GTK_BOX(line), container);
+    
+    gtk_box_pack_start_defaults(GTK_BOX(child), line);
+
+    /**************************/
+    /* Retrieve interval Form */
+    /**************************/
+
+    /* label */
+    line = gtk_hbox_new(FALSE, 0);
+    widget = gtk_label_new("Retrieve interval");
+    container = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(container), widget);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(container), 0, 0, 20, 0);
+    gtk_box_pack_start_defaults(GTK_BOX(line), container);
+
+    /* spin button */
+    value = service_account_get_int(conv,
+                                    OPT_API_BASE_GET_INTERVAL,
+                                    0);
+    if(value == 0) {
+        value = TWITTER_DEFAULT_INTERVAL;
+        purple_prefs_set_int(OPT_API_BASE_GET_INTERVAL, value);
+    }
+    adjust = gtk_adjustment_new(value, 40, 3600, 10, 100, 100);
+    widget = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1.0, 0);
+    gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(widget), TRUE);
+    gtk_spin_button_set_snap_to_ticks(GTK_SPIN_BUTTON(widget), TRUE);
+    g_object_set_data(G_OBJECT(widget),
+                      PLUGIN_ID "-account_pref-key",
+                      OPT_API_BASE_GET_INTERVAL);
+    g_signal_connect(G_OBJECT(widget),
+                     "value-changed",
+                     G_CALLBACK(account_preference_spin_changed_cb),
+                     conv);
+    container = gtk_alignment_new(1.0, 0.5, 0.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(container), widget);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(container), 0, 0, 0, 0);
+    gtk_box_pack_start(GTK_BOX(line), container, FALSE, FALSE, 0);
+
+    /* label */
+    widget = gtk_label_new("sec");
+    container = gtk_alignment_new(1.0, 0.5, 0.0, 0.0);
+    gtk_container_add(GTK_CONTAINER(container), widget);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(container), 0, 0, 0, 20);
+    gtk_box_pack_start(GTK_BOX(line), container, FALSE, FALSE, 0);
+
+    gtk_box_pack_start_defaults(GTK_BOX(child), line);
+
+    /***************/
+    /* create page */
+    /***************/
+
+    tab_label = gtk_label_new("API Based Access");
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), child, tab_label);
+}
+
+static void
+account_preference_text_changed_cb(GtkEditable *editable, gpointer conv_ptr)
+{
+    PurpleConversation *conv = (PurpleConversation *) conv_ptr;
+    const char *key = g_object_get_data(G_OBJECT(editable),
+                                        PLUGIN_ID "-account_pref-key");
+    gchar *value = gtk_editable_get_chars(editable, 0, -1);
+
+    service_account_set_string(conv, key, value);
+    g_free(value);
+}
+
+static void
+account_preference_bool_toggled_cb(GtkToggleButton *togglebutton,
+                                   gpointer conv_ptr)
+{
+    PurpleConversation *conv = (PurpleConversation *) conv_ptr;
+    const char *key = g_object_get_data(G_OBJECT(togglebutton),
+                                        PLUGIN_ID "-account_pref-key");
+    gboolean value = gtk_toggle_button_get_active(togglebutton);
+
+    service_account_set_bool(conv, key, value);
+}
+
+static void
+account_preference_spin_changed_cb(GtkSpinButton *spinbutton, gpointer conv_ptr)
+{
+    PurpleConversation *conv = (PurpleConversation *) conv_ptr;
+    const char *key = g_object_get_data(G_OBJECT(spinbutton),
+                                         PLUGIN_ID "-account_pref-key");
+    gint value = gtk_spin_button_get_value_as_int(spinbutton);
+
+    service_account_set_int(conv, key, value);
+}
+
+static void
+destroyed_pref_button_cb(GtkWidget *pref_button, gpointer dialog)
+{
+    gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+static void
+closed_account_preference_window_cb(GtkDialog *dialog,
+                                    gint response_id,
+                                    gpointer pref_button)
+{
+    g_signal_handlers_disconnect_by_func(GTK_WIDGET(pref_button),
+                                         (GFunc) destroyed_pref_button_cb,
+                                         dialog);
+    gtk_widget_set_sensitive(GTK_WIDGET(pref_button), TRUE);
+    gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+static gchar*
+create_key_with_service_account(PurpleConversation *conv, const char *key)
+{
+    return g_strdup_printf("%s:%s", key, conv->name);
+}
+
+static void
+service_account_set_string(PurpleConversation *conv,
+                                      const char *key,
+                                      const char *value)
+{
+    PurpleAccount *account = purple_conversation_get_account(conv);
+    gchar *key_with_account = create_key_with_service_account(conv, key);
+
+    purple_account_set_string(account, key_with_account, value);
+    g_free(key_with_account);
+}
+
+static const char*
+service_account_get_string(PurpleConversation *conv,
+                                      const char *key,
+                                      const char *default_value)
+{
+    PurpleAccount *account = purple_conversation_get_account(conv);
+    gchar *key_with_account = create_key_with_service_account(conv, key);
+    const char *value = purple_account_get_string(account,
+                                                  key_with_account,
+                                                  default_value);
+
+    g_free(key_with_account);
+
+    return value;
+}
+
+static void
+service_account_set_int(PurpleConversation *conv,
+                                   const char *key,
+                                   int value)
+{
+    PurpleAccount *account = purple_conversation_get_account(conv);
+    gchar *key_with_account = create_key_with_service_account(conv, key);
+
+    purple_account_set_int(account, key_with_account, value);
+    g_free(key_with_account);
+}
+
+static int
+service_account_get_int(PurpleConversation *conv,
+                                   const char *key,
+                                   int default_value)
+{
+    PurpleAccount *account = purple_conversation_get_account(conv);
+    gchar *key_with_account = create_key_with_service_account(conv, key);
+    int value = purple_account_get_int(account,
+                                        key_with_account,
+                                        default_value);
+
+    g_free(key_with_account);
+
+    return value;
+}
+
+static void
+service_account_set_bool(PurpleConversation *conv,
+                                    const char *key,
+                                    gboolean value)
+{
+    PurpleAccount *account = purple_conversation_get_account(conv);
+    gchar *key_with_account = create_key_with_service_account(conv, key);
+
+    purple_account_set_bool(account, key_with_account, value);
+    g_free(key_with_account);
+}
+
+static gboolean
+service_account_get_bool(PurpleConversation *conv,
+                                    const char *key,
+                                    gboolean default_value)
+{
+    PurpleAccount *account = purple_conversation_get_account(conv);
+    gchar *key_with_account = create_key_with_service_account(conv, key);
+    gboolean value = purple_account_get_bool(account,
+                                             key_with_account,
+                                             default_value);
+
+    g_free(key_with_account);
+
+    return value;
+}
+
 static PidginPluginUiInfo ui_info = {
     prefs_get_frame,
 	0,									/* page number - reserved	*/
--- a/pidgin-twitter.h	Thu Jul 31 17:22:02 2008 +0900
+++ b/pidgin-twitter.h	Thu Jul 31 19:30:24 2008 +0900
@@ -118,6 +118,10 @@
 #define OPT_API_BASE_GET_INTERVAL OPT_PIDGINTWITTER "/api_base_get_interval"
 #define OPT_LOG_OUTPUT          OPT_PIDGINTWITTER "/log_output"
 
+/* options depend on service account */
+#define OPT_SCREEN_NAME         OPT_PIDGINTWITTER "/screen_name"
+#define OPT_API_BASE_PASSWORD      OPT_PIDGINTWITTER "/api_base_password"
+
 /* formats and templates */
 #define RECIPIENT_FORMAT_TWITTER "@<a href='http://twitter.com/%s'>%s</a>"
 #define SENDER_FORMAT_TWITTER   "%s<a href='http://twitter.com/%s'>%s</a>: "
@@ -130,6 +134,10 @@
 #define OOPS_MESSAGE            "<body>Oops! Your update was over 140 characters. We sent the short version to your friends (they can view the entire update on the web).<BR></body>"
 #define EMPTY                   ""
 
+/* labels of account preference button */
+#define ACCOUNT_PREF_BUTTON_NORMAL   "Account Preference"
+#define ACCOUNT_PREF_BUTTON_NOTICE   "(*)Account Preference"
+
 /* patterns */
 #define P_RECIPIENT         "@([A-Za-z0-9_]+)"
 #define P_SENDER            "^(\\r?\\n?)\\s*([A-Za-z0-9_]+): "
@@ -185,6 +193,7 @@
 static void delete_requested_icon_marks(PidginConversation *gtkconv, GHashTable *table);
 static void attach_to_window(void);
 static void attach_to_conv(PurpleConversation *conv, gpointer null);
+static void append_account_preference_button(PidginConversation *gtkconv);
 static gboolean is_twitter_account(PurpleAccount *account, const char *name);
 static gboolean is_twitter_conv(PurpleConversation *conv);
 static gboolean is_wassr_account(PurpleAccount *account, const char *name);
@@ -210,6 +219,21 @@
 static void cancel_fetch_func(gpointer key, gpointer value, gpointer user_data);
 static gint get_service_type(PurpleConversation *conv);
 static GdkPixbuf *make_scaled_pixbuf(const gchar *url_text, gsize len);
+static void open_account_preference_window(GtkWidget *called_widged, gpointer gtkconv_ptr);
+static void build_screen_name_preference_form(PurpleConversation *conv, GtkWidget *notebook);
+static void build_twitter_api_preference_form(PurpleConversation *conv, GtkWidget *notebook);
+static void destroyed_pref_button_cb(GtkWidget *pref_button, gpointer dialog);
+static void closed_account_preference_window_cb(GtkDialog *dialog, gint response_id, gpointer pref_button);
+static void account_preference_text_changed_cb(GtkEditable *editable, gpointer conv_ptr);
+static void account_preference_bool_toggled_cb(GtkToggleButton *togglebutton, gpointer conv_ptr);
+static void account_preference_spin_changed_cb(GtkSpinButton *spinbutton, gpointer conv_ptr);
+static gchar* create_key_with_service_account(PurpleConversation *conv, const char *key);
+static void service_account_set_string(PurpleConversation *conv, const char *key, const char *value);
+static const char* service_account_get_string(PurpleConversation *conv, const char *key, const char *default_value);
+static void service_account_set_int(PurpleConversation *conv, const char *key, gboolean value);
+static gboolean service_account_get_int(PurpleConversation *conv, const char *key, gboolean default_value);
+static void service_account_set_bool(PurpleConversation *conv, const char *key, gboolean value);
+static gboolean service_account_get_bool(PurpleConversation *conv, const char *key, gboolean default_value);
 
 static void parse_user(xmlNode *user, status_t *st);
 static void parse_status(xmlNode *status, status_t *st);