Merged in default (pull request #594)
[pidgin-git.git] / libpurple / buddy.c
blob64839a75e8ea7c3efb84b03ddb7b6084bb0b3993
1 /*
2 * purple
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "internal.h"
24 #include "glibcompat.h"
25 #include "util.h"
27 typedef struct _PurpleBuddyPrivate PurpleBuddyPrivate;
29 struct _PurpleBuddyPrivate {
30 char *name; /* The name of the buddy. */
31 char *local_alias; /* The user-set alias of the buddy */
32 char *server_alias; /* The server-specified alias of the buddy.
33 (i.e. MSN "Friendly Names") */
34 void *proto_data; /* This allows the protocol to associate
35 whatever data it wants with a buddy. */
36 PurpleBuddyIcon *icon; /* The buddy icon. */
37 PurpleAccount *account; /* the account this buddy belongs to */
38 PurplePresence *presence; /* Presense information of the buddy */
39 PurpleMediaCaps media_caps; /* The media capabilities of the buddy. */
41 gboolean is_constructed; /* Indicates if the buddy has finished
42 being constructed. */
45 enum {
46 PROP_0,
47 PROP_NAME,
48 PROP_LOCAL_ALIAS,
49 PROP_SERVER_ALIAS,
50 PROP_ICON,
51 PROP_ACCOUNT,
52 PROP_PRESENCE,
53 PROP_MEDIA_CAPS,
54 PROP_LAST
57 /******************************************************************************
58 * Globals
59 *****************************************************************************/
60 static GParamSpec *properties[PROP_LAST];
62 G_DEFINE_TYPE_WITH_PRIVATE(PurpleBuddy, purple_buddy, PURPLE_TYPE_BLIST_NODE)
64 /******************************************************************************
65 * API
66 *****************************************************************************/
67 void
68 purple_buddy_set_icon(PurpleBuddy *buddy, PurpleBuddyIcon *icon)
70 PurpleBuddyPrivate *priv = NULL;
72 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
74 priv = purple_buddy_get_instance_private(buddy);
76 if (priv->icon != icon)
78 purple_buddy_icon_unref(priv->icon);
79 priv->icon = (icon != NULL ? purple_buddy_icon_ref(icon) : NULL);
81 g_object_notify_by_pspec(G_OBJECT(buddy),
82 properties[PROP_ICON]);
85 purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy);
87 purple_blist_update_node(purple_blist_get_default(),
88 PURPLE_BLIST_NODE(buddy));
91 PurpleBuddyIcon *
92 purple_buddy_get_icon(PurpleBuddy *buddy)
94 PurpleBuddyPrivate *priv = NULL;
96 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
98 priv = purple_buddy_get_instance_private(buddy);
99 return priv->icon;
102 PurpleAccount *
103 purple_buddy_get_account(PurpleBuddy *buddy)
105 PurpleBuddyPrivate *priv = NULL;
107 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
109 priv = purple_buddy_get_instance_private(buddy);
110 return priv->account;
113 void
114 purple_buddy_set_name(PurpleBuddy *buddy, const char *name)
116 PurpleBuddyPrivate *priv = NULL;
118 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
120 priv = purple_buddy_get_instance_private(buddy);
122 purple_blist_update_buddies_cache(buddy, name);
124 g_free(priv->name);
125 priv->name = purple_utf8_strip_unprintables(name);
127 g_object_notify_by_pspec(G_OBJECT(buddy), properties[PROP_NAME]);
129 purple_blist_save_node(purple_blist_get_default(),
130 PURPLE_BLIST_NODE(buddy));
131 purple_blist_update_node(purple_blist_get_default(),
132 PURPLE_BLIST_NODE(buddy));
135 const char *
136 purple_buddy_get_name(PurpleBuddy *buddy)
138 PurpleBuddyPrivate *priv = NULL;
140 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
142 priv = purple_buddy_get_instance_private(buddy);
143 return priv->name;
146 gpointer
147 purple_buddy_get_protocol_data(PurpleBuddy *buddy)
149 PurpleBuddyPrivate *priv = NULL;
151 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
153 priv = purple_buddy_get_instance_private(buddy);
154 return priv->proto_data;
157 void
158 purple_buddy_set_protocol_data(PurpleBuddy *buddy, gpointer data)
160 PurpleBuddyPrivate *priv = NULL;
162 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
164 priv = purple_buddy_get_instance_private(buddy);
165 priv->proto_data = data;
168 const char *purple_buddy_get_alias_only(PurpleBuddy *buddy)
170 PurpleBuddyPrivate *priv = NULL;
172 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
174 priv = purple_buddy_get_instance_private(buddy);
176 if ((priv->local_alias != NULL) && (*priv->local_alias != '\0')) {
177 return priv->local_alias;
178 } else if ((priv->server_alias != NULL) &&
179 (*priv->server_alias != '\0')) {
181 return priv->server_alias;
184 return NULL;
187 const char *purple_buddy_get_contact_alias(PurpleBuddy *buddy)
189 PurpleBuddyPrivate *priv = NULL;
190 PurpleContact *c;
192 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
194 priv = purple_buddy_get_instance_private(buddy);
196 /* Search for an alias for the buddy. In order of precedence: */
197 /* The local buddy alias */
198 if (priv->local_alias != NULL)
199 return priv->local_alias;
201 /* The contact alias */
202 c = purple_buddy_get_contact(buddy);
203 if ((c != NULL) && (purple_contact_get_alias(c) != NULL))
204 return purple_contact_get_alias(c);
206 /* The server alias */
207 if ((priv->server_alias) && (*priv->server_alias))
208 return priv->server_alias;
210 /* The buddy's user name (i.e. no alias) */
211 return priv->name;
214 const char *purple_buddy_get_alias(PurpleBuddy *buddy)
216 PurpleBuddyPrivate *priv = NULL;
218 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
220 priv = purple_buddy_get_instance_private(buddy);
222 /* Search for an alias for the buddy. In order of precedence: */
223 /* The buddy alias */
224 if (priv->local_alias != NULL)
225 return priv->local_alias;
227 /* The server alias */
228 if ((priv->server_alias) && (*priv->server_alias))
229 return priv->server_alias;
231 /* The buddy's user name (i.e. no alias) */
232 return priv->name;
235 void
236 purple_buddy_set_local_alias(PurpleBuddy *buddy, const char *alias)
238 PurpleBuddyPrivate *priv = NULL;
239 PurpleIMConversation *im;
240 char *old_alias;
241 char *new_alias = NULL;
243 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
245 priv = purple_buddy_get_instance_private(buddy);
247 if ((alias != NULL) && (*alias != '\0'))
248 new_alias = purple_utf8_strip_unprintables(alias);
250 if (purple_strequal(priv->local_alias, new_alias)) {
251 g_free(new_alias);
252 return;
255 old_alias = priv->local_alias;
257 if ((new_alias != NULL) && (*new_alias != '\0'))
258 priv->local_alias = new_alias;
259 else {
260 priv->local_alias = NULL;
261 g_free(new_alias); /* could be "\0" */
264 g_object_notify_by_pspec(G_OBJECT(buddy),
265 properties[PROP_LOCAL_ALIAS]);
267 purple_blist_save_node(purple_blist_get_default(),
268 PURPLE_BLIST_NODE(buddy));
269 purple_blist_update_node(purple_blist_get_default(),
270 PURPLE_BLIST_NODE(buddy));
272 im = purple_conversations_find_im_with_account(priv->name,
273 priv->account);
274 if (im)
275 purple_conversation_autoset_title(PURPLE_CONVERSATION(im));
277 purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
278 buddy, old_alias);
279 g_free(old_alias);
282 const char *purple_buddy_get_local_alias(PurpleBuddy *buddy)
284 PurpleBuddyPrivate *priv = NULL;
286 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
288 priv = purple_buddy_get_instance_private(buddy);
289 return priv->local_alias;
292 void
293 purple_buddy_set_server_alias(PurpleBuddy *buddy, const char *alias)
295 PurpleBuddyPrivate *priv = NULL;
296 PurpleIMConversation *im;
297 char *old_alias;
298 char *new_alias = NULL;
300 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
302 priv = purple_buddy_get_instance_private(buddy);
304 if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL))
305 new_alias = purple_utf8_strip_unprintables(alias);
307 if (purple_strequal(priv->server_alias, new_alias)) {
308 g_free(new_alias);
309 return;
312 old_alias = priv->server_alias;
314 if ((new_alias != NULL) && (*new_alias != '\0'))
315 priv->server_alias = new_alias;
316 else {
317 priv->server_alias = NULL;
318 g_free(new_alias); /* could be "\0"; */
321 g_object_notify_by_pspec(G_OBJECT(buddy),
322 properties[PROP_SERVER_ALIAS]);
324 purple_blist_save_node(purple_blist_get_default(),
325 PURPLE_BLIST_NODE(buddy));
326 purple_blist_update_node(purple_blist_get_default(),
327 PURPLE_BLIST_NODE(buddy));
329 im = purple_conversations_find_im_with_account(priv->name,
330 priv->account);
331 if (im)
332 purple_conversation_autoset_title(PURPLE_CONVERSATION(im));
334 purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
335 buddy, old_alias);
336 g_free(old_alias);
339 const char *purple_buddy_get_server_alias(PurpleBuddy *buddy)
341 PurpleBuddyPrivate *priv = NULL;
343 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
345 priv = purple_buddy_get_instance_private(buddy);
347 if ((priv->server_alias) && (*priv->server_alias))
348 return priv->server_alias;
350 return NULL;
353 PurpleContact *purple_buddy_get_contact(PurpleBuddy *buddy)
355 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
357 return PURPLE_CONTACT(PURPLE_BLIST_NODE(buddy)->parent);
360 PurplePresence *purple_buddy_get_presence(PurpleBuddy *buddy)
362 PurpleBuddyPrivate *priv = NULL;
364 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
366 priv = purple_buddy_get_instance_private(buddy);
367 return priv->presence;
370 void
371 purple_buddy_update_status(PurpleBuddy *buddy, PurpleStatus *old_status)
373 PurpleBuddyPrivate *priv = NULL;
374 PurpleStatus *status;
375 PurpleBlistNode *cnode;
376 PurpleContact *contact;
377 PurpleCountingNode *contact_counter, *group_counter;
379 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
381 priv = purple_buddy_get_instance_private(buddy);
383 status = purple_presence_get_active_status(priv->presence);
385 purple_debug_info("blistnodetypes", "Updating buddy status for %s (%s)\n",
386 priv->name, purple_account_get_protocol_name(priv->account));
388 if (purple_status_is_online(status) &&
389 !purple_status_is_online(old_status)) {
391 purple_signal_emit(purple_blist_get_handle(), "buddy-signed-on", buddy);
393 cnode = PURPLE_BLIST_NODE(buddy)->parent;
394 contact = PURPLE_CONTACT(cnode);
395 contact_counter = PURPLE_COUNTING_NODE(contact);
396 group_counter = PURPLE_COUNTING_NODE(cnode->parent);
398 purple_counting_node_change_online_count(contact_counter, +1);
399 if (purple_counting_node_get_online_count(contact_counter) == 1)
400 purple_counting_node_change_online_count(group_counter, +1);
401 } else if (!purple_status_is_online(status) &&
402 purple_status_is_online(old_status)) {
404 purple_blist_node_set_int(PURPLE_BLIST_NODE(buddy), "last_seen", time(NULL));
405 purple_signal_emit(purple_blist_get_handle(), "buddy-signed-off", buddy);
407 cnode = PURPLE_BLIST_NODE(buddy)->parent;
408 contact = PURPLE_CONTACT(cnode);
409 contact_counter = PURPLE_COUNTING_NODE(contact);
410 group_counter = PURPLE_COUNTING_NODE(cnode->parent);
412 purple_counting_node_change_online_count(contact_counter, -1);
413 if (purple_counting_node_get_online_count(contact_counter) == 0)
414 purple_counting_node_change_online_count(group_counter, -1);
415 } else {
416 purple_signal_emit(purple_blist_get_handle(),
417 "buddy-status-changed", buddy, old_status,
418 status);
422 * This function used to only call the following two functions if one of
423 * the above signals had been triggered, but that's not good, because
424 * if someone's away message changes and they don't go from away to back
425 * to away then no signal is triggered.
427 * It's a safe assumption that SOMETHING called this function. PROBABLY
428 * because something, somewhere changed. Calling the stuff below
429 * certainly won't hurt anything. Unless you're on a K6-2 300.
431 purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
433 purple_blist_update_node(purple_blist_get_default(),
434 PURPLE_BLIST_NODE(buddy));
437 PurpleMediaCaps purple_buddy_get_media_caps(PurpleBuddy *buddy)
439 PurpleBuddyPrivate *priv = NULL;
441 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), 0);
443 priv = purple_buddy_get_instance_private(buddy);
444 return priv->media_caps;
447 void purple_buddy_set_media_caps(PurpleBuddy *buddy, PurpleMediaCaps media_caps)
449 PurpleBuddyPrivate *priv = NULL;
451 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
453 priv = purple_buddy_get_instance_private(buddy);
454 priv->media_caps = media_caps;
456 g_object_notify_by_pspec(G_OBJECT(buddy),
457 properties[PROP_MEDIA_CAPS]);
460 PurpleGroup *purple_buddy_get_group(PurpleBuddy *buddy)
462 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
464 if (PURPLE_BLIST_NODE(buddy)->parent == NULL)
465 return purple_blist_get_default_group();
467 return PURPLE_GROUP(PURPLE_BLIST_NODE(buddy)->parent->parent);
470 /******************************************************************************
471 * GObject Stuff
472 *****************************************************************************/
473 static void
474 purple_buddy_set_property(GObject *obj, guint param_id, const GValue *value,
475 GParamSpec *pspec)
477 PurpleBuddy *buddy = PURPLE_BUDDY(obj);
478 PurpleBuddyPrivate *priv = purple_buddy_get_instance_private(buddy);
480 switch (param_id) {
481 case PROP_NAME:
482 if (priv->is_constructed)
483 purple_buddy_set_name(buddy, g_value_get_string(value));
484 else
485 priv->name =
486 purple_utf8_strip_unprintables(g_value_get_string(value));
487 break;
488 case PROP_LOCAL_ALIAS:
489 if (priv->is_constructed)
490 purple_buddy_set_local_alias(buddy, g_value_get_string(value));
491 else
492 priv->local_alias =
493 purple_utf8_strip_unprintables(g_value_get_string(value));
494 break;
495 case PROP_SERVER_ALIAS:
496 purple_buddy_set_server_alias(buddy, g_value_get_string(value));
497 break;
498 case PROP_ICON:
499 purple_buddy_set_icon(buddy, g_value_get_pointer(value));
500 break;
501 case PROP_ACCOUNT:
502 priv->account = g_value_get_object(value);
503 break;
504 case PROP_MEDIA_CAPS:
505 purple_buddy_set_media_caps(buddy, g_value_get_enum(value));
506 break;
507 default:
508 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
509 break;
513 static void
514 purple_buddy_get_property(GObject *obj, guint param_id, GValue *value,
515 GParamSpec *pspec)
517 PurpleBuddy *buddy = PURPLE_BUDDY(obj);
519 switch (param_id) {
520 case PROP_NAME:
521 g_value_set_string(value, purple_buddy_get_name(buddy));
522 break;
523 case PROP_LOCAL_ALIAS:
524 g_value_set_string(value, purple_buddy_get_local_alias(buddy));
525 break;
526 case PROP_SERVER_ALIAS:
527 g_value_set_string(value, purple_buddy_get_server_alias(buddy));
528 break;
529 case PROP_ICON:
530 g_value_set_pointer(value, purple_buddy_get_icon(buddy));
531 break;
532 case PROP_ACCOUNT:
533 g_value_set_object(value, purple_buddy_get_account(buddy));
534 break;
535 case PROP_PRESENCE:
536 g_value_set_object(value, purple_buddy_get_presence(buddy));
537 break;
538 case PROP_MEDIA_CAPS:
539 g_value_set_enum(value, purple_buddy_get_media_caps(buddy));
540 break;
541 default:
542 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
543 break;
547 static void
548 purple_buddy_init(PurpleBuddy *buddy)
552 static void
553 purple_buddy_constructed(GObject *object) {
554 PurpleBuddy *buddy = PURPLE_BUDDY(object);
555 PurpleBuddyPrivate *priv = purple_buddy_get_instance_private(buddy);
557 G_OBJECT_CLASS(purple_buddy_parent_class)->constructed(object);
559 priv->presence = PURPLE_PRESENCE(purple_buddy_presence_new(buddy));
560 purple_presence_set_status_active(priv->presence, "offline", TRUE);
562 purple_blist_new_node(purple_blist_get_default(),
563 PURPLE_BLIST_NODE(buddy));
565 priv->is_constructed = TRUE;
568 static void
569 purple_buddy_dispose(GObject *object) {
570 PurpleBuddyPrivate *priv = purple_buddy_get_instance_private(PURPLE_BUDDY(object));
572 if (priv->icon) {
573 purple_buddy_icon_unref(priv->icon);
574 priv->icon = NULL;
577 if (priv->presence) {
578 g_object_unref(priv->presence);
579 priv->presence = NULL;
582 G_OBJECT_CLASS(purple_buddy_parent_class)->dispose(object);
585 static void
586 purple_buddy_finalize(GObject *object) {
587 PurpleBuddy *buddy = PURPLE_BUDDY(object);
588 PurpleBuddyPrivate *priv = purple_buddy_get_instance_private(buddy);
589 PurpleProtocol *protocol;
592 * Tell the owner protocol that we're about to free the buddy so it
593 * can free proto_data
595 protocol = purple_protocols_find(purple_account_get_protocol_id(priv->account));
596 if (protocol)
597 purple_protocol_client_iface_buddy_free(protocol, buddy);
599 g_free(priv->name);
600 g_free(priv->local_alias);
601 g_free(priv->server_alias);
603 G_OBJECT_CLASS(purple_buddy_parent_class)->finalize(object);
606 static void purple_buddy_class_init(PurpleBuddyClass *klass) {
607 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
609 obj_class->dispose = purple_buddy_dispose;
610 obj_class->finalize = purple_buddy_finalize;
612 /* Setup properties */
613 obj_class->get_property = purple_buddy_get_property;
614 obj_class->set_property = purple_buddy_set_property;
615 obj_class->constructed = purple_buddy_constructed;
617 properties[PROP_NAME] = g_param_spec_string(
618 "name",
619 "Name",
620 "The name of the buddy.",
621 NULL,
622 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS
625 properties[PROP_LOCAL_ALIAS] = g_param_spec_string(
626 "local-alias",
627 "Local alias",
628 "Local alias of thee buddy.",
629 NULL,
630 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS
633 properties[PROP_SERVER_ALIAS] = g_param_spec_string(
634 "server-alias",
635 "Server alias",
636 "Server-side alias of the buddy.",
637 NULL,
638 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
641 properties[PROP_ICON] = g_param_spec_pointer(
642 "icon",
643 "Buddy icon",
644 "The icon for the buddy.",
645 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
648 properties[PROP_ACCOUNT] = g_param_spec_object(
649 "account",
650 "Account",
651 "The account for the buddy.",
652 PURPLE_TYPE_ACCOUNT,
653 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS
656 properties[PROP_PRESENCE] = g_param_spec_object(
657 "presence",
658 "Presence",
659 "The status information for the buddy.",
660 PURPLE_TYPE_PRESENCE,
661 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS
664 properties[PROP_MEDIA_CAPS] = g_param_spec_enum(
665 "media-caps",
666 "Media capabilities",
667 "The media capabilities of the buddy.",
668 PURPLE_MEDIA_TYPE_CAPS, PURPLE_MEDIA_CAPS_NONE,
669 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
672 g_object_class_install_properties(obj_class, PROP_LAST, properties);
675 PurpleBuddy *
676 purple_buddy_new(PurpleAccount *account, const char *name, const char *alias)
678 g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
679 g_return_val_if_fail(name != NULL, NULL);
681 return g_object_new(PURPLE_TYPE_BUDDY,
682 "account", account,
683 "name", name,
684 "local-alias", alias,
685 NULL);