pidgin/gtkblist.c

changeset 41445
61d9b9e2b974
parent 41440
348cc74f084e
child 41452
e4b5b21470b4
equal deleted inserted replaced
41444:d8e0a25a57ac 41445:61d9b9e2b974
94 94
95 typedef struct 95 typedef struct
96 { 96 {
97 /* GBoxed reference count */ 97 /* GBoxed reference count */
98 int box_count; 98 int box_count;
99
100 /* Used to hold error minidialogs. Gets packed
101 * inside PidginBuddyList.error_buttons
102 */
103 PidginScrollBook *error_scrollbook;
104
105 /* Pointer to the mini-dialog about having signed on elsewhere, if one
106 * is showing; %NULL otherwise.
107 */
108 PidginMiniDialog *signed_on_elsewhere;
109 99
110 guint select_notebook_page_timeout; 100 guint select_notebook_page_timeout;
111 101
112 102
113 } PidginBuddyListPrivate; 103 } PidginBuddyListPrivate;
3095 { 3085 {
3096 if(purple_strequal(pref_name, PIDGIN_PREFS_ROOT "/blist/sort_type")) 3086 if(purple_strequal(pref_name, PIDGIN_PREFS_ROOT "/blist/sort_type"))
3097 pidgin_blist_sort_method_set(val); 3087 pidgin_blist_sort_method_set(val);
3098 } 3088 }
3099 3089
3100 /***********************************/
3101 /* Connection error handling stuff */
3102 /***********************************/
3103
3104 #define OBJECT_DATA_KEY_ACCOUNT "account"
3105 #define DO_NOT_CLEAR_ERROR "do-not-clear-error"
3106
3107 static gboolean
3108 find_account_widget(GObject *widget,
3109 PurpleAccount *account)
3110 {
3111 if (g_object_get_data(widget, OBJECT_DATA_KEY_ACCOUNT) == account)
3112 return 0; /* found */
3113 else
3114 return 1;
3115 }
3116
3117 static void
3118 pack_protocol_icon_start(GtkWidget *box,
3119 PurpleAccount *account)
3120 {
3121 GdkPixbuf *pixbuf;
3122 GtkWidget *image;
3123
3124 pixbuf = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
3125 if (pixbuf != NULL) {
3126 image = gtk_image_new_from_pixbuf(pixbuf);
3127 g_object_unref(pixbuf);
3128
3129 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
3130 }
3131 }
3132
3133 static void
3134 add_error_dialog(PidginBuddyList *gtkblist,
3135 GtkWidget *dialog)
3136 {
3137 PidginBuddyListPrivate *priv =
3138 pidgin_buddy_list_get_instance_private(gtkblist);
3139 gtk_container_add(GTK_CONTAINER(priv->error_scrollbook), dialog);
3140 }
3141
3142 static GtkWidget *
3143 find_child_widget_by_account(GtkContainer *container,
3144 PurpleAccount *account)
3145 {
3146 GList *l = NULL;
3147 GList *children = NULL;
3148 GtkWidget *ret = NULL;
3149 /* XXX: Workaround for the currently incomplete implementation of PidginScrollBook */
3150 if(PIDGIN_IS_SCROLL_BOOK(container)) {
3151 PidginScrollBook *scroll_book = PIDGIN_SCROLL_BOOK(container);
3152 GtkWidget *notebook = pidgin_scroll_book_get_notebook(scroll_book);
3153 container = GTK_CONTAINER(notebook);
3154 }
3155 children = gtk_container_get_children(container);
3156 l = g_list_find_custom(children, account, (GCompareFunc) find_account_widget);
3157 if (l)
3158 ret = GTK_WIDGET(l->data);
3159 g_list_free(children);
3160 return ret;
3161 }
3162
3163 static void
3164 remove_child_widget_by_account(GtkContainer *container,
3165 PurpleAccount *account)
3166 {
3167 GtkWidget *widget = find_child_widget_by_account(container, account);
3168 if(widget) {
3169 /* Since we are destroying the widget in response to a change in
3170 * error, we should not clear the error.
3171 */
3172 g_object_set_data(G_OBJECT(widget), DO_NOT_CLEAR_ERROR,
3173 GINT_TO_POINTER(TRUE));
3174 gtk_widget_destroy(widget);
3175 }
3176 }
3177
3178 /* Generic error buttons */
3179
3180 static void
3181 generic_account_connect_cb(G_GNUC_UNUSED PidginMiniDialog *mini_dialog,
3182 G_GNUC_UNUSED GtkButton *button,
3183 gpointer user_data)
3184 {
3185 PurpleAccount *account = user_data;
3186 purple_account_connect(account);
3187 }
3188
3189 static void
3190 generic_error_modify_cb(G_GNUC_UNUSED PidginMiniDialog *mini_dialog,
3191 G_GNUC_UNUSED GtkButton *button,
3192 gpointer user_data)
3193 {
3194 PurpleAccount *account = user_data;
3195 purple_account_clear_current_error(account);
3196 pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, account);
3197 }
3198
3199 static void
3200 generic_error_enable_cb(G_GNUC_UNUSED PidginMiniDialog *mini_dialog,
3201 G_GNUC_UNUSED GtkButton *button,
3202 gpointer user_data)
3203 {
3204 PurpleAccount *account = user_data;
3205 purple_account_clear_current_error(account);
3206 purple_account_set_enabled(account, TRUE);
3207 }
3208
3209 static void
3210 generic_error_destroy_cb(GtkWidget *dialog,
3211 PurpleAccount *account)
3212 {
3213 /* If the error dialog is being destroyed in response to the
3214 * account-error-changed signal, we don't want to clear the current
3215 * error.
3216 */
3217 if (g_object_get_data(G_OBJECT(dialog), DO_NOT_CLEAR_ERROR) == NULL)
3218 purple_account_clear_current_error(account);
3219 }
3220
3221 #define SSL_FAQ_URI "https://developer.pidgin.im/wiki/FAQssl"
3222
3223 static void
3224 ssl_faq_clicked_cb(PidginMiniDialog *mini_dialog,
3225 GtkButton *button,
3226 gpointer ignored)
3227 {
3228 purple_notify_uri(NULL, SSL_FAQ_URI);
3229 }
3230
3231 static void
3232 add_generic_error_dialog(PurpleAccount *account,
3233 const PurpleConnectionErrorInfo *err)
3234 {
3235 GtkWidget *mini_dialog;
3236 const char *username = purple_account_get_username(account);
3237 gboolean enabled = purple_account_get_enabled(account);
3238 char *primary;
3239
3240 if (enabled)
3241 primary = g_strdup_printf(_("%s disconnected"), username);
3242 else
3243 primary = g_strdup_printf(_("%s disabled"), username);
3244
3245 mini_dialog = pidgin_mini_dialog_new_with_buttons(
3246 primary, err->description, "dialog-error", account,
3247 enabled ? _("Reconnect") : _("Re-enable"),
3248 enabled ? generic_account_connect_cb : generic_error_enable_cb,
3249 _("Modify Account"), generic_error_modify_cb,
3250 NULL);
3251
3252 g_free(primary);
3253
3254 g_object_set_data(G_OBJECT(mini_dialog), OBJECT_DATA_KEY_ACCOUNT,
3255 account);
3256
3257 if(err->type == PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT)
3258 pidgin_mini_dialog_add_non_closing_button(PIDGIN_MINI_DIALOG(mini_dialog),
3259 _("SSL FAQs"), ssl_faq_clicked_cb, NULL);
3260
3261 g_signal_connect_after(mini_dialog, "destroy",
3262 (GCallback)generic_error_destroy_cb,
3263 account);
3264
3265 add_error_dialog(gtkblist, mini_dialog);
3266 }
3267
3268 static void
3269 remove_generic_error_dialog(PurpleAccount *account)
3270 {
3271 PidginBuddyListPrivate *priv =
3272 pidgin_buddy_list_get_instance_private(gtkblist);
3273 remove_child_widget_by_account(
3274 GTK_CONTAINER(priv->error_scrollbook), account);
3275 }
3276
3277
3278 static void
3279 update_generic_error_message(PurpleAccount *account,
3280 const char *description)
3281 {
3282 PidginBuddyListPrivate *priv =
3283 pidgin_buddy_list_get_instance_private(gtkblist);
3284 GtkWidget *mini_dialog = find_child_widget_by_account(
3285 GTK_CONTAINER(priv->error_scrollbook), account);
3286 pidgin_mini_dialog_set_description(PIDGIN_MINI_DIALOG(mini_dialog),
3287 description);
3288 }
3289
3290
3291 /* Notifications about accounts which were disconnected with
3292 * PURPLE_CONNECTION_ERROR_NAME_IN_USE
3293 */
3294
3295 typedef void (*AccountFunction)(PurpleAccount *);
3296
3297 static void
3298 elsewhere_foreach_account(PidginMiniDialog *mini_dialog,
3299 AccountFunction f)
3300 {
3301 PurpleAccount *account;
3302 GList *labels = gtk_container_get_children(
3303 GTK_CONTAINER(mini_dialog->contents));
3304 GList *l;
3305
3306 for (l = labels; l; l = l->next) {
3307 account = g_object_get_data(G_OBJECT(l->data), OBJECT_DATA_KEY_ACCOUNT);
3308 if (account)
3309 f(account);
3310 else
3311 purple_debug_warning("gtkblist", "mini_dialog's child "
3312 "didn't have an account stored in it!");
3313 }
3314 g_list_free(labels);
3315 }
3316
3317 static void
3318 enable_account(PurpleAccount *account)
3319 {
3320 purple_account_set_enabled(account, TRUE);
3321 }
3322
3323 static void
3324 reconnect_elsewhere_accounts(PidginMiniDialog *mini_dialog,
3325 GtkButton *button,
3326 gpointer unused)
3327 {
3328 elsewhere_foreach_account(mini_dialog, enable_account);
3329 }
3330
3331 static void
3332 clear_elsewhere_errors(PidginMiniDialog *mini_dialog,
3333 gpointer unused)
3334 {
3335 elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error);
3336 }
3337
3338 static void
3339 ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist)
3340 {
3341 PidginBuddyListPrivate *priv =
3342 pidgin_buddy_list_get_instance_private(gtkblist);
3343 PidginMiniDialog *mini_dialog;
3344
3345 if(priv->signed_on_elsewhere)
3346 return;
3347
3348 mini_dialog = priv->signed_on_elsewhere =
3349 pidgin_mini_dialog_new(_("Welcome back!"), NULL, "pidgin-disconnect");
3350
3351 pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"),
3352 reconnect_elsewhere_accounts, NULL);
3353
3354 /* Make dismissing the dialog clear the errors. The "destroy" signal
3355 * does not appear to fire at quit, which is fortunate!
3356 */
3357 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
3358 (GCallback) clear_elsewhere_errors, NULL);
3359
3360 add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog));
3361
3362 /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
3363 g_signal_connect(G_OBJECT(mini_dialog), "destroy",
3364 (GCallback) gtk_widget_destroyed, &(priv->signed_on_elsewhere));
3365 }
3366
3367 static void
3368 update_signed_on_elsewhere_minidialog_title(void)
3369 {
3370 PidginBuddyListPrivate *priv =
3371 pidgin_buddy_list_get_instance_private(gtkblist);
3372 PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
3373 guint accounts;
3374 char *title;
3375
3376 if (mini_dialog == NULL)
3377 return;
3378
3379 accounts = pidgin_mini_dialog_get_num_children(mini_dialog);
3380 if (accounts == 0) {
3381 gtk_widget_destroy(GTK_WIDGET(mini_dialog));
3382 return;
3383 }
3384
3385 title = g_strdup_printf(
3386 ngettext("%d account was disabled because you signed on from another location:",
3387 "%d accounts were disabled because you signed on from another location:",
3388 accounts),
3389 accounts);
3390 pidgin_mini_dialog_set_description(mini_dialog, title);
3391 g_free(title);
3392 }
3393
3394 static GtkWidget *
3395 create_account_label(PurpleAccount *account)
3396 {
3397 GtkWidget *hbox, *label;
3398 const char *username = purple_account_get_username(account);
3399 char *markup;
3400 char *description;
3401
3402 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
3403 g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_KEY_ACCOUNT, account);
3404
3405 pack_protocol_icon_start(hbox, account);
3406
3407 label = gtk_label_new(NULL);
3408 markup = g_strdup_printf("<span size=\"smaller\">%s</span>", username);
3409 gtk_label_set_markup(GTK_LABEL(label), markup);
3410 g_free(markup);
3411 gtk_label_set_xalign(GTK_LABEL(label), 0);
3412 gtk_label_set_yalign(GTK_LABEL(label), 0);
3413 g_object_set(G_OBJECT(label), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
3414 description = purple_account_get_current_error(account)->description;
3415 if (description != NULL && *description != '\0')
3416 gtk_widget_set_tooltip_text(label, description);
3417 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
3418
3419 return hbox;
3420 }
3421
3422 static void
3423 add_to_signed_on_elsewhere(PurpleAccount *account)
3424 {
3425 PidginBuddyListPrivate *priv =
3426 pidgin_buddy_list_get_instance_private(gtkblist);
3427 PidginMiniDialog *mini_dialog;
3428 GtkWidget *account_label;
3429
3430 ensure_signed_on_elsewhere_minidialog(gtkblist);
3431 mini_dialog = priv->signed_on_elsewhere;
3432
3433 if(find_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account))
3434 return;
3435
3436 account_label = create_account_label(account);
3437 gtk_box_pack_start(mini_dialog->contents, account_label, FALSE, FALSE, 0);
3438 gtk_widget_show_all(account_label);
3439
3440 update_signed_on_elsewhere_minidialog_title();
3441 }
3442
3443 static void
3444 remove_from_signed_on_elsewhere(PurpleAccount *account)
3445 {
3446 PidginBuddyListPrivate *priv =
3447 pidgin_buddy_list_get_instance_private(gtkblist);
3448 PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere;
3449 if(mini_dialog == NULL)
3450 return;
3451
3452 remove_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account);
3453
3454 update_signed_on_elsewhere_minidialog_title();
3455 }
3456
3457
3458 static void
3459 update_signed_on_elsewhere_tooltip(PurpleAccount *account,
3460 const char *description)
3461 {
3462 PidginBuddyListPrivate *priv =
3463 pidgin_buddy_list_get_instance_private(gtkblist);
3464 GtkContainer *c = GTK_CONTAINER(priv->signed_on_elsewhere->contents);
3465 GtkWidget *label = find_child_widget_by_account(c, account);
3466 gtk_widget_set_tooltip_text(label, description);
3467 }
3468
3469
3470 /* Call appropriate error notification code based on error types */
3471 static void
3472 update_account_error_state(PurpleAccount *account,
3473 const PurpleConnectionErrorInfo *old,
3474 const PurpleConnectionErrorInfo *new,
3475 PidginBuddyList *gtkblist)
3476 {
3477 gboolean descriptions_differ;
3478 const char *desc;
3479
3480 if (old == NULL && new == NULL)
3481 return;
3482
3483 if (old != NULL && new == NULL) {
3484 if(old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
3485 remove_from_signed_on_elsewhere(account);
3486 else
3487 remove_generic_error_dialog(account);
3488 return;
3489 }
3490
3491 if (old == NULL && new != NULL) {
3492 if(new->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
3493 add_to_signed_on_elsewhere(account);
3494 else
3495 add_generic_error_dialog(account, new);
3496 return;
3497 }
3498
3499 /* else, new and old are both non-NULL */
3500
3501 descriptions_differ = !purple_strequal(old->description, new->description);
3502 desc = new->description;
3503
3504 switch (new->type) {
3505 case PURPLE_CONNECTION_ERROR_NAME_IN_USE:
3506 if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE
3507 && descriptions_differ) {
3508 update_signed_on_elsewhere_tooltip(account, desc);
3509 } else {
3510 remove_generic_error_dialog(account);
3511 add_to_signed_on_elsewhere(account);
3512 }
3513 break;
3514 default:
3515 if (old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE) {
3516 remove_from_signed_on_elsewhere(account);
3517 add_generic_error_dialog(account, new);
3518 } else if (descriptions_differ) {
3519 update_generic_error_message(account, desc);
3520 }
3521 break;
3522 }
3523 }
3524
3525 /* In case accounts are loaded before the blist (which they currently are),
3526 * let's call update_account_error_state ourselves on every account's current
3527 * state when the blist starts.
3528 */
3529 static void
3530 show_initial_account_errors(PidginBuddyList *gtkblist)
3531 {
3532 PurpleAccountManager *manager = NULL;
3533 GList *l = NULL;
3534 PurpleAccount *account;
3535 const PurpleConnectionErrorInfo *err;
3536
3537 manager = purple_account_manager_get_default();
3538 l = purple_account_manager_get_all(manager);
3539 for(; l; l = l->next) {
3540 account = l->data;
3541 err = purple_account_get_current_error(account);
3542
3543 update_account_error_state(account, NULL, err, gtkblist);
3544 }
3545 }
3546
3547 /* This assumes there are not things like groupless buddies or multi-leveled groups. 3090 /* This assumes there are not things like groupless buddies or multi-leveled groups.
3548 * I'm sure other things in this code assumes that also. 3091 * I'm sure other things in this code assumes that also.
3549 */ 3092 */
3550 static void 3093 static void
3551 treeview_style_set(GtkWidget *widget, 3094 treeview_style_set(GtkWidget *widget,
3731 #endif /* USE_VV */ 3274 #endif /* USE_VV */
3732 } 3275 }
3733 3276
3734 static void pidgin_blist_show(PurpleBuddyList *list) 3277 static void pidgin_blist_show(PurpleBuddyList *list)
3735 { 3278 {
3736 PidginBuddyListPrivate *priv;
3737 GSimpleActionGroup *action_group = NULL; 3279 GSimpleActionGroup *action_group = NULL;
3738 void *handle; 3280 void *handle;
3739 GtkTreeViewColumn *column; 3281 GtkTreeViewColumn *column;
3740 GtkWidget *sep; 3282 GtkWidget *sep;
3741 GtkEventController *key_controller = NULL; 3283 GtkEventController *key_controller = NULL;
3745 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible")); 3287 purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
3746 return; 3288 return;
3747 } 3289 }
3748 3290
3749 gtkblist = PIDGIN_BUDDY_LIST(list); 3291 gtkblist = PIDGIN_BUDDY_LIST(list);
3750 priv = pidgin_buddy_list_get_instance_private(gtkblist);
3751 3292
3752 gtkblist->window = pidgin_contact_list_window_new(); 3293 gtkblist->window = pidgin_contact_list_window_new();
3753 g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event", 3294 g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event",
3754 G_CALLBACK(blist_focus_cb), gtkblist); 3295 G_CALLBACK(blist_focus_cb), gtkblist);
3755 g_signal_connect(G_OBJECT(gtkblist->window), "focus-out-event", 3296 g_signal_connect(G_OBJECT(gtkblist->window), "focus-out-event",
3838 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sep, FALSE, FALSE, 0); 3379 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sep, FALSE, FALSE, 0);
3839 3380
3840 gtkblist->scrollbook = pidgin_scroll_book_new(); 3381 gtkblist->scrollbook = pidgin_scroll_book_new();
3841 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0); 3382 gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0);
3842 3383
3843 priv->error_scrollbook = PIDGIN_SCROLL_BOOK(pidgin_scroll_book_new());
3844 gtk_box_pack_start(GTK_BOX(gtkblist->vbox),
3845 GTK_WIDGET(priv->error_scrollbook), FALSE, FALSE, 0);
3846
3847 /* Update some dynamic things */ 3384 /* Update some dynamic things */
3848 pidgin_blist_update_sort_methods(); 3385 pidgin_blist_update_sort_methods();
3849 3386
3850 /* OK... let's show this bad boy. */ 3387 /* OK... let's show this bad boy. */
3851 pidgin_blist_refresh(list); 3388 pidgin_blist_refresh(list);
3860 /* sorting */ 3397 /* sorting */
3861 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/sort_type", 3398 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/sort_type",
3862 _prefs_change_sort_method, NULL); 3399 _prefs_change_sort_method, NULL);
3863 3400
3864 /* Setup some purple signal handlers. */ 3401 /* Setup some purple signal handlers. */
3865
3866 handle = purple_accounts_get_handle();
3867 purple_signal_connect(handle, "account-error-changed", gtkblist,
3868 G_CALLBACK(update_account_error_state),
3869 gtkblist);
3870 3402
3871 handle = purple_conversations_get_handle(); 3403 handle = purple_conversations_get_handle();
3872 purple_signal_connect(handle, "conversation-updated", gtkblist, 3404 purple_signal_connect(handle, "conversation-updated", gtkblist,
3873 G_CALLBACK(conversation_updated_cb), 3405 G_CALLBACK(conversation_updated_cb),
3874 gtkblist); 3406 gtkblist);
3879 G_CALLBACK(conversation_created_cb), 3411 G_CALLBACK(conversation_created_cb),
3880 gtkblist); 3412 gtkblist);
3881 purple_signal_connect(handle, "chat-joined", gtkblist, 3413 purple_signal_connect(handle, "chat-joined", gtkblist,
3882 G_CALLBACK(conversation_created_cb), 3414 G_CALLBACK(conversation_created_cb),
3883 gtkblist); 3415 gtkblist);
3884
3885 show_initial_account_errors(gtkblist);
3886 3416
3887 /* emit our created signal */ 3417 /* emit our created signal */
3888 handle = pidgin_blist_get_handle(); 3418 handle = pidgin_blist_get_handle();
3889 purple_signal_emit(handle, "gtkblist-created", list); 3419 purple_signal_emit(handle, "gtkblist-created", list);
3890 3420

mercurial