Sun, 10 Aug 2025 23:44:08 +0800
Add Purple.Conversation.find_message_by_id
The method was added so that a protocol or plugin could easily lookup
for the reference for a message. This will be especially useful when a
protocol received a quoted message but only with an id.
/* * Pidgin - Internet Messenger * Copyright (C) Pidgin Developers <devel@pidgin.im> * * Pidgin 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, see <https://www.gnu.org/licenses/>. */ #include <glib/gi18n.h> #include <gtk/gtk.h> #include <birb.h> #include "pidginaccountsenabledmenu.h" #include "pidginapplication.h" struct _PidginAccountsEnabledMenu { GMenuModel parent; GListModel *accounts; }; /****************************************************************************** * Helpers *****************************************************************************/ static void pidgin_accounts_enabled_menu_foreach_add_action_groups(const char *prefix, GActionGroup *group, gpointer data) { PidginApplication *application = data; pidgin_application_add_action_group(application, prefix, group); } static void pidgin_accounts_enabled_menu_foreach_remove_action_groups(const char *prefix, G_GNUC_UNUSED GActionGroup *group, gpointer data) { PidginApplication *application = data; pidgin_application_add_action_group(application, prefix, NULL); } static void pidgin_accounts_enabled_menu_update(PidginAccountsEnabledMenu *menu, PurpleAccount *account) { PurpleProtocol *protocol = NULL; for(guint index = 0; index < g_list_model_get_n_items(menu->accounts); index++) { PurpleAccount *account2 = g_list_model_get_item(menu->accounts, index); if(account == account2) { /* Tell the model that the account needs to be updated. */ g_menu_model_items_changed(G_MENU_MODEL(menu), index, 1, 1); g_clear_object(&account2); break; } g_clear_object(&account2); } /* If the protocol has actions add them to the application windows. */ protocol = purple_account_get_protocol(account); if(PURPLE_IS_PROTOCOL(protocol)) { BirbActionMenu *action_menu = NULL; action_menu = purple_protocol_get_action_menu(protocol, account); if(BIRB_IS_ACTION_MENU(action_menu)) { GApplication *application = g_application_get_default(); birb_action_menu_foreach_action_group(action_menu, pidgin_accounts_enabled_menu_foreach_add_action_groups, application); } g_clear_object(&action_menu); } } /****************************************************************************** * Callbacks *****************************************************************************/ static void pidgin_accounts_enabled_menu_items_changed_cb(G_GNUC_UNUSED GListModel *model, guint position, guint removed, guint added, gpointer data) { PidginAccountsEnabledMenu *menu = data; g_menu_model_items_changed(G_MENU_MODEL(menu), position, removed, added); } static void pidgin_accounts_enabled_menu_account_connected_cb(G_GNUC_UNUSED PurpleAccountManager *m, PurpleAccount *account, gpointer data) { pidgin_accounts_enabled_menu_update(data, account); } static void pidgin_accounts_enabled_menu_account_disconnected_cb(G_GNUC_UNUSED PurpleAccountManager *manager, PurpleAccount *account, gpointer data) { PidginAccountsEnabledMenu *menu = data; PurpleProtocol *protocol = NULL; for(guint index = 0; index < g_list_model_get_n_items(menu->accounts); index++) { PurpleAccount *account2 = g_list_model_get_item(menu->accounts, index); if(account == account2) { /* Tell the model that the account needs to be updated. */ g_menu_model_items_changed(G_MENU_MODEL(menu), index, 1, 1); g_clear_object(&account2); break; } g_clear_object(&account2); } /* Figure out if this is the last connected account for this protocol, and * if so, remove the action group from the application windows. */ protocol = purple_account_get_protocol(account); if(PURPLE_IS_PROTOCOL(protocol)) { gboolean found = FALSE; manager = purple_account_manager_get_default(); for(guint i = 0; i < g_list_model_get_n_items(menu->accounts); i++) { PurpleAccount *account2 = g_list_model_get_item(menu->accounts, i); PurpleProtocol *protocol2 = purple_account_get_protocol(account2); if(!found && protocol2 == protocol) { found = TRUE; g_clear_object(&account2); break; } g_clear_object(&account2); } if(!found) { GApplication *application = g_application_get_default(); BirbActionMenu *action_menu = NULL; action_menu = purple_protocol_get_action_menu(protocol, account); birb_action_menu_foreach_action_group(action_menu, pidgin_accounts_enabled_menu_foreach_remove_action_groups, application); g_clear_object(&action_menu); } } } /****************************************************************************** * GMenuModel Implementation *****************************************************************************/ static gboolean pidgin_accounts_enabled_menu_is_mutable(G_GNUC_UNUSED GMenuModel *model) { return TRUE; } static gint pidgin_accounts_enabled_menu_get_n_items(GMenuModel *model) { PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(model); return g_list_model_get_n_items(menu->accounts); } static void pidgin_accounts_enabled_menu_get_item_attributes(GMenuModel *model, gint index, GHashTable **attributes) { PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(model); PurpleAccount *account = NULL; PurpleProtocol *protocol = NULL; GVariant *value = NULL; gchar *label = NULL; const gchar *account_name = NULL, *protocol_name = NULL, *icon_name = NULL; /* Create our hash table of attributes to return. This must always be * populated. */ *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_variant_unref); /* Get the account the caller is interested in. */ account = g_list_model_get_item(menu->accounts, index); if(!PURPLE_IS_ACCOUNT(account)) { return; } account_name = purple_account_get_username(account); /* Get the protocol from the account. */ protocol = purple_account_get_protocol(account); if(PURPLE_IS_PROTOCOL(protocol)) { protocol_name = purple_protocol_get_name(protocol); icon_name = purple_protocol_get_icon_name(protocol); } /* Add the label. */ /* translators: This format string is intended to contain the account * name followed by the protocol name to uniquely identify a specific * account. */ label = g_strdup_printf(_("%s (%s)"), account_name, protocol_name); value = g_variant_new_string(label); g_free(label); g_hash_table_insert(*attributes, G_MENU_ATTRIBUTE_LABEL, g_variant_ref_sink(value)); /* Add the icon if we have one. */ if(icon_name != NULL) { value = g_variant_new_string(icon_name); g_hash_table_insert(*attributes, G_MENU_ATTRIBUTE_ICON, g_variant_ref_sink(value)); } g_clear_object(&account); } static void pidgin_accounts_enabled_menu_get_item_links(GMenuModel *model, gint index, GHashTable **links) { PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(model); PurpleAccount *account = NULL; PurpleConnection *connection = NULL; PurpleProtocol *protocol = NULL; GApplication *application = g_application_get_default(); GMenu *submenu = NULL, *template = NULL; const gchar *account_id = NULL, *connection_id = NULL; /* Create our hash table for links, this must always be populated. */ *links = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref); account = g_list_model_get_item(menu->accounts, index); if(!PURPLE_IS_ACCOUNT(account)) { return; } account_id = purple_account_get_id(account); connection = purple_account_get_connection(account); if(PURPLE_IS_CONNECTION(connection)) { connection_id = purple_connection_get_id(connection); } /* Create a copy of our template menu. */ template = gtk_application_get_menu_by_id(GTK_APPLICATION(application), "enabled-account"); submenu = purple_menu_copy(G_MENU_MODEL(template)); /* Add the account actions if we have any. */ protocol = purple_account_get_protocol(account); if(PURPLE_IS_PROTOCOL(protocol)) { BirbActionMenu *action_menu = NULL; action_menu = purple_protocol_get_action_menu(protocol, account); if(BIRB_IS_ACTION_MENU(action_menu)) { GMenu *protocol_menu = NULL; protocol_menu = birb_action_menu_get_menu(action_menu); if(G_IS_MENU(protocol_menu)) { g_menu_insert_section(submenu, 1, NULL, G_MENU_MODEL(protocol_menu)); } g_clear_object(&action_menu); } } purple_menu_populate_dynamic_targets(submenu, "account", account_id, "connection", connection_id, NULL); g_hash_table_insert(*links, G_MENU_LINK_SUBMENU, submenu); g_clear_object(&account); } /****************************************************************************** * GObject Implementation *****************************************************************************/ G_DEFINE_FINAL_TYPE(PidginAccountsEnabledMenu, pidgin_accounts_enabled_menu, G_TYPE_MENU_MODEL) static void pidgin_accounts_enabled_menu_constructed(GObject *obj) { PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(obj); guint count = 0; G_OBJECT_CLASS(pidgin_accounts_enabled_menu_parent_class)->constructed(obj); count = g_list_model_get_n_items(menu->accounts); g_menu_model_items_changed(G_MENU_MODEL(obj), 0, 0, count); } static void pidgin_accounts_enabled_menu_init(PidginAccountsEnabledMenu *menu) { PurpleAccountManager *account_manager = NULL; account_manager = purple_account_manager_get_default(); menu->accounts = purple_account_manager_get_enabled(account_manager); g_signal_connect_object(menu->accounts, "items-changed", G_CALLBACK(pidgin_accounts_enabled_menu_items_changed_cb), menu, G_CONNECT_DEFAULT); g_signal_connect_object(account_manager, "account-connected", G_CALLBACK(pidgin_accounts_enabled_menu_account_connected_cb), menu, 0); g_signal_connect_object(account_manager, "account-disconnected", G_CALLBACK(pidgin_accounts_enabled_menu_account_disconnected_cb), menu, 0); } static void pidgin_accounts_enabled_menu_class_init(PidginAccountsEnabledMenuClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); GMenuModelClass *model_class = G_MENU_MODEL_CLASS(klass); obj_class->constructed = pidgin_accounts_enabled_menu_constructed; model_class->is_mutable = pidgin_accounts_enabled_menu_is_mutable; model_class->get_n_items = pidgin_accounts_enabled_menu_get_n_items; model_class->get_item_attributes = pidgin_accounts_enabled_menu_get_item_attributes; model_class->get_item_links = pidgin_accounts_enabled_menu_get_item_links; } /****************************************************************************** * Public API *****************************************************************************/ GMenuModel * pidgin_accounts_enabled_menu_new(void) { return g_object_new(PIDGIN_TYPE_ACCOUNTS_ENABLED_MENU, NULL); }