/* * purple * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "group.h" #include "debug.h" #include "purpleprivate.h" #include "purpleprotocolserver.h" typedef struct _PurpleGroupPrivate PurpleGroupPrivate; /****************************************************************************** * Private Data *****************************************************************************/ struct _PurpleGroupPrivate { char *name; /* The name of this group. */ gboolean is_constructed; /* Indicates if the group has finished being constructed. */ }; /* Group property enums */ enum { GROUP_PROP_0, GROUP_PROP_NAME, GROUP_PROP_LAST }; /****************************************************************************** * Globals *****************************************************************************/ static GParamSpec *properties[GROUP_PROP_LAST]; G_DEFINE_TYPE_WITH_PRIVATE(PurpleGroup, purple_group, PURPLE_TYPE_COUNTING_NODE); /****************************************************************************** * Group API *****************************************************************************/ GSList *purple_group_get_accounts(PurpleGroup *group) { GSList *l = NULL; PurpleBlistNode *gnode, *cnode, *bnode; gnode = (PurpleBlistNode *)group; for (cnode = gnode->child; cnode; cnode = cnode->next) { if (PURPLE_IS_CHAT(cnode)) { if (!g_slist_find(l, purple_chat_get_account(PURPLE_CHAT(cnode)))) l = g_slist_append(l, purple_chat_get_account(PURPLE_CHAT(cnode))); } else if (PURPLE_IS_META_CONTACT(cnode)) { for (bnode = cnode->child; bnode; bnode = bnode->next) { if (PURPLE_IS_BUDDY(bnode)) { if (!g_slist_find(l, purple_buddy_get_account(PURPLE_BUDDY(bnode)))) l = g_slist_append(l, purple_buddy_get_account(PURPLE_BUDDY(bnode))); } } } } return l; } gboolean purple_group_on_account(PurpleGroup *g, PurpleAccount *account) { PurpleBlistNode *cnode; for (cnode = ((PurpleBlistNode *)g)->child; cnode; cnode = cnode->next) { if (PURPLE_IS_META_CONTACT(cnode)) { if(purple_meta_contact_on_account((PurpleMetaContact *) cnode, account)) return TRUE; } else if (PURPLE_IS_CHAT(cnode)) { PurpleChat *chat = (PurpleChat *)cnode; if ((!account && purple_account_is_connected(purple_chat_get_account(chat))) || purple_chat_get_account(chat) == account) return TRUE; } } return FALSE; } /* * TODO: If merging, prompt the user if they want to merge. */ void purple_group_set_name(PurpleGroup *source, const char *name) { PurpleGroupPrivate *priv = NULL; PurpleGroup *dest; gchar *old_name; gchar *new_name; GList *moved_buddies = NULL; GSList *accts; g_return_if_fail(PURPLE_IS_GROUP(source)); g_return_if_fail(name != NULL); priv = purple_group_get_instance_private(source); new_name = purple_utf8_strip_unprintables(name); if (*new_name == '\0' || purple_strequal(new_name, priv->name)) { g_free(new_name); return; } dest = purple_blist_find_group(new_name); if (dest != NULL && purple_utf8_strcasecmp(priv->name, purple_group_get_name(dest)) != 0) { /* We're merging two groups */ PurpleBlistNode *prev, *child, *next; prev = _purple_blist_get_last_child((PurpleBlistNode*)dest); child = PURPLE_BLIST_NODE(source)->child; /* * TODO: This seems like a dumb way to do this... why not just * append all children from the old group to the end of the new * one? Protocols might be expecting to receive an add_buddy() for * each moved buddy... */ while (child) { next = child->next; if (PURPLE_IS_META_CONTACT(child)) { PurpleBlistNode *bnode; purple_blist_add_contact((PurpleMetaContact *)child, dest, prev); for (bnode = child->child; bnode != NULL; bnode = bnode->next) { purple_blist_add_buddy((PurpleBuddy *)bnode, (PurpleMetaContact *)child, NULL, bnode->prev); moved_buddies = g_list_append(moved_buddies, bnode); } prev = child; } else if (PURPLE_IS_CHAT(child)) { purple_blist_add_chat((PurpleChat *)child, dest, prev); prev = child; } else { purple_debug_error("blistnodetypes", "Unknown child type in group %s", priv->name); } child = next; } /* Make a copy of the old group name and then delete the old group */ old_name = g_strdup(priv->name); purple_blist_remove_group(source); source = dest; g_free(new_name); } else { /* A simple rename */ PurpleBlistNode *cnode, *bnode; /* Build a GList of all buddies in this group */ for (cnode = PURPLE_BLIST_NODE(source)->child; cnode != NULL; cnode = cnode->next) { if (PURPLE_IS_META_CONTACT(cnode)) for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) moved_buddies = g_list_append(moved_buddies, bnode); } purple_blist_update_groups_cache(source, new_name); old_name = priv->name; priv->name = new_name; g_object_notify_by_pspec(G_OBJECT(source), properties[GROUP_PROP_NAME]); } /* Save our changes */ purple_blist_save_node(purple_blist_get_default(), PURPLE_BLIST_NODE(source)); /* Update the UI */ purple_blist_update_node(purple_blist_get_default(), PURPLE_BLIST_NODE(source)); /* Notify all protocols */ /* TODO: Is this condition needed? Seems like it would always be TRUE */ if(old_name && !purple_strequal(priv->name, old_name)) { for (accts = purple_group_get_accounts(source); accts; accts = g_slist_delete_link(accts, accts)) { PurpleAccount *account = accts->data; PurpleConnection *gc = NULL; PurpleProtocol *protocol = NULL; GList *l = NULL, *buddies = NULL; gc = purple_account_get_connection(account); if(gc) protocol = purple_connection_get_protocol(gc); if(!protocol) continue; for(l = moved_buddies; l; l = l->next) { PurpleBuddy *buddy = PURPLE_BUDDY(l->data); if(buddy && purple_buddy_get_account(buddy) == account) buddies = g_list_append(buddies, (PurpleBlistNode *)buddy); } if(PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER, rename_group)) { purple_protocol_server_rename_group(PURPLE_PROTOCOL_SERVER(protocol), gc, old_name, source, buddies); } else { GList *cur, *groups = NULL; /* Make a list of what the groups each buddy is in */ for(cur = buddies; cur; cur = cur->next) { PurpleBlistNode *node = (PurpleBlistNode *)cur->data; groups = g_list_prepend(groups, node->parent->parent); } purple_account_remove_buddies(account, buddies, groups); g_list_free(groups); purple_account_add_buddies(account, buddies, NULL); } g_list_free(buddies); } } g_list_free(moved_buddies); g_free(old_name); g_object_notify_by_pspec(G_OBJECT(source), properties[GROUP_PROP_NAME]); } const char *purple_group_get_name(PurpleGroup *group) { PurpleGroupPrivate *priv = NULL; g_return_val_if_fail(PURPLE_IS_GROUP(group), NULL); priv = purple_group_get_instance_private(group); return priv->name; } /****************************************************************************** * GObject Stuff *****************************************************************************/ /* Set method for GObject properties */ static void purple_group_set_property(GObject *obj, guint param_id, const GValue *value, GParamSpec *pspec) { PurpleGroup *group = PURPLE_GROUP(obj); PurpleGroupPrivate *priv = purple_group_get_instance_private(group); switch (param_id) { case GROUP_PROP_NAME: if (priv->is_constructed) purple_group_set_name(group, g_value_get_string(value)); else priv->name = purple_utf8_strip_unprintables(g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); break; } } /* Get method for GObject properties */ static void purple_group_get_property(GObject *obj, guint param_id, GValue *value, GParamSpec *pspec) { PurpleGroup *group = PURPLE_GROUP(obj); switch (param_id) { case GROUP_PROP_NAME: g_value_set_string(value, purple_group_get_name(group)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); break; } } /* GObject initialization function */ static void purple_group_init(G_GNUC_UNUSED PurpleGroup *group) { } /* Called when done constructing */ static void purple_group_constructed(GObject *object) { PurpleGroup *group = PURPLE_GROUP(object); PurpleGroupPrivate *priv = purple_group_get_instance_private(group); G_OBJECT_CLASS(purple_group_parent_class)->constructed(object); purple_blist_new_node(purple_blist_get_default(), PURPLE_BLIST_NODE(group)); priv->is_constructed = TRUE; } /* GObject finalize function */ static void purple_group_finalize(GObject *object) { PurpleGroupPrivate *priv = purple_group_get_instance_private( PURPLE_GROUP(object)); g_free(priv->name); G_OBJECT_CLASS(purple_group_parent_class)->finalize(object); } /* Class initializer function */ static void purple_group_class_init(PurpleGroupClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); obj_class->finalize = purple_group_finalize; obj_class->constructed = purple_group_constructed; /* Setup properties */ obj_class->get_property = purple_group_get_property; obj_class->set_property = purple_group_set_property; properties[GROUP_PROP_NAME] = g_param_spec_string( "name", "Name", "Name of the group.", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS ); g_object_class_install_properties(obj_class, GROUP_PROP_LAST, properties); } PurpleGroup * purple_group_new(const char *name) { PurpleGroup *group; if (name == NULL || name[0] == '\0') name = PURPLE_BLIST_DEFAULT_GROUP_NAME; if (g_strcmp0(name, "Buddies") == 0) name = PURPLE_BLIST_DEFAULT_GROUP_NAME; if (g_strcmp0(name, _purple_blist_get_localized_default_group_name()) == 0) name = PURPLE_BLIST_DEFAULT_GROUP_NAME; group = purple_blist_find_group(name); if (group != NULL) return group; return g_object_new(PURPLE_TYPE_GROUP, "name", name, NULL); }