pidgin/pidgin

Add some new methods to purple tags
default tip
45 hours ago, Gary Kramlich
b45add2a840c
Add some new methods to purple tags

* purple_tags_exists is a simplier version of purple_tags_lookup.
* purple_tags_contains makes it easier to find multiple matching tags.

Testing Done:
Ran the unit tests under valgrind and had the turtles check in on things too.

Reviewed at https://reviews.imfreedom.org/r/3143/
/*
* Purple - Internet Messaging Library
* Copyright (C) Pidgin Developers <devel@pidgin.im>
*
* 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 library 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 library 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 library; if not, see <https://www.gnu.org/licenses/>.
*/
#include "purpleperson.h"
#include "util.h"
#include "util.h"
struct _PurplePerson {
GObject parent;
char *id;
gchar *alias;
PurpleAvatar *avatar;
char *color;
PurpleTags *tags;
GPtrArray *contacts;
};
enum {
PROP_0,
PROP_ITEM_TYPE,
PROP_N_ITEMS,
PROP_ID,
PROP_ALIAS,
PROP_AVATAR,
PROP_AVATAR_FOR_DISPLAY,
PROP_COLOR,
PROP_COLOR_FOR_DISPLAY,
PROP_TAGS,
PROP_NAME_FOR_DISPLAY,
PROP_PRIORITY_CONTACT_INFO,
N_PROPERTIES,
};
static GParamSpec *properties[N_PROPERTIES] = {NULL, };
static void
purple_person_priority_contact_info_notify_cb(GObject *obj,
G_GNUC_UNUSED GParamSpec *pspec,
gpointer data);
/******************************************************************************
* Helpers
*****************************************************************************/
static void
purple_person_set_id(PurplePerson *person, const char *id) {
g_return_if_fail(PURPLE_IS_PERSON(person));
g_free(person->id);
if(id != NULL) {
person->id = g_strdup(id);
} else {
person->id = g_uuid_string_random();
}
g_object_notify_by_pspec(G_OBJECT(person), properties[PROP_ID]);
}
static gint
purple_person_contact_compare(gconstpointer a, gconstpointer b) {
PurpleContactInfo *c1 = *(PurpleContactInfo **)a;
PurpleContactInfo *c2 = *(PurpleContactInfo **)b;
PurplePresence *p1 = NULL;
PurplePresence *p2 = NULL;
p1 = purple_contact_info_get_presence(c1);
p2 = purple_contact_info_get_presence(c2);
return purple_presence_compare(p1, p2);
}
static void
purple_person_sort_contacts(PurplePerson *person,
PurpleContactInfo *original_priority)
{
PurpleContactInfo *new_priority = NULL;
guint n_items = person->contacts->len;
g_ptr_array_sort(person->contacts, purple_person_contact_compare);
/* Tell the list we update our stuff. */
g_list_model_items_changed(G_LIST_MODEL(person), 0, n_items, n_items);
/* See if the priority contact changed. */
new_priority = g_ptr_array_index(person->contacts, 0);
if(original_priority != new_priority) {
PurpleAvatar *old_avatar = NULL;
PurpleAvatar *new_avatar = NULL;
GObject *obj = G_OBJECT(person);
const char *old_color = NULL;
const char *new_color = NULL;
if(PURPLE_IS_CONTACT_INFO(original_priority)) {
old_avatar = purple_contact_info_get_avatar(original_priority);
old_color = purple_contact_info_get_color(original_priority);
g_signal_handlers_disconnect_by_func(original_priority,
purple_person_priority_contact_info_notify_cb,
person);
}
if(PURPLE_IS_CONTACT_INFO(new_priority)) {
new_avatar = purple_contact_info_get_avatar(new_priority);
new_color = purple_contact_info_get_color(new_priority);
g_signal_connect_object(new_priority, "notify",
G_CALLBACK(purple_person_priority_contact_info_notify_cb),
person, 0);
}
g_object_freeze_notify(obj);
g_object_notify_by_pspec(obj, properties[PROP_NAME_FOR_DISPLAY]);
g_object_notify_by_pspec(obj, properties[PROP_PRIORITY_CONTACT_INFO]);
/* If the color isn't overridden by the person, check if it has
* changed.
*/
if(purple_strempty(person->color)) {
if(!purple_strequal(old_color, new_color)) {
g_object_notify_by_pspec(obj,
properties[PROP_COLOR_FOR_DISPLAY]);
}
}
/* If the person doesn't have an avatar set, check if the avatar
* changed and notify if it has.
*/
if(!PURPLE_IS_AVATAR(person->avatar)) {
if(old_avatar != new_avatar) {
g_object_notify_by_pspec(obj, properties[PROP_AVATAR_FOR_DISPLAY]);
}
}
g_object_thaw_notify(obj);
}
}
/* This function is used by purple_person_matches to determine if a contact info
* matches the needle.
*/
static gboolean
purple_person_matches_find_func(gconstpointer a, gconstpointer b) {
PurpleContactInfo *info = (gpointer)a;
const char *needle = b;
return purple_contact_info_matches(info, needle);
}
/******************************************************************************
* Callbacks
*****************************************************************************/
static void
purple_person_priority_contact_info_notify_cb(G_GNUC_UNUSED GObject *obj,
GParamSpec *pspec,
gpointer data)
{
PurplePerson *person = data;
const char *property = NULL;
property = g_param_spec_get_name(pspec);
if(purple_strequal(property, "name-for-display")) {
g_object_notify_by_pspec(G_OBJECT(person),
properties[PROP_NAME_FOR_DISPLAY]);
} else if(purple_strequal(property, "avatar")) {
g_object_notify_by_pspec(G_OBJECT(person),
properties[PROP_AVATAR_FOR_DISPLAY]);
} else if(purple_strequal(property, "color")) {
g_object_notify_by_pspec(G_OBJECT(person),
properties[PROP_COLOR_FOR_DISPLAY]);
}
}
static void
purple_person_presence_notify_cb(G_GNUC_UNUSED GObject *obj,
G_GNUC_UNUSED GParamSpec *pspec,
gpointer data)
{
PurplePerson *person = data;
PurpleContactInfo *current_priority = NULL;
current_priority = purple_person_get_priority_contact_info(person);
purple_person_sort_contacts(person, current_priority);
}
/******************************************************************************
* GListModel Implementation
*****************************************************************************/
static GType
purple_person_get_item_type(G_GNUC_UNUSED GListModel *list) {
return PURPLE_TYPE_CONTACT_INFO;
}
static guint
purple_person_get_n_items(GListModel *list) {
PurplePerson *person = PURPLE_PERSON(list);
return person->contacts->len;
}
static gpointer
purple_person_get_item(GListModel *list, guint position) {
PurplePerson *person = PURPLE_PERSON(list);
PurpleContactInfo *info = NULL;
if(position < person->contacts->len) {
info = g_ptr_array_index(person->contacts, position);
g_object_ref(info);
}
return info;
}
static void
purple_person_list_model_init(GListModelInterface *iface) {
iface->get_item_type = purple_person_get_item_type;
iface->get_n_items = purple_person_get_n_items;
iface->get_item = purple_person_get_item;
}
/******************************************************************************
* GObject Implementation
*****************************************************************************/
G_DEFINE_FINAL_TYPE_WITH_CODE(PurplePerson, purple_person, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL,
purple_person_list_model_init))
static void
purple_person_get_property(GObject *obj, guint param_id, GValue *value,
GParamSpec *pspec)
{
PurplePerson *person = PURPLE_PERSON(obj);
switch(param_id) {
case PROP_ITEM_TYPE:
g_value_set_gtype(value,
purple_person_get_item_type(G_LIST_MODEL(person)));
break;
case PROP_N_ITEMS:
g_value_set_uint(value,
purple_person_get_n_items(G_LIST_MODEL(person)));
break;
case PROP_ID:
g_value_set_string(value, purple_person_get_id(person));
break;
case PROP_ALIAS:
g_value_set_string(value, purple_person_get_alias(person));
break;
case PROP_AVATAR:
g_value_set_object(value, purple_person_get_avatar(person));
break;
case PROP_AVATAR_FOR_DISPLAY:
g_value_set_object(value,
purple_person_get_avatar_for_display(person));
break;
case PROP_COLOR:
g_value_set_string(value, purple_person_get_color(person));
break;
case PROP_COLOR_FOR_DISPLAY:
g_value_set_string(value, purple_person_get_color_for_display(person));
break;
case PROP_TAGS:
g_value_set_object(value, purple_person_get_tags(person));
break;
case PROP_NAME_FOR_DISPLAY:
g_value_set_string(value, purple_person_get_name_for_display(person));
break;
case PROP_PRIORITY_CONTACT_INFO:
g_value_set_object(value,
purple_person_get_priority_contact_info(person));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
}
}
static void
purple_person_set_property(GObject *obj, guint param_id, const GValue *value,
GParamSpec *pspec)
{
PurplePerson *person = PURPLE_PERSON(obj);
switch(param_id) {
case PROP_ID:
purple_person_set_id(person, g_value_get_string(value));
break;
case PROP_ALIAS:
purple_person_set_alias(person, g_value_get_string(value));
break;
case PROP_AVATAR:
purple_person_set_avatar(person, g_value_get_object(value));
break;
case PROP_COLOR:
purple_person_set_color(person, g_value_get_string(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
}
}
static void
purple_person_dispose(GObject *obj) {
PurplePerson *person = PURPLE_PERSON(obj);
g_clear_object(&person->avatar);
g_clear_object(&person->tags);
if(person->contacts != NULL) {
g_ptr_array_free(person->contacts, TRUE);
person->contacts = NULL;
}
G_OBJECT_CLASS(purple_person_parent_class)->dispose(obj);
}
static void
purple_person_finalize(GObject *obj) {
PurplePerson *person = PURPLE_PERSON(obj);
g_clear_pointer(&person->id, g_free);
g_clear_pointer(&person->alias, g_free);
g_clear_pointer(&person->color, g_free);
G_OBJECT_CLASS(purple_person_parent_class)->finalize(obj);
}
static void
purple_person_constructed(GObject *obj) {
PurplePerson *person = NULL;
G_OBJECT_CLASS(purple_person_parent_class)->constructed(obj);
person = PURPLE_PERSON(obj);
if(person->id == NULL) {
purple_person_set_id(person, NULL);
}
}
static void
purple_person_init(PurplePerson *person) {
person->tags = purple_tags_new();
person->contacts = g_ptr_array_new_full(0, (GDestroyNotify)g_object_unref);
}
static void
purple_person_class_init(PurplePersonClass *klass) {
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
obj_class->get_property = purple_person_get_property;
obj_class->set_property = purple_person_set_property;
obj_class->constructed = purple_person_constructed;
obj_class->dispose = purple_person_dispose;
obj_class->finalize = purple_person_finalize;
/**
* PurplePerson:item-type:
*
* The type of items. See [iface@Gio.ListModel.get_item_type].
*
* Since: 3.0
*/
properties[PROP_ITEM_TYPE] = g_param_spec_gtype(
"item-type", "item-type",
"The type of the contained items.",
G_TYPE_OBJECT,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
* PurplePerson:n-items:
*
* The number of items. See [iface@Gio.ListModel.get_n_items].
*
* Since: 3.0
*/
properties[PROP_N_ITEMS] = g_param_spec_uint(
"n-items", "n-items",
"The number of contained items.",
0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
* PurplePerson:id:
*
* The protocol specific id for the contact.
*
* Since: 3.0
*/
properties[PROP_ID] = g_param_spec_string(
"id", "id",
"The id of this contact",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
/**
* PurplePerson:alias:
*
* The alias for this person. This is controlled by the libpurple user.
*
* Since: 3.0
*/
properties[PROP_ALIAS] = g_param_spec_string(
"alias", "alias",
"The alias of this person.",
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
/**
* PurplePerson:avatar:
*
* The avatar for this person. This is controlled by the libpurple user,
* which they can use to set a custom avatar.
*
* Since: 3.0
*/
properties[PROP_AVATAR] = g_param_spec_object(
"avatar", "avatar",
"The avatar of this person",
PURPLE_TYPE_AVATAR,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
/**
* PurplePerson:avatar-for-display
*
* The avatar to show for the person. If [property@Purple.Person:avatar] is
* set, it will be returned. Otherwise the value of
* [property@Purple.ContactInfo:avatar] for
* [property@Purple.Person:priority-contact-info] will be returned.
*
* Since: 3.0
*/
properties[PROP_AVATAR_FOR_DISPLAY] = g_param_spec_object(
"avatar-for-display", "avatar-for-display",
"The avatar to display for this person",
PURPLE_TYPE_AVATAR,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
* PurplePerson:color:
*
* A custom color to use for this person which will override any colors for
* the contacts that belong to this person.
*
* This is an RGB hex code that user interfaces can use when rendering the
* person.
*
* Since: 3.0
*/
properties[PROP_COLOR] = g_param_spec_string(
"color", "color",
"The custom color for this person.",
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
/**
* PurplePerson:color-for-display:
*
* The color to use for this person.
*
* This will return the value of [property@Person:color] if it is set,
* otherwise it will return the value of [property@ContactInfo:color] of
* the priority contact info.
*
* Since: 3.0
*/
properties[PROP_COLOR_FOR_DISPLAY] = g_param_spec_string(
"color-for-display", "color-for-display",
"The color to use when displaying this person.",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
* PurplePerson:tags:
*
* The [class@Purple.Tags] for this person.
*
* Since: 3.0
*/
properties[PROP_TAGS] = g_param_spec_object(
"tags", "tags",
"The tags for this person",
PURPLE_TYPE_TAGS,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
* PurplePerson:name-for-display:
*
* The name that should be displayed for this person.
*
* If [property@Purple.Person:alias] is set that will be returned. If not
* the value of [method@Purple.ContactInfo.get_name_for_display] for
* [property@Purple.Person:priority-contact-info] will be used. If
* [property@Purple.Person:priority-contact-info] is %NULL, then %NULL will
* be returned.
*
* Since: 3.0
*/
properties[PROP_NAME_FOR_DISPLAY] = g_param_spec_string(
"name-for-display", "name-for-display",
"The name that should be displayed for the person",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
* PurplePerson:priority-contact-info:
*
* The [class@Purple.ContactInfo] that currently has the highest priority.
*
* This is used by user interfaces to determine which
* [class@Purple.ContactInfo] to use when messaging and so on.
*
* Since: 3.0
*/
properties[PROP_PRIORITY_CONTACT_INFO] = g_param_spec_object(
"priority-contact-info", "priority-contact-info",
"The priority contact info for the person",
PURPLE_TYPE_CONTACT_INFO,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
}
/******************************************************************************
* Public API
*****************************************************************************/
PurplePerson *
purple_person_new(void) {
return g_object_new(PURPLE_TYPE_PERSON, NULL);
}
const char *
purple_person_get_id(PurplePerson *person) {
g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
return person->id;
}
const char *
purple_person_get_alias(PurplePerson *person) {
g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
return person->alias;
}
void
purple_person_set_alias(PurplePerson *person, const char *alias) {
g_return_if_fail(PURPLE_IS_PERSON(person));
if(!purple_strequal(person->alias, alias)) {
GObject *obj = G_OBJECT(person);
g_free(person->alias);
person->alias = g_strdup(alias);
g_object_freeze_notify(obj);
g_object_notify_by_pspec(obj, properties[PROP_ALIAS]);
g_object_notify_by_pspec(obj, properties[PROP_NAME_FOR_DISPLAY]);
g_object_thaw_notify(obj);
}
}
PurpleAvatar *
purple_person_get_avatar_for_display(PurplePerson *person) {
PurpleContactInfo *priority = NULL;
g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
if(PURPLE_IS_AVATAR(person->avatar)) {
return person->avatar;
}
priority = purple_person_get_priority_contact_info(person);
if(PURPLE_IS_CONTACT_INFO(priority)) {
return purple_contact_info_get_avatar(priority);
}
return NULL;
}
PurpleAvatar *
purple_person_get_avatar(PurplePerson *person) {
g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
return person->avatar;
}
void
purple_person_set_avatar(PurplePerson *person, PurpleAvatar *avatar) {
g_return_if_fail(PURPLE_IS_PERSON(person));
if(g_set_object(&person->avatar, avatar)) {
GObject *obj = G_OBJECT(person);
g_object_freeze_notify(obj);
g_object_notify_by_pspec(obj, properties[PROP_AVATAR]);
g_object_notify_by_pspec(obj, properties[PROP_AVATAR_FOR_DISPLAY]);
g_object_thaw_notify(obj);
}
}
const char *
purple_person_get_color(PurplePerson *person) {
g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
return person->color;
}
void
purple_person_set_color(PurplePerson *person, const char *color) {
g_return_if_fail(PURPLE_IS_PERSON(person));
if(!purple_strequal(person->color, color)) {
GObject *obj = G_OBJECT(person);
g_free(person->color);
person->color = g_strdup(color);
g_object_freeze_notify(obj);
g_object_notify_by_pspec(obj, properties[PROP_COLOR]);
g_object_notify_by_pspec(obj, properties[PROP_COLOR_FOR_DISPLAY]);
g_object_thaw_notify(obj);
}
}
const char *
purple_person_get_color_for_display(PurplePerson *person) {
PurpleContactInfo *priority = NULL;
g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
if(!purple_strempty(person->color)) {
return person->color;
}
priority = purple_person_get_priority_contact_info(person);
if(PURPLE_IS_CONTACT_INFO(priority)) {
return purple_contact_info_get_color(priority);
}
return NULL;
}
PurpleTags *
purple_person_get_tags(PurplePerson *person) {
g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
return person->tags;
}
const char *
purple_person_get_name_for_display(PurplePerson *person) {
PurpleContactInfo *priority = NULL;
g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
if(!purple_strempty(person->alias)) {
return person->alias;
}
priority = purple_person_get_priority_contact_info(person);
if(PURPLE_IS_CONTACT_INFO(priority)) {
return purple_contact_info_get_name_for_display(priority);
}
return NULL;
}
void
purple_person_add_contact_info(PurplePerson *person,
PurpleContactInfo *info)
{
PurplePresence *presence = NULL;
PurpleContactInfo *current_priority = NULL;
g_return_if_fail(PURPLE_IS_PERSON(person));
g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));
current_priority = purple_person_get_priority_contact_info(person);
g_ptr_array_add(person->contacts, g_object_ref(info));
presence = purple_contact_info_get_presence(info);
g_signal_connect_object(presence, "notify",
G_CALLBACK(purple_person_presence_notify_cb),
person, 0);
purple_contact_info_set_person(info, person);
purple_person_sort_contacts(person, current_priority);
}
gboolean
purple_person_remove_contact_info(PurplePerson *person,
PurpleContactInfo *info)
{
PurpleContactInfo *current_priority = NULL;
gboolean removed = FALSE;
g_return_val_if_fail(PURPLE_IS_PERSON(person), FALSE);
g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), FALSE);
/* Ref the contact info to avoid a use-after free. */
g_object_ref(info);
current_priority = purple_person_get_priority_contact_info(person);
/* g_ptr_array_remove calls g_object_unref because we passed it in as a
* GDestroyNotify.
*/
removed = g_ptr_array_remove(person->contacts, info);
if(removed) {
PurplePresence *presence = purple_contact_info_get_presence(info);
g_signal_handlers_disconnect_by_func(presence,
purple_person_presence_notify_cb,
person);
purple_contact_info_set_person(info, NULL);
purple_person_sort_contacts(person, current_priority);
}
/* Remove our reference. */
g_object_unref(info);
return removed;
}
PurpleContactInfo *
purple_person_get_priority_contact_info(PurplePerson *person) {
g_return_val_if_fail(PURPLE_IS_PERSON(person), NULL);
if(person->contacts->len == 0) {
return NULL;
}
return g_ptr_array_index(person->contacts, 0);
}
gboolean
purple_person_has_contacts(PurplePerson *person) {
g_return_val_if_fail(PURPLE_IS_PERSON(person), FALSE);
return person->contacts->len > 0;
}
gboolean
purple_person_matches(PurplePerson *person, const char *needle) {
g_return_val_if_fail(PURPLE_IS_PERSON(person), FALSE);
if(purple_strempty(needle)) {
return TRUE;
}
/* Check if the person's alias matches. */
if(!purple_strempty(person->alias)) {
if(purple_strmatches(needle, person->alias)) {
return TRUE;
}
}
/* See if any of the contact infos match. */
return g_ptr_array_find_with_equal_func(person->contacts, needle,
purple_person_matches_find_func,
NULL);
}