summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Sales de Andrade <quantum.analyst@gmail.com>2023-01-17 02:29:19 -0600
committerElliott Sales de Andrade <quantum.analyst@gmail.com>2023-01-17 02:29:19 -0600
commit5a8f549f794f0e8572f30d00c2377dcd9085a63c (patch)
tree5fc8ab1a1c86f07e6c258596ac0fd69b4d3e59c8
parent5945736f75db24a8b2beeb1ae212aaa38f1c6807 (diff)
downloadpidgin-5a8f549f794f0e8572f30d00c2377dcd9085a63c.tar.gz
Re-design account manager as a GtkListBox
Moves to a nicely spaced `GtkListBox`. The avatar is implemented using `AdwAvatar` solely for the autogenerated colour for accounts without an icon to give them some uniqueness, but it could be moved to `PidginAvatar`. As a followup, I'd probably move the Account Editors from a separate dialog to stack within the manager, similar to how the Plugin dialog works. Neither the previous manager nor this one prevents you from opening two editors for an account, but this would remove that issue as well. Testing Done: Toggled enabled/disabled to check that account status worked and was displayed. Added a few bogus accounts to confirm that errors are shown. Activated a row to show the editor. Hit the delete button and cancelled/approved and confirmed that account was kept/removed. Reviewed at https://reviews.imfreedom.org/r/2079/
-rw-r--r--pidgin/meson.build2
-rw-r--r--pidgin/pidginaccountmanager.c334
-rw-r--r--pidgin/pidginaccountrow.c447
-rw-r--r--pidgin/pidginaccountrow.h85
-rw-r--r--pidgin/resources/Accounts/account-row.ui119
-rw-r--r--pidgin/resources/Accounts/manager.ui167
-rw-r--r--pidgin/resources/pidgin.gresource.xml1
-rw-r--r--po/POTFILES.in2
8 files changed, 760 insertions, 397 deletions
diff --git a/pidgin/meson.build b/pidgin/meson.build
index c0d786267b..122c69206e 100644
--- a/pidgin/meson.build
+++ b/pidgin/meson.build
@@ -24,6 +24,7 @@ libpidgin_SOURCES = [
'pidginaccountfilterconnected.c',
'pidginaccountfilterprotocol.c',
'pidginaccountmanager.c',
+ 'pidginaccountrow.c',
'pidginaccountsdisabledmenu.c',
'pidginaccountsenabledmenu.c',
'pidginactiongroup.c',
@@ -90,6 +91,7 @@ libpidgin_headers = [
'pidginaccountfilterconnected.h',
'pidginaccountfilterprotocol.h',
'pidginaccountmanager.h',
+ 'pidginaccountrow.h',
'pidginaccountsdisabledmenu.h',
'pidginaccountsenabledmenu.h',
'pidginactiongroup.h',
diff --git a/pidgin/pidginaccountmanager.c b/pidgin/pidginaccountmanager.c
index f3c352c0a1..4162d8a617 100644
--- a/pidgin/pidginaccountmanager.c
+++ b/pidgin/pidginaccountmanager.c
@@ -29,167 +29,33 @@
#include "gtkaccount.h"
#include "pidgincore.h"
#include "pidginaccounteditor.h"
+#include "pidginaccountrow.h"
struct _PidginAccountManager {
GtkDialog parent;
- GtkListStore *model;
- GtkTreeSelection *selection;
-
- GtkWidget *modify_button;
- GtkWidget *remove_button;
+ GtkListBox *list_box;
+ GtkWidget *add;
};
enum {
RESPONSE_ADD,
- RESPONSE_MODIFY,
- RESPONSE_REMOVE,
-};
-
-enum {
- COLUMN_ENABLED,
- COLUMN_AVATAR,
- COLUMN_USERNAME,
- COLUMN_PROTOCOL_ICON,
- COLUMN_PROTOCOL_NAME,
- COLUMN_ACCOUNT
};
G_DEFINE_TYPE(PidginAccountManager, pidgin_account_manager, GTK_TYPE_DIALOG)
-static void pidgin_account_manager_account_notify_cb(GObject *obj, GParamSpec *pspec, gpointer data);
-
/******************************************************************************
* Helpers
*****************************************************************************/
-static gboolean
-pidgin_account_manager_find_account(PidginAccountManager *manager,
- PurpleAccount *account, GtkTreeIter *iter)
+static GtkWidget *
+pidgin_account_manager_create_widget(gpointer item,
+ G_GNUC_UNUSED gpointer data)
{
- GtkTreeModel *model = GTK_TREE_MODEL(manager->model);
-
- if(!gtk_tree_model_get_iter_first(model, iter)) {
- return FALSE;
- }
-
- do {
- PurpleAccount *current = NULL;
-
- gtk_tree_model_get(model, iter,
- COLUMN_ACCOUNT, &current,
- -1);
-
- if(current == account) {
- g_clear_object(&current);
-
- return TRUE;
- }
-
- g_clear_object(&current);
- } while(gtk_tree_model_iter_next(model, iter));
-
- return FALSE;
-}
-
-static PurpleAccount *
-pidgin_account_manager_get_selected_account(PidginAccountManager *manager) {
- PurpleAccount *account = NULL;
- GtkTreeIter iter;
-
- if(gtk_tree_selection_count_selected_rows(manager->selection) == 0) {
+ if(!PURPLE_IS_ACCOUNT(item)) {
return NULL;
}
- gtk_tree_selection_get_selected(manager->selection, NULL, &iter);
-
- gtk_tree_model_get(GTK_TREE_MODEL(manager->model), &iter,
- COLUMN_ACCOUNT, &account,
- -1);
-
- return account;
-}
-
-static void
-pidgin_account_manager_refresh_account(PidginAccountManager *manager,
- PurpleAccount *account,
- GtkTreeIter *iter)
-{
- PurpleContactInfo *info = NULL;
- PurpleImage *image = NULL;
- PurpleProtocol *protocol = NULL;
- GdkPixbuf *avatar = NULL;
- const gchar *protocol_icon = NULL, *protocol_name = NULL;
-
- /* Try to find the avatar for the account. */
- image = purple_buddy_icons_find_account_icon(account);
- if(image != NULL) {
- GdkPixbuf *raw = NULL;
-
- raw = purple_gdk_pixbuf_from_image(image);
- g_object_unref(image);
-
- avatar = gdk_pixbuf_scale_simple(raw, 22, 22, GDK_INTERP_HYPER);
- g_clear_object(&raw);
- }
-
- /* Get the protocol fields. */
- protocol = purple_account_get_protocol(account);
- if(PURPLE_IS_PROTOCOL(protocol)) {
- protocol_name = purple_protocol_get_name(protocol);
- protocol_icon = purple_protocol_get_icon_name(protocol);
- } else {
- protocol_name = _("Unknown");
- }
-
- info = PURPLE_CONTACT_INFO(account);
- gtk_list_store_set(manager->model, iter,
- COLUMN_ENABLED, purple_account_get_enabled(account),
- COLUMN_AVATAR, avatar,
- COLUMN_USERNAME, purple_contact_info_get_username(info),
- COLUMN_PROTOCOL_ICON, protocol_icon,
- COLUMN_PROTOCOL_NAME, protocol_name,
- COLUMN_ACCOUNT, account,
- -1);
-
- g_clear_object(&avatar);
-}
-
-static void
-pidgin_account_manager_update_account(PidginAccountManager *manager,
- PurpleAccount *account)
-{
- GtkTreeIter iter;
-
- if(pidgin_account_manager_find_account(manager, account, &iter)) {
- pidgin_account_manager_refresh_account(manager, account, &iter);
- }
-}
-
-static void
-pidgin_account_manager_add_account(PidginAccountManager *manager,
- PurpleAccount *account)
-{
- GtkTreeIter iter;
-
- gtk_list_store_append(manager->model, &iter);
-
- pidgin_account_manager_refresh_account(manager, account, &iter);
-
- g_signal_connect_object(account, "notify",
- G_CALLBACK(pidgin_account_manager_account_notify_cb),
- manager, 0);
-}
-
-static void
-pidgin_account_manager_populate_helper(PurpleAccount *account, gpointer data) {
- pidgin_account_manager_add_account(PIDGIN_ACCOUNT_MANAGER(data), account);
-}
-
-static void
-pidgin_account_manager_populate(PidginAccountManager *manager) {
- purple_account_manager_foreach(purple_account_manager_get_default(),
- pidgin_account_manager_populate_helper,
- manager);
+ return pidgin_account_row_new(PURPLE_ACCOUNT(item));
}
static void
@@ -200,51 +66,22 @@ pidgin_account_manager_create_account(PidginAccountManager *manager) {
gtk_window_present_with_time(GTK_WINDOW(editor), GDK_CURRENT_TIME);
}
-static void
-pidgin_account_manager_edit_selected_account(PidginAccountManager *manager) {
- PurpleAccount *account = NULL;
- GtkWidget *editor = NULL;
-
- account = pidgin_account_manager_get_selected_account(manager);
-
- editor = pidgin_account_editor_new(account);
- gtk_window_set_transient_for(GTK_WINDOW(editor),
- GTK_WINDOW(manager));
- gtk_window_present_with_time(GTK_WINDOW(editor), GDK_CURRENT_TIME);
-
- g_clear_object(&account);
-}
-
-static void
-pidgin_account_manager_remove_selected_account(PidginAccountManager *manager) {
- PurpleAccount *account = NULL;
- PurpleNotificationManager *notification_manager = NULL;
-
- account = pidgin_account_manager_get_selected_account(manager);
-
- /* Remove all notifications including connection errors for the account. */
- notification_manager = purple_notification_manager_get_default();
- purple_notification_manager_remove_with_account(notification_manager,
- account, TRUE);
-
- /* Delete the account. */
- purple_accounts_delete(account);
-
- g_clear_object(&account);
-}
-
/******************************************************************************
* Callbacks
*****************************************************************************/
+
static void
-pidgin_account_manager_account_notify_cb(GObject *obj,
- G_GNUC_UNUSED GParamSpec *pspec,
- gpointer data)
+pidgin_account_manager_refresh_add_cb(GListModel *list,
+ G_GNUC_UNUSED guint position,
+ G_GNUC_UNUSED guint removed,
+ G_GNUC_UNUSED guint added,
+ gpointer data)
{
- PidginAccountManager *manager = PIDGIN_ACCOUNT_MANAGER(data);
- PurpleAccount *account = PURPLE_ACCOUNT(obj);
+ PidginAccountManager *manager = data;
- pidgin_account_manager_update_account(manager, account);
+ /* If there are no accounts, the placeholder is shown, which includes an
+ * Add button. So hide the one in the button box if that's the case. */
+ gtk_widget_set_visible(manager->add, g_list_model_get_n_items(list) != 0);
}
static void
@@ -257,12 +94,6 @@ pidgin_account_manager_response_cb(GtkDialog *dialog, gint response_id,
case RESPONSE_ADD:
pidgin_account_manager_create_account(manager);
break;
- case RESPONSE_MODIFY:
- pidgin_account_manager_edit_selected_account(manager);
- break;
- case RESPONSE_REMOVE:
- pidgin_account_manager_remove_selected_account(manager);
- break;
case GTK_RESPONSE_CLOSE:
case GTK_RESPONSE_DELETE_EVENT:
gtk_window_destroy(GTK_WINDOW(dialog));
@@ -273,100 +104,16 @@ pidgin_account_manager_response_cb(GtkDialog *dialog, gint response_id,
}
static void
-pidgin_account_manager_selection_changed_cb(GtkTreeSelection *selection,
- gpointer data)
-{
- PidginAccountManager *manager = data;
- gboolean sensitive = TRUE;
-
- if(gtk_tree_selection_count_selected_rows(selection) == 0) {
- sensitive = FALSE;
- }
-
- gtk_widget_set_sensitive(manager->modify_button, sensitive);
- gtk_widget_set_sensitive(manager->remove_button, sensitive);
-}
-
-static void
-pidgin_account_manager_row_activated_cb(G_GNUC_UNUSED GtkTreeView *tree_view,
- GtkTreePath *path,
- G_GNUC_UNUSED GtkTreeViewColumn *column,
- gpointer data)
-{
- PidginAccountManager *manager = data;
- GtkTreeIter iter;
-
- if(gtk_tree_model_get_iter(GTK_TREE_MODEL(manager->model), &iter, path)) {
- GtkWidget *editor = NULL;
- PurpleAccount *account = NULL;
-
- gtk_tree_model_get(GTK_TREE_MODEL(manager->model), &iter,
- COLUMN_ACCOUNT, &account,
- -1);
-
- editor = pidgin_account_editor_new(account);
- gtk_widget_show(editor);
-
- g_clear_object(&account);
- }
-}
-
-static void
-pidgin_account_manager_enable_toggled_cb(G_GNUC_UNUSED GtkCellRendererToggle *renderer,
- gchar *path, gpointer data)
-{
- PidginAccountManager *manager = data;
- GtkTreeModel *model = GTK_TREE_MODEL(manager->model);
- GtkTreeIter iter;
-
- if(gtk_tree_model_get_iter_from_string(model, &iter, path)) {
- PurpleAccount *account = NULL;
- gboolean enabled = FALSE;
-
- /* The value of enabled in the model is the old value, so if enabled
- * is currently set to TRUE, we are disabling the account and vice
- * versa.
- */
- gtk_tree_model_get(model, &iter,
- COLUMN_ENABLED, &enabled,
- COLUMN_ACCOUNT, &account,
- -1);
-
- /* The account was just enabled, so set its status. */
- if(!enabled) {
- PurpleSavedStatus *status = purple_savedstatus_get_current();
- purple_savedstatus_activate_for_account(status, account);
- }
-
- purple_account_set_enabled(account, !enabled);
-
- /* We don't update the model here, as it's updated via the notify
- * signal.
- */
- }
-}
-
-static void
-pidgin_account_manager_account_added_cb(G_GNUC_UNUSED PurpleAccountManager *purple_manager,
- PurpleAccount *account,
- gpointer data)
-{
- PidginAccountManager *manager = data;
-
- pidgin_account_manager_add_account(manager, account);
-}
-
-static void
-pidgin_account_manager_account_removed_cb(G_GNUC_UNUSED PurpleAccountManager *purple_manager,
- PurpleAccount *account,
- gpointer data)
+pidgin_account_manager_row_activated_cb(G_GNUC_UNUSED GtkListBox *box,
+ GtkListBoxRow *row,
+ G_GNUC_UNUSED gpointer data)
{
- PidginAccountManager *manager = data;
- GtkTreeIter iter;
+ GtkWidget *editor = NULL;
+ PurpleAccount *account = NULL;
- if(pidgin_account_manager_find_account(manager, account, &iter)) {
- gtk_list_store_remove(manager->model, &iter);
- }
+ account = pidgin_account_row_get_account(PIDGIN_ACCOUNT_ROW(row));
+ editor = pidgin_account_editor_new(account);
+ gtk_widget_show(editor);
}
/******************************************************************************
@@ -374,19 +121,17 @@ pidgin_account_manager_account_removed_cb(G_GNUC_UNUSED PurpleAccountManager *pu
*****************************************************************************/
static void
pidgin_account_manager_init(PidginAccountManager *manager) {
- PurpleAccountManager *purple_manager = NULL;
+ GListModel *purple_manager = NULL;
gtk_widget_init_template(GTK_WIDGET(manager));
- pidgin_account_manager_populate(manager);
-
- purple_manager = purple_account_manager_get_default();
- g_signal_connect_object(purple_manager, "added",
- G_CALLBACK(pidgin_account_manager_account_added_cb),
- manager, 0);
- g_signal_connect_object(purple_manager, "removed",
- G_CALLBACK(pidgin_account_manager_account_removed_cb),
+ purple_manager = purple_account_manager_get_default_as_model();
+ gtk_list_box_bind_model(manager->list_box, purple_manager,
+ pidgin_account_manager_create_widget, NULL, NULL);
+ g_signal_connect_object(purple_manager, "items-changed",
+ G_CALLBACK(pidgin_account_manager_refresh_add_cb),
manager, 0);
+ pidgin_account_manager_refresh_add_cb(purple_manager, 0, 0, 0, manager);
}
static void
@@ -399,23 +144,16 @@ pidgin_account_manager_class_init(PidginAccountManagerClass *klass) {
);
gtk_widget_class_bind_template_child(widget_class, PidginAccountManager,
- model);
+ list_box);
gtk_widget_class_bind_template_child(widget_class, PidginAccountManager,
- selection);
+ add);
- gtk_widget_class_bind_template_child(widget_class, PidginAccountManager,
- modify_button);
- gtk_widget_class_bind_template_child(widget_class, PidginAccountManager,
- remove_button);
-
- gtk_widget_class_bind_template_callback(widget_class,
- pidgin_account_manager_enable_toggled_cb);
gtk_widget_class_bind_template_callback(widget_class,
pidgin_account_manager_response_cb);
gtk_widget_class_bind_template_callback(widget_class,
pidgin_account_manager_row_activated_cb);
gtk_widget_class_bind_template_callback(widget_class,
- pidgin_account_manager_selection_changed_cb);
+ pidgin_account_manager_create_account);
}
/******************************************************************************
diff --git a/pidgin/pidginaccountrow.c b/pidgin/pidginaccountrow.c
new file mode 100644
index 0000000000..a1a17db43b
--- /dev/null
+++ b/pidgin/pidginaccountrow.c
@@ -0,0 +1,447 @@
+/*
+ * 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-lib.h>
+
+#include <adwaita.h>
+
+#include "pidgin/pidginaccountrow.h"
+
+struct _PidginAccountRow {
+ GtkListBoxRow parent;
+
+ PurpleAccount *account;
+
+ GtkSwitch *enabled;
+ AdwAvatar *avatar;
+ GtkLabel *name;
+ GtkLabel *status;
+};
+
+enum {
+ PROP_0,
+ PROP_ACCOUNT,
+ N_PROPERTIES
+};
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+pidgin_account_row_refresh_buddy_icon(PidginAccountRow *row) {
+ PurpleImage *image = NULL;
+
+#warning FIX call this in the right place when buddy icons are better and can autorefresh
+ if(!PURPLE_IS_ACCOUNT(row->account)) {
+ return;
+ }
+
+ image = purple_buddy_icons_find_account_icon(row->account);
+ if(PURPLE_IS_IMAGE(image)) {
+ GdkTexture *texture = NULL;
+ GBytes *bytes = NULL;
+
+ bytes = purple_image_get_contents(image);
+ texture = gdk_texture_new_from_bytes(bytes, NULL);
+ g_bytes_unref(bytes);
+
+ if(GDK_IS_TEXTURE(texture)) {
+ adw_avatar_set_custom_image(row->avatar, GDK_PAINTABLE(texture));
+ g_object_unref(texture);
+ }
+ }
+}
+
+static void
+pidgin_account_row_refresh_status(PidginAccountRow *row) {
+ const char *status = NULL;
+ gboolean connected = FALSE;
+ gboolean error = FALSE;
+
+ if(PURPLE_IS_ACCOUNT(row->account)) {
+ if(!purple_account_get_enabled(row->account)) {
+ status = _("Disabled");
+ } else {
+ const PurpleConnectionErrorInfo *error_info = NULL;
+
+ error_info = purple_account_get_current_error(row->account);
+ if(error_info != NULL) {
+ status = error_info->description;
+ error = TRUE;
+ } else {
+ PurpleConnection *connection = NULL;
+
+ connection = purple_account_get_connection(row->account);
+ if(PURPLE_IS_CONNECTION(connection)) {
+ switch(purple_connection_get_state(connection)) {
+ case PURPLE_CONNECTION_STATE_DISCONNECTED:
+ status = _("Disconnected");
+ break;
+ case PURPLE_CONNECTION_STATE_DISCONNECTING:
+ status = _("Disconnecting...");
+ break;
+ case PURPLE_CONNECTION_STATE_CONNECTED:
+ status = _("Connected");
+ connected = TRUE;
+ break;
+ case PURPLE_CONNECTION_STATE_CONNECTING:
+ status = _("Connecting...");
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ gtk_switch_set_state(row->enabled, connected);
+ gtk_label_set_text(row->status, status);
+ if(error) {
+ gtk_widget_add_css_class(GTK_WIDGET(row->status), "error");
+ } else {
+ gtk_widget_remove_css_class(GTK_WIDGET(row->status), "error");
+ }
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+pidgin_account_row_state_changed_cb(G_GNUC_UNUSED GObject *obj,
+ G_GNUC_UNUSED GParamSpec *pspec,
+ gpointer data)
+{
+ PidginAccountRow *row = data;
+
+ pidgin_account_row_refresh_status(row);
+}
+
+static void
+pidgin_account_row_connection_changed_cb(G_GNUC_UNUSED GObject *obj,
+ G_GNUC_UNUSED GParamSpec *pspec,
+ gpointer data)
+{
+ PidginAccountRow *row = data;
+ PurpleConnection *connection = NULL;
+
+ connection = purple_account_get_connection(row->account);
+ if(PURPLE_IS_CONNECTION(connection)) {
+ g_signal_connect_object(connection, "notify::state",
+ G_CALLBACK(pidgin_account_row_state_changed_cb),
+ row, 0);
+ }
+
+ pidgin_account_row_refresh_status(row);
+}
+
+static void
+pidgin_account_row_enable_state_set_cb(G_GNUC_UNUSED GtkSwitch *sw,
+ gboolean state, gpointer data)
+{
+ PidginAccountRow *row = data;
+ PurpleAccount *account = row->account;
+
+ if(purple_account_get_enabled(account) == state) {
+ return;
+ }
+
+ /* The account was just enabled, so set its status. */
+ if(state) {
+ PurpleSavedStatus *status = purple_savedstatus_get_current();
+ purple_savedstatus_activate_for_account(status, account);
+ }
+
+ purple_account_set_enabled(account, state);
+}
+
+static char *
+pidgin_account_row_buddyicon_cb(G_GNUC_UNUSED GObject *self,
+ PurpleAccount *account,
+ G_GNUC_UNUSED gpointer data)
+{
+ const char *buddy_icon_path = NULL;
+ char *path = NULL;
+
+ if(!PURPLE_IS_ACCOUNT(account)) {
+ return NULL;
+ }
+
+ buddy_icon_path = purple_account_get_buddy_icon_path(account);
+ if(buddy_icon_path != NULL) {
+ path = g_strdup_printf("file://%s", buddy_icon_path);
+ }
+
+ return path;
+}
+
+static char *
+pidgin_account_row_protocol_name_cb(G_GNUC_UNUSED GObject *self,
+ PurpleAccount *account,
+ G_GNUC_UNUSED gpointer data)
+{
+ const char *name = _("Unknown");
+
+ if(PURPLE_IS_ACCOUNT(account)) {
+ PurpleProtocol *protocol = purple_account_get_protocol(account);
+ if(PURPLE_IS_PROTOCOL(protocol)) {
+ name = purple_protocol_get_name(protocol);
+ }
+ }
+
+ return g_strdup(name);
+}
+
+static char *
+pidgin_account_row_protocol_icon_cb(G_GNUC_UNUSED GObject *self,
+ PurpleAccount *account,
+ G_GNUC_UNUSED gpointer data)
+{
+ const char *icon_name = NULL;
+
+ if(PURPLE_IS_ACCOUNT(account)) {
+ PurpleProtocol *protocol = purple_account_get_protocol(account);
+ if(PURPLE_IS_PROTOCOL(protocol)) {
+ icon_name = purple_protocol_get_icon_name(protocol);
+ }
+ }
+
+ return g_strdup(icon_name);
+}
+
+static void
+pidgin_account_manager_remove_account_cb(G_GNUC_UNUSED AdwMessageDialog *self,
+ char *response, gpointer data)
+{
+ PurpleAccount *account = data;
+ PurpleNotificationManager *notification_manager = NULL;
+
+ if(!purple_strequal(response, "remove")) {
+ return;
+ }
+
+ /* Remove all notifications including connection errors for the account. */
+ notification_manager = purple_notification_manager_get_default();
+ purple_notification_manager_remove_with_account(notification_manager,
+ account, TRUE);
+
+ /* Delete the account. */
+ purple_accounts_delete(account);
+}
+
+static void
+pidgin_account_row_remove_cb(G_GNUC_UNUSED GtkButton *self, gpointer data) {
+ PidginAccountRow *row = data;
+ GtkRoot *root = NULL;
+ AdwMessageDialog *dialog = NULL;
+ PurpleContactInfo *info = NULL;
+ const char *name = NULL;
+ char *protocol_name = NULL;
+
+ info = PURPLE_CONTACT_INFO(row->account);
+ name = purple_contact_info_get_name_for_display(info);
+ protocol_name = pidgin_account_row_protocol_name_cb(NULL, row->account,
+ NULL);
+
+ root = gtk_widget_get_root(GTK_WIDGET(row));
+ dialog = ADW_MESSAGE_DIALOG(adw_message_dialog_new(GTK_WINDOW(root),
+ _("Remove account?"),
+ NULL));
+ adw_message_dialog_format_body(dialog,
+ _("Do you want to remove the %s (%s) "
+ "account from Pidgin?"),
+ name, protocol_name);
+ adw_message_dialog_add_responses(dialog, "cancel", _("Cancel"),
+ "remove", _("Remove"), NULL);
+ adw_message_dialog_set_response_appearance(dialog, "remove",
+ ADW_RESPONSE_DESTRUCTIVE);
+ adw_message_dialog_set_default_response(dialog, "cancel");
+ adw_message_dialog_set_close_response(dialog, "cancel");
+
+ g_signal_connect_object(dialog, "response",
+ G_CALLBACK(pidgin_account_manager_remove_account_cb),
+ row->account, 0);
+
+ gtk_window_present(GTK_WINDOW(dialog));
+
+ g_free(protocol_name);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_TYPE(PidginAccountRow, pidgin_account_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+pidgin_account_row_get_property(GObject *obj, guint param_id, GValue *value,
+ GParamSpec *pspec)
+{
+ PidginAccountRow *row = PIDGIN_ACCOUNT_ROW(obj);
+
+ switch(param_id) {
+ case PROP_ACCOUNT:
+ g_value_set_object(value, pidgin_account_row_get_account(row));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+pidgin_account_row_set_property(GObject *obj, guint param_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ PidginAccountRow *row = PIDGIN_ACCOUNT_ROW(obj);
+
+ switch(param_id) {
+ case PROP_ACCOUNT:
+ pidgin_account_row_set_account(row, g_value_get_object(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+pidgin_account_row_finalize(GObject *obj) {
+ PidginAccountRow *row = PIDGIN_ACCOUNT_ROW(obj);
+
+ g_clear_object(&row->account);
+
+ G_OBJECT_CLASS(pidgin_account_row_parent_class)->finalize(obj);
+}
+
+static void
+pidgin_account_row_init(PidginAccountRow *row) {
+ gtk_widget_init_template(GTK_WIDGET(row));
+
+ pidgin_account_row_refresh_buddy_icon(row);
+ pidgin_account_row_refresh_status(row);
+}
+
+static void
+pidgin_account_row_class_init(PidginAccountRowClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+ obj_class->finalize = pidgin_account_row_finalize;
+ obj_class->get_property = pidgin_account_row_get_property;
+ obj_class->set_property = pidgin_account_row_set_property;
+
+ /* properties */
+
+ /**
+ * PidginAccountRow:account:
+ *
+ * The account that this row will be representing.
+ *
+ * Since: 3.0.0
+ */
+ properties[PROP_ACCOUNT] = g_param_spec_object(
+ "account", "account",
+ "The account that this row is representing",
+ PURPLE_TYPE_ACCOUNT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+ gtk_widget_class_set_template_from_resource(
+ widget_class,
+ "/im/pidgin/Pidgin3/Accounts/account-row.ui"
+ );
+
+ gtk_widget_class_bind_template_child(widget_class, PidginAccountRow,
+ enabled);
+ gtk_widget_class_bind_template_child(widget_class, PidginAccountRow,
+ avatar);
+ gtk_widget_class_bind_template_child(widget_class, PidginAccountRow,
+ name);
+ gtk_widget_class_bind_template_child(widget_class, PidginAccountRow,
+ status);
+
+ gtk_widget_class_bind_template_callback(widget_class,
+ pidgin_account_row_enable_state_set_cb);
+ gtk_widget_class_bind_template_callback(widget_class,
+ pidgin_account_row_buddyicon_cb);
+ gtk_widget_class_bind_template_callback(widget_class,
+ pidgin_account_row_protocol_name_cb);
+ gtk_widget_class_bind_template_callback(widget_class,
+ pidgin_account_row_protocol_icon_cb);
+ gtk_widget_class_bind_template_callback(widget_class,
+ pidgin_account_row_remove_cb);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+GtkWidget *
+pidgin_account_row_new(PurpleAccount *account) {
+ return g_object_new(PIDGIN_TYPE_ACCOUNT_ROW, "account", account, NULL);
+}
+
+PurpleAccount *
+pidgin_account_row_get_account(PidginAccountRow *row) {
+ g_return_val_if_fail(PIDGIN_IS_ACCOUNT_ROW(row), NULL);
+
+ return row->account;
+}
+
+void
+pidgin_account_row_set_account(PidginAccountRow *row, PurpleAccount *account) {
+ PurpleAccount *old = NULL;
+
+ g_return_if_fail(PIDGIN_IS_ACCOUNT_ROW(row));
+
+ old = (row->account != NULL) ? g_object_ref(row->account) : NULL;
+
+ if(g_set_object(&row->account, account)) {
+ if(PURPLE_IS_ACCOUNT(old)) {
+ PurpleConnection *connection = purple_account_get_connection(old);
+
+ if(PURPLE_IS_CONNECTION(connection)) {
+ g_signal_handlers_disconnect_by_func(connection,
+ pidgin_account_row_state_changed_cb,
+ row);
+ }
+
+ g_signal_handlers_disconnect_by_func(old,
+ pidgin_account_row_connection_changed_cb,
+ row);
+ }
+
+ if(PURPLE_IS_ACCOUNT(account)) {
+ g_signal_connect_object(account, "notify::connection",
+ G_CALLBACK(pidgin_account_row_connection_changed_cb),
+ row, 0);
+ g_signal_connect_object(account, "notify::error",
+ G_CALLBACK(pidgin_account_row_state_changed_cb),
+ row, 0);
+ pidgin_account_row_refresh_buddy_icon(row);
+ pidgin_account_row_refresh_status(row);
+ }
+
+ g_object_notify_by_pspec(G_OBJECT(row), properties[PROP_ACCOUNT]);
+ }
+
+ g_clear_object(&old);
+}
diff --git a/pidgin/pidginaccountrow.h b/pidgin/pidginaccountrow.h
new file mode 100644
index 0000000000..aeac09bd91
--- /dev/null
+++ b/pidgin/pidginaccountrow.h
@@ -0,0 +1,85 @@
+/*
+ * 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/>.
+ */
+
+#if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PIDGIN_ACCOUNT_ROW_H
+#define PIDGIN_ACCOUNT_ROW_H
+
+#include <gtk/gtk.h>
+
+#include <purple.h>
+
+/**
+ * PidginAccountRow:
+ *
+ * A [class@Gtk.ListBoxRow] subclass to display a [class@Purple.Account].
+ *
+ * Since: 3.0.0
+ */
+
+#define PIDGIN_TYPE_ACCOUNT_ROW (pidgin_account_row_get_type())
+G_DECLARE_FINAL_TYPE(PidginAccountRow, pidgin_account_row, PIDGIN, ACCOUNT_ROW,
+ GtkListBoxRow)
+
+G_BEGIN_DECLS
+
+/**
+ * pidgin_account_row_new:
+ * @account: (nullable): The account to represent.
+ *
+ * Creates a new #PidginAccountRow to display a [class@Purple.Account].
+ *
+ * Returns: (transfer full): The new account row.
+ *
+ * Since: 3.0.0
+ */
+GtkWidget *pidgin_account_row_new(PurpleAccount *account);
+
+/**
+ * pidgin_account_row_get_account:
+ * @row: The instance.
+ *
+ * Gets the [class@Purple.Account] that @row is displaying.
+ *
+ * Returns: (transfer none): The account if set otherwise %NULL.
+ *
+ * Since: 3.0.0
+ */
+PurpleAccount *pidgin_account_row_get_account(PidginAccountRow *row);
+
+/**
+ * pidgin_account_row_set_account:
+ * @row: The instance.
+ * @account: (nullable): The new account to set, or %NULL to unset.
+ *
+ * Sets the [class@Purple.Account] for @row to display.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_account_row_set_account(PidginAccountRow *row, PurpleAccount *account);
+
+G_END_DECLS
+
+#endif /* PIDGIN_ACCOUNT_ROW_H */
diff --git a/pidgin/resources/Accounts/account-row.ui b/pidgin/resources/Accounts/account-row.ui
new file mode 100644
index 0000000000..6a25958ac7
--- /dev/null
+++ b/pidgin/resources/Accounts/account-row.ui
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Pidgin - Internet Messenger
+Copyright (C) Pidgin Developers <devel@pidgin.im>
+
+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 library; if not, see <https://www.gnu.org/licenses/>.
+-->
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <!-- interface-license-type gplv2 -->
+ <!-- interface-name Pidgin -->
+ <!-- interface-description Internet Messenger -->
+ <!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
+ <template class="PidginAccountRow" parent="GtkListBoxRow">
+ <property name="selectable">0</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkSwitch" id="enabled">
+ <binding name="active">
+ <lookup name="enabled" type="PurpleAccount">
+ <lookup name="account">PidginAccountRow</lookup>
+ </lookup>
+ </binding>
+ <property name="valign">center</property>
+ <signal name="state-set" handler="pidgin_account_row_enable_state_set_cb" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="AdwAvatar" id="avatar">
+ <property name="size">48</property>
+ <binding name="text">
+ <lookup name="name-for-display" type="PurpleContactInfo">
+ <lookup name="account">PidginAccountRow</lookup>
+ </lookup>
+ </binding>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">1</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkLabel" id="name">
+ <property name="xalign">0</property>
+ <binding name="label">
+ <lookup name="name-for-display" type="PurpleContactInfo">
+ <lookup name="account">PidginAccountRow</lookup>
+ </lookup>
+ </binding>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status">
+ <property name="css-classes">dim-label</property>
+ <property name="wrap">1</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage">
+ <binding name="icon-name">
+ <closure type="gchararray" function="pidgin_account_row_protocol_icon_cb">
+ <lookup name="account">PidginAccountRow</lookup>
+ </closure>
+ </binding>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <binding name="label">
+ <closure type="gchararray" function="pidgin_account_row_protocol_name_cb">
+ <lookup name="account">PidginAccountRow</lookup>
+ </closure>
+ </binding>
+ <property name="css-classes">dim-label</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <!-- This button is not really an error, but the destructive-action styling is far too bold to use in a list box. -->
+ <property name="css-classes">circular
+error</property>
+ <property name="icon-name">list-remove-symbolic</property>
+ <property name="valign">center</property>
+ <signal name="clicked" handler="pidgin_account_row_remove_cb" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/pidgin/resources/Accounts/manager.ui b/pidgin/resources/Accounts/manager.ui
index 4d224ea5e5..60a9d90188 100644
--- a/pidgin/resources/Accounts/manager.ui
+++ b/pidgin/resources/Accounts/manager.ui
@@ -24,118 +24,89 @@ along with this program; if not, see <https://www.gnu.org/licenses/>.
<!-- interface-name Pidgin -->
<!-- interface-description Internet Messenger -->
<!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
- <object class="GtkListStore" id="model">
- <columns>
- <!-- column-name enabled -->
- <column type="gboolean"/>
- <!-- column-name avatar -->
- <column type="GdkPixbuf"/>
- <!-- column-name username -->
- <column type="gchararray"/>
- <!-- column-name protocol-icon -->
- <column type="gchararray"/>
- <!-- column-name protocol-name -->
- <column type="gchararray"/>
- <!-- column-name account -->
- <column type="GObject"/>
- </columns>
- </object>
<template class="PidginAccountManager" parent="GtkDialog">
<property name="title" translatable="1">Accounts</property>
- <property name="default-width">500</property>
- <property name="default-height">300</property>
+ <property name="default-width">640</property>
+ <property name="default-height">480</property>
<signal name="response" handler="pidgin_account_manager_response_cb" swapped="no"/>
<child internal-child="content_area">
- <object class="GtkScrolledWindow">
- <property name="vexpand">1</property>
- <property name="focusable">1</property>
+ <object class="GtkBox">
<child>
- <object class="GtkTreeView">
- <property name="focusable">1</property>
- <property name="model">model</property>
- <signal name="row-activated" handler="pidgin_account_manager_row_activated_cb" object="PidginAccountManager" swapped="no"/>
- <child internal-child="selection">
- <object class="GtkTreeSelection" id="selection">
- <signal name="changed" handler="pidgin_account_manager_selection_changed_cb" object="PidginAccountManager" swapped="no"/>
- </object>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="title" translatable="1">Enabled</property>
+ <object class="GtkScrolledWindow">
+ <property name="vexpand">1</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="margin-bottom">24</property>
+ <property name="margin-end">24</property>
+ <property name="margin-start">24</property>
+ <property name="margin-top">24</property>
+ <property name="orientation">horizontal</property>
+ <property name="valign">center</property>
<child>
- <object class="GtkCellRendererToggle">
- <signal name="toggled" handler="pidgin_account_manager_enable_toggled_cb" object="PidginAccountManager" swapped="no"/>
+ <object class="GtkListBox" id="list_box">
+ <property name="css-classes">boxed-list
+rich-list</property>
+ <property name="selection-mode">none</property>
+ <property name="show-separators">1</property>
+ <signal name="row-activated" handler="pidgin_account_manager_row_activated_cb" swapped="no"/>
+ <child type="placeholder">
+ <object class="GtkBox">
+ <property name="margin-bottom">48</property>
+ <property name="margin-top">48</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <property name="valign">center</property>
+ <property name="vexpand">1</property>
+ <child>
+ <object class="GtkImage">
+ <property name="css-classes">dim-label</property>
+ <property name="icon-name">view-list-symbolic</property>
+ <property name="pixel-size">128</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="css-classes">title-1</property>
+ <property name="label" translatable="1">No Accounts</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="css-classes">pill
+suggested-action</property>
+ <property name="halign">center</property>
+ <signal name="clicked" handler="pidgin_account_manager_create_account" swapped="yes"/>
+ <property name="child">
+ <object class="AdwButtonContent">
+ <property name="icon-name">list-add-symbolic</property>
+ <property name="label" translatable="1">_Add…</property>
+ <property name="use-underline">1</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
</object>
- <attributes>
- <attribute name="active">0</attribute>
- </attributes>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="resizable">1</property>
- <property name="title" translatable="1">Username</property>
- <child>
- <object class="GtkCellRendererPixbuf"/>
- <attributes>
- <attribute name="pixbuf">1</attribute>
- </attributes>
- </child>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="markup">2</attribute>
- </attributes>
</child>
</object>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="resizable">1</property>
- <property name="title" translatable="1">Protocol</property>
- <child>
- <object class="GtkCellRendererPixbuf"/>
- <attributes>
- <attribute name="icon-name">3</attribute>
- </attributes>
- </child>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="markup">4</attribute>
- </attributes>
- </child>
- </object>
- </child>
+ </property>
</object>
</child>
</object>
</child>
<child type="action">
- <object class="GtkButton" id="button3">
- <property name="label" translatable="1">_Add...</property>
- <property name="focusable">1</property>
- <property name="receives-default">1</property>
- <property name="use-underline">1</property>
- </object>
- </child>
- <child type="action">
- <object class="GtkButton" id="modify_button">
- <property name="label" translatable="1">_Modify...</property>
- <property name="sensitive">0</property>
+ <object class="GtkButton" id="add">
+ <property name="css-classes">suggested-action</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
- <property name="use-underline">1</property>
- </object>
- </child>
- <child type="action">
- <object class="GtkButton" id="remove_button">
- <property name="label" translatable="1">_Remove</property>
- <property name="sensitive">0</property>
- <property name="focusable">1</property>
- <property name="receives-default">1</property>
- <property name="use-underline">1</property>
+ <property name="child">
+ <object class="AdwButtonContent">
+ <property name="icon-name">list-add-symbolic</property>
+ <property name="label" translatable="1">_Add…</property>
+ <property name="use-underline">1</property>
+ </object>
+ </property>
</object>
</child>
<child type="action">
@@ -147,9 +118,7 @@ along with this program; if not, see <https://www.gnu.org/licenses/>.
</object>
</child>
<action-widgets>
- <action-widget response="0">button3</action-widget>
- <action-widget response="1">modify_button</action-widget>
- <action-widget response="2">remove_button</action-widget>
+ <action-widget response="0">add</action-widget>
<action-widget response="close">button2</action-widget>
</action-widgets>
</template>
diff --git a/pidgin/resources/pidgin.gresource.xml b/pidgin/resources/pidgin.gresource.xml
index e69e9ec9da..4fe9a70a5e 100644
--- a/pidgin/resources/pidgin.gresource.xml
+++ b/pidgin/resources/pidgin.gresource.xml
@@ -4,6 +4,7 @@
<file compressed="true" preprocess="xml-stripblanks">About/about.ui</file>
<file compressed="true">About/about.md</file>
<file compressed="true" preprocess="json-stripblanks">About/credits.json</file>
+ <file compressed="true" preprocess="xml-stripblanks">Accounts/account-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks">Accounts/chooser.ui</file>
<file compressed="true" preprocess="xml-stripblanks">Accounts/editor.ui</file>
<file compressed="true" preprocess="xml-stripblanks">Accounts/manager.ui</file>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3ee3f29b79..afdac19434 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -263,6 +263,7 @@ pidgin/pidginaccounteditor.c
pidgin/pidginaccountfilterconnected.c
pidgin/pidginaccountfilterprotocol.c
pidgin/pidginaccountmanager.c
+pidgin/pidginaccountrow.c
pidgin/pidginaccountsdisabledmenu.c
pidgin/pidginaccountsenabledmenu.c
pidgin/pidginactiongroup.c
@@ -315,6 +316,7 @@ pidgin/prefs/pidginprefs.c
pidgin/prefs/pidginproxyprefs.c
pidgin/prefs/pidginvvprefs.c
pidgin/resources/About/about.ui
+pidgin/resources/Accounts/account-row.ui
pidgin/resources/Accounts/chooser.ui
pidgin/resources/Accounts/editor.ui
pidgin/resources/Accounts/manager.ui