Mon, 21 Mar 2022 20:25:43 -0500
Create a new status manager that's built in glade
Behavorial Changes:
* Confirmation dialog for remove has been removed.
* Remove button is disabled if the currently selected status is the active
status. Previously this scenario was silently ignored after confirmation.
Errata:
It is possible to open multiple modify windows for a status. Previously the
editor was reaching into the manager and controlling this pointer. I was going
to fix this, but the editor isn't a widget yet and I don't like doing duplicate
work.
Testing Done:
Basically tried everything I could think of with editing and selecting and so on.
Bugs closed: PIDGIN-17590
Reviewed at https://reviews.imfreedom.org/r/1322/
--- a/pidgin/meson.build Fri Mar 18 00:13:23 2022 -0500 +++ b/pidgin/meson.build Mon Mar 21 20:25:43 2022 -0500 @@ -53,6 +53,7 @@ 'pidginprotocolchooser.c', 'pidginprotocolstore.c', 'pidginscrollbook.c', + 'pidginstatusmanager.c', 'pidginstatusprimitivechooser.c', 'pidginstatusprimitivestore.c', 'pidginstylecontext.c', @@ -116,6 +117,7 @@ 'pidginprotocolchooser.h', 'pidginprotocolstore.h', 'pidginscrollbook.h', + 'pidginstatusmanager.h', 'pidginstatusprimitivechooser.h', 'pidginstatusprimitivestore.h', 'pidginstylecontext.h',
--- a/pidgin/pidginapplication.c Fri Mar 18 00:13:23 2022 -0500 +++ b/pidgin/pidginapplication.c Mon Mar 21 20:25:43 2022 -0500 @@ -40,7 +40,6 @@ #include "gtkdialogs.h" #include "gtkprivacy.h" #include "gtkroomlist.h" -#include "gtksavedstatuses.h" #include "gtkxfer.h" #include "pidginabout.h" #include "pidginaccountsdisabledmenu.h" @@ -50,6 +49,7 @@ #include "pidgindebug.h" #include "pidginmooddialog.h" #include "pidgin/pidginpluginsdialog.h" +#include "pidgin/pidginstatusmanager.h" #include "pidginprefs.h" struct _PidginApplication { @@ -410,7 +410,14 @@ pidgin_application_show_status_manager(GSimpleAction *simple, GVariant *parameter, gpointer data) { - pidgin_status_window_show(); + static GtkWidget *manager = NULL; + + if(!GTK_IS_WIDGET(manager)) { + manager = pidgin_status_manager_new(); + g_object_add_weak_pointer(G_OBJECT(manager), (gpointer)&manager); + } + + gtk_widget_show_all(manager); } static GActionEntry app_entries[] = {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pidginstatusmanager.c Mon Mar 21 20:25:43 2022 -0500 @@ -0,0 +1,307 @@ +/* + * 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 "pidginstatusmanager.h" + +#include "gtksavedstatuses.h" +#include "pidginiconname.h" + +enum { + RESPONSE_USE, + RESPONSE_ADD, + RESPONSE_MODIFY, + RESPONSE_REMOVE +}; + +enum { + COLUMN_TITLE, + COLUMN_ICON_NAME, + COLUMN_TYPE, + COLUMN_MESSAGE, + COLUMN_STATUS +}; + +struct _PidginStatusManager { + GtkDialog parent; + + GtkListStore *model; + GtkTreeSelection *selection; + + GtkWidget *use_button; + GtkWidget *modify_button; + GtkWidget *remove_button; +}; + +G_DEFINE_TYPE(PidginStatusManager, pidgin_status_manager, GTK_TYPE_DIALOG) + +/****************************************************************************** + * Helpers + *****************************************************************************/ +static PurpleSavedStatus * +pidgin_status_manager_get_selected_status(PidginStatusManager *manager) { + PurpleSavedStatus *status = NULL; + GtkTreeIter iter; + + if(gtk_tree_selection_count_selected_rows(manager->selection) == 0) { + return NULL; + } + + gtk_tree_selection_get_selected(manager->selection, NULL, &iter); + gtk_tree_model_get(GTK_TREE_MODEL(manager->model), &iter, + COLUMN_STATUS, &status, + -1); + + return status; +} + +static void +pidgin_status_manager_add(PidginStatusManager *manager, + PurpleSavedStatus *status) +{ + PurpleStatusPrimitive primitive; + GtkTreeIter iter; + gchar *message = NULL; + const gchar *icon_name = NULL, *type = NULL; + + message = purple_markup_strip_html(purple_savedstatus_get_message(status)); + + primitive = purple_savedstatus_get_primitive_type(status); + icon_name = pidgin_icon_name_from_status_primitive(primitive, NULL); + type = purple_primitive_get_name_from_type(primitive); + + gtk_list_store_append(manager->model, &iter); + gtk_list_store_set(manager->model, &iter, + COLUMN_TITLE, purple_savedstatus_get_title(status), + COLUMN_ICON_NAME, icon_name, + COLUMN_TYPE, type, + COLUMN_MESSAGE, message, + COLUMN_STATUS, status, + -1); + + g_free(message); +} + +static void +pidgin_status_manager_populate_helper(gpointer data, gpointer user_data) { + PidginStatusManager *manager = user_data; + PurpleSavedStatus *status = data; + + if(!purple_savedstatus_is_transient(status)) { + pidgin_status_manager_add(manager, status); + } +} + +static void +pidgin_status_manager_refresh(PidginStatusManager *manager) { + GList *statuses = NULL; + + gtk_list_store_clear(manager->model); + + statuses = purple_savedstatuses_get_all(); + g_list_foreach(statuses, pidgin_status_manager_populate_helper, manager); +} + +/****************************************************************************** + * Callbacks + *****************************************************************************/ +static void +pidgin_status_manager_response_cb(GtkDialog *dialog, gint response_id, + gpointer data) +{ + PidginStatusManager *manager = data; + PurpleSavedStatus *status = NULL; + + switch(response_id) { + case RESPONSE_USE: + status = pidgin_status_manager_get_selected_status(manager); + + purple_savedstatus_activate(status); + + break; + case RESPONSE_ADD: + pidgin_status_editor_show(FALSE, NULL); + break; + case RESPONSE_MODIFY: + status = pidgin_status_manager_get_selected_status(manager); + + pidgin_status_editor_show(TRUE, status); + + break; + case RESPONSE_REMOVE: + status = pidgin_status_manager_get_selected_status(manager); + + purple_savedstatus_delete_by_status(status); + + break; + case GTK_RESPONSE_CLOSE: + gtk_widget_destroy(GTK_WIDGET(dialog)); + break; + } +} + +static void +pidgin_status_manager_row_activated_cb(G_GNUC_UNUSED GtkTreeView *tree_view, + GtkTreePath *path, + G_GNUC_UNUSED GtkTreeViewColumn *column, + gpointer data) +{ + PidginStatusManager *manager = data; + GtkTreeIter iter; + + if(gtk_tree_model_get_iter(GTK_TREE_MODEL(manager->model), &iter, path)) { + PurpleSavedStatus *status = NULL; + + gtk_tree_model_get(GTK_TREE_MODEL(manager->model), &iter, + COLUMN_STATUS, &status, + -1); + + pidgin_status_editor_show(TRUE, status); + } +} + +static void +pidgin_status_manager_selection_changed_cb(GtkTreeSelection *selection, + gpointer data) +{ + PidginStatusManager *manager = data; + gboolean sensitive = TRUE; + + if(gtk_tree_selection_count_selected_rows(selection) == 0) { + sensitive = FALSE; + } + + gtk_widget_set_sensitive(manager->use_button, sensitive); + gtk_widget_set_sensitive(manager->modify_button, sensitive); + + /* Only enable the remove button if the currently selected row is not the + * currently active status. + */ + if(sensitive) { + PurpleSavedStatus *status = NULL; + + status = pidgin_status_manager_get_selected_status(manager); + + sensitive = status != purple_savedstatus_get_current(); + } + + gtk_widget_set_sensitive(manager->remove_button, sensitive); +} + +static void +pidgin_status_manager_savedstatus_changed_cb(PurpleSavedStatus *new_status, + G_GNUC_UNUSED PurpleSavedStatus *old_status, + gpointer data) +{ + PidginStatusManager *manager = data; + PurpleSavedStatus *selected = NULL; + + /* Disable the remove button if the selected status is the currently active + * status. + */ + selected = pidgin_status_manager_get_selected_status(manager); + if(selected != NULL) { + gboolean sensitive = selected != new_status; + + gtk_widget_set_sensitive(manager->remove_button, sensitive); + } +} + +static void +pidgin_status_manager_savedstatus_updated_cb(G_GNUC_UNUSED PurpleSavedStatus *status, + gpointer data) +{ + PidginStatusManager *manager = data; + + pidgin_status_manager_refresh(manager); +} + +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +static void +pidgin_status_manager_finalize(GObject *obj) { + purple_signals_disconnect_by_handle(obj); + + G_OBJECT_CLASS(pidgin_status_manager_parent_class)->finalize(obj); +} + +static void +pidgin_status_manager_init(PidginStatusManager *manager) { + gpointer handle = NULL; + + gtk_widget_init_template(GTK_WIDGET(manager)); + + pidgin_status_manager_refresh(manager); + + handle = purple_savedstatuses_get_handle(); + purple_signal_connect(handle, "savedstatus-changed", manager, + PURPLE_CALLBACK(pidgin_status_manager_savedstatus_changed_cb), + manager); + purple_signal_connect(handle, "savedstatus-added", manager, + PURPLE_CALLBACK(pidgin_status_manager_savedstatus_updated_cb), + manager); + purple_signal_connect(handle, "savedstatus-deleted", manager, + PURPLE_CALLBACK(pidgin_status_manager_savedstatus_updated_cb), + manager); + purple_signal_connect(handle, "savedstatus-modified", manager, + PURPLE_CALLBACK(pidgin_status_manager_savedstatus_updated_cb), + manager); +} + +static void +pidgin_status_manager_class_init(PidginStatusManagerClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + + obj_class->finalize = pidgin_status_manager_finalize; + + gtk_widget_class_set_template_from_resource( + widget_class, + "/im/pidgin/Pidgin3/Status/manager.ui" + ); + + gtk_widget_class_bind_template_child(widget_class, PidginStatusManager, + model); + gtk_widget_class_bind_template_child(widget_class, PidginStatusManager, + selection); + gtk_widget_class_bind_template_child(widget_class, PidginStatusManager, + use_button); + gtk_widget_class_bind_template_child(widget_class, PidginStatusManager, + modify_button); + gtk_widget_class_bind_template_child(widget_class, PidginStatusManager, + remove_button); + + gtk_widget_class_bind_template_callback(widget_class, + pidgin_status_manager_response_cb); + gtk_widget_class_bind_template_callback(widget_class, + pidgin_status_manager_row_activated_cb); + gtk_widget_class_bind_template_callback(widget_class, + pidgin_status_manager_selection_changed_cb); +} + +/****************************************************************************** + * Public API + *****************************************************************************/ +GtkWidget * +pidgin_status_manager_new(void) { + return g_object_new(PIDGIN_TYPE_STATUS_MANAGER, NULL); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pidginstatusmanager.h Mon Mar 21 20:25:43 2022 -0500 @@ -0,0 +1,57 @@ +/* + * 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_STATUS_MANAGER_H +#define PIDGIN_STATUS_MANAGER_H + +#include <gtk/gtk.h> + +#include <purple.h> + +G_BEGIN_DECLS + +/** + * PidginStatusManager: + * + * A dialog for managing statuses. + */ + +#define PIDGIN_TYPE_STATUS_MANAGER (pidgin_status_manager_get_type()) +G_DECLARE_FINAL_TYPE(PidginStatusManager, pidgin_status_manager, PIDGIN, + STATUS_MANAGER, GtkDialog) + +/** + * pidgin_status_manager_new: + * + * Creates a new instance of the dialog. + * + * Returns: (transfer full): The new #PidginStatusManager instance. + */ +GtkWidget *pidgin_status_manager_new(void); + +G_END_DECLS + +#endif /* PIDGIN_STATUS_MANAGER_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/resources/Status/manager.ui Mon Mar 21 20:25:43 2022 -0500 @@ -0,0 +1,226 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.38.2 + +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 Lesser General Public +License along with this library; if not, see <https://www.gnu.org/licenses/>. + +--> +<interface> + <requires lib="gtk+" version="3.24"/> + <!-- interface-license-type gplv2 --> + <!-- interface-name Pidgin --> + <!-- interface-description Internet Messenger --> + <!-- interface-copyright Pidgin Developers <devel@pidgin.im> --> + <object class="GtkListStore" id="model"> + <columns> + <!-- column-name title --> + <column type="gchararray"/> + <!-- column-name icon-name --> + <column type="gchararray"/> + <!-- column-name type --> + <column type="gchararray"/> + <!-- column-name message --> + <column type="gchararray"/> + <!-- column-name status --> + <column type="gpointer"/> + </columns> + </object> + <template class="PidginStatusManager" parent="GtkDialog"> + <property name="can-focus">False</property> + <property name="title" translatable="yes">Saved Statuses</property> + <property name="default-width">550</property> + <property name="default-height">250</property> + <property name="type-hint">dialog</property> + <signal name="response" handler="pidgin_status_manager_response_cb" swapped="no"/> + <child internal-child="vbox"> + <object class="GtkBox"> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkButtonBox"> + <property name="can-focus">False</property> + <property name="layout-style">end</property> + <child> + <object class="GtkButton" id="use_button"> + <property name="label" translatable="yes">_Use</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button1"> + <property name="label" translatable="yes">_Add</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="modify_button"> + <property name="label" translatable="yes">_Modify</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="remove_button"> + <property name="label" translatable="yes">_Remove</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button2"> + <property name="label" translatable="yes">_Close</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkTreeView"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="model">model</property> + <property name="search-column">0</property> + <signal name="row-activated" handler="pidgin_status_manager_row_activated_cb" object="PidginStatusManager" swapped="no"/> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="selection"> + <signal name="changed" handler="pidgin_status_manager_selection_changed_cb" object="PidginStatusManager" swapped="no"/> + </object> + </child> + <child> + <object class="GtkTreeViewColumn"> + <property name="resizable">True</property> + <property name="min-width">100</property> + <property name="title" translatable="yes">Title</property> + <property name="clickable">True</property> + <property name="sort-column-id">0</property> + <child> + <object class="GtkCellRendererText"> + <property name="ellipsize">end</property> + </object> + <attributes> + <attribute name="markup">0</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn"> + <property name="resizable">True</property> + <property name="title" translatable="yes">Type</property> + <property name="clickable">True</property> + <property name="sort-column-id">2</property> + <child> + <object class="GtkCellRendererPixbuf"/> + <attributes> + <attribute name="icon-name">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">True</property> + <property name="title" translatable="yes">Message</property> + <property name="clickable">True</property> + <property name="sort-column-id">4</property> + <child> + <object class="GtkCellRendererText"> + <property name="ellipsize">end</property> + </object> + <attributes> + <attribute name="markup">3</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">use_button</action-widget> + <action-widget response="1">button1</action-widget> + <action-widget response="2">modify_button</action-widget> + <action-widget response="3">remove_button</action-widget> + <action-widget response="-7">button2</action-widget> + </action-widgets> + </template> +</interface>
--- a/pidgin/resources/pidgin.gresource.xml Fri Mar 18 00:13:23 2022 -0500 +++ b/pidgin/resources/pidgin.gresource.xml Mon Mar 21 20:25:43 2022 -0500 @@ -30,6 +30,7 @@ <file compressed="true">Privacy/dialog.ui</file> <file compressed="true">Protocols/chooser.ui</file> <file compressed="true">Roomlist/roomlist.ui</file> + <file compressed="true">Status/manager.ui</file> <file compressed="true">Whiteboard/whiteboard.ui</file> <file compressed="true">Xfer/xfer.ui</file> <file compressed="true">closebutton.ui</file>
--- a/po/POTFILES.in Fri Mar 18 00:13:23 2022 -0500 +++ b/po/POTFILES.in Mon Mar 21 20:25:43 2022 -0500 @@ -412,6 +412,7 @@ pidgin/resources/Privacy/dialog.ui pidgin/resources/Protocols/chooser.ui pidgin/resources/Roomlist/roomlist.ui +pidgin/resources/Status/manager.ui pidgin/resources/Whiteboard/whiteboard.ui pidgin/resources/Xfer/xfer.ui pidgin/resources/gtk/menus.ui