pidgin/pidgin

Convert PidginStatusBox to a GtkMenuButton

12 months ago, Elliott Sales de Andrade
adf6d358d438
Parents 4df23def5fe0
Children c68c3ce47da8
Convert PidginStatusBox to a GtkMenuButton

This moves to a menu system to populate the list of statuses, and the new `PidginStatusDisplay` to show them. I tried a `GtkDropDown` previously, but that was unable to show the separators that we previously had. Consequently, it uses actions a bit more then before.

However, since menus don't support icons+text at the same time, and doesn't have a factory property, we do need to manually create custom widgets a bit, but it's mostly only a button + the `PidginStatusDisplay`.

The main display is now centred, but there seems to be nothing we can do about that, as the box with that alignment is internal to the `GtkMenuButton`. If we don't want that, we could switch to managing a `GtkToggleButton` and `GtkPopoverMenu` ourselves.

Testing Done:
Opened status box and confirmed that all primitives and saved statuses were there as expected.
Modified a status, and confirmed that the menu updated to match.
Added/deleted statuses from the manager and confirmed they menu updated to match.
Chose the New Status/Saved Statuses menu items and confirmed they opened the correct dialogs.
Picked a bunch of statuses from the menu items and confirmed the main display updated, and some updates were sent out to protocols (e.g. Demo disconnecting when offline, etc.)

Reviewed at https://reviews.imfreedom.org/r/2386/
--- a/pidgin/pidginstatusbox.c Mon Apr 03 23:09:36 2023 -0500
+++ b/pidgin/pidginstatusbox.c Mon Apr 03 23:11:34 2023 -0500
@@ -28,153 +28,112 @@
#include "pidginstatusbox.h"
#include "pidginiconname.h"
-
-#define PRIMITIVE_FORMAT "primitive_%d"
-#define SAVEDSTATUS_FORMAT "savedstatus_%lu"
+#include "pidginstatusdisplay.h"
-typedef enum {
- PIDGIN_STATUS_BOX_TYPE_SEPARATOR,
- PIDGIN_STATUS_BOX_TYPE_PRIMITIVE,
- PIDGIN_STATUS_BOX_TYPE_POPULAR,
- PIDGIN_STATUS_BOX_TYPE_ACTION,
- PIDGIN_STATUS_BOX_NUM_TYPES
-} PidginStatusBoxItemType;
+#define SAVEDSTATUS_FORMAT "savedstatus_%lu"
struct _PidginStatusBox {
GtkBox parent;
- GtkListStore *model;
- GtkWidget *combo;
-
- /* This is used to flipback to the correct status when one of the actions
- * items is selected.
- */
- gchar *active_id;
-};
+ GtkWidget *button;
+ PidginStatusDisplay *display;
+ GtkPopover *popover;
-enum {
- ID_COLUMN,
- TYPE_COLUMN, /* PidginStatusBoxItemType */
- ICON_NAME_COLUMN,
- PRIMITIVE_COLUMN,
- TEXT_COLUMN,
- /* This value depends on TYPE_COLUMN. For POPULAR types, this is the
- * creation time.
- */
- DATA_COLUMN,
- EMBLEM_VISIBLE_COLUMN,
- NUM_COLUMNS
+ GMenu *primitives;
+ GMenu *saved_statuses;
+ GList *custom_widgets;
};
G_DEFINE_TYPE(PidginStatusBox, pidgin_status_box, GTK_TYPE_BOX)
-/* This prototype is necessary so we can block this signal handler when we are
- * manually updating the combobox.
- */
-static void pidgin_status_box_combo_changed_cb(GtkComboBox *combo, gpointer user_data);
-
/******************************************************************************
* Helpers
*****************************************************************************/
-static void
-pidgin_status_box_update_to_status(PidginStatusBox *status_box,
- PurpleSavedStatus *status)
-{
- gchar *id = NULL;
- gboolean set = FALSE;
- time_t creation_time = 0;
+static GtkWidget *
+pidgin_status_box_make_primitive_widget(const char *action, const char *id) {
+ GtkWidget *button = NULL;
+ GtkWidget *display = NULL;
+ PurpleStatusPrimitive primitive = PURPLE_STATUS_UNSET;
- /* Try to set the combo box to the saved status. */
- creation_time = purple_savedstatus_get_creation_time(status);
- id = g_strdup_printf(SAVEDSTATUS_FORMAT, creation_time);
+ primitive = purple_primitive_get_type_from_id(id);
+ display = pidgin_status_display_new_for_primitive(primitive);
- set = gtk_combo_box_set_active_id(GTK_COMBO_BOX(status_box->combo), id);
- g_free(id);
-
- /* If we failed to set via the savedstatus, fallback to the primitive. */
- if(!set) {
- PurpleStatusPrimitive primitive;
+ button = gtk_button_new();
+ gtk_widget_add_css_class(button, "flat");
+ gtk_button_set_child(GTK_BUTTON(button), display);
- primitive = purple_savedstatus_get_primitive_type(status);
- id = g_strdup_printf(PRIMITIVE_FORMAT, primitive);
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button), action);
+ gtk_actionable_set_action_target(GTK_ACTIONABLE(button),
+ (const char *)G_VARIANT_TYPE_INT32,
+ primitive);
- gtk_combo_box_set_active_id(GTK_COMBO_BOX(status_box->combo), id);
- g_free(id);
- }
+ return button;
}
static void
-pidgin_status_box_add(PidginStatusBox *status_box,
- PidginStatusBoxItemType type,
- PurpleStatusPrimitive primitive, const gchar *text,
- gpointer data)
-{
- GtkTreeIter iter;
- gchar *id = NULL, *escaped_text = NULL;
- const gchar *icon_name = NULL;
- gboolean emblem_visible = FALSE;
+pidgin_status_box_populate_primitives(PidginStatusBox *status_box) {
+ GtkPopoverMenu *popover = GTK_POPOVER_MENU(status_box->popover);
+ GMenuModel *menu = G_MENU_MODEL(status_box->primitives);
+ gint n_items = 0;
- escaped_text = g_markup_escape_text(text, -1);
+ n_items = g_menu_model_get_n_items(menu);
+ for(gint index = 0; index < n_items; index++) {
+ GtkWidget *button = NULL;
+ char *action = NULL;
+ char *target = NULL;
+ char *custom_id = NULL;
+
+ g_menu_model_get_item_attribute(menu, index, G_MENU_ATTRIBUTE_ACTION,
+ "s", &action);
+ g_menu_model_get_item_attribute(menu, index, G_MENU_ATTRIBUTE_TARGET,
+ "s", &target);
+ g_menu_model_get_item_attribute(menu, index, "custom", "s", &custom_id);
+
+ button = pidgin_status_box_make_primitive_widget(action, target);
+ gtk_popover_menu_add_child(popover, button, custom_id);
- if(type == PIDGIN_STATUS_BOX_TYPE_POPULAR) {
- PurpleSavedStatus *saved_status = NULL;
- time_t creation_time = GPOINTER_TO_INT(data);
-
- saved_status = purple_savedstatus_find_by_creation_time(creation_time);
+ g_free(action);
+ g_free(target);
+ g_free(custom_id);
+ }
+}
- if(saved_status != NULL) {
- id = g_strdup_printf(SAVEDSTATUS_FORMAT, creation_time);
+static char *
+pidgin_status_box_make_savedstatus_widget(PurpleSavedStatus *saved_status,
+ GtkWidget **widget)
+{
+ GtkWidget *button = NULL;
+ GtkWidget *display = NULL;
+ time_t creation_time = 0;
- if(!purple_savedstatus_is_transient(saved_status)) {
- emblem_visible = TRUE;
- }
- }
+ display = pidgin_status_display_new_for_saved_status(saved_status);
+
+ if(!purple_savedstatus_is_transient(saved_status)) {
+ GtkWidget *image = gtk_image_new_from_icon_name("document-save");
+ gtk_widget_set_halign(image, GTK_ALIGN_END);
+ gtk_widget_set_hexpand(image, TRUE);
+ gtk_box_append(GTK_BOX(display), image);
}
- if(id == NULL && primitive != PURPLE_STATUS_UNSET) {
- id = g_strdup_printf(PRIMITIVE_FORMAT, primitive);
- }
-
- icon_name = pidgin_icon_name_from_status_primitive(primitive, NULL);
+ button = gtk_button_new();
+ gtk_widget_add_css_class(button, "flat");
+ gtk_button_set_child(GTK_BUTTON(button), display);
- gtk_list_store_append(status_box->model, &iter);
- gtk_list_store_set(status_box->model, &iter,
- ID_COLUMN, id,
- TYPE_COLUMN, type,
- ICON_NAME_COLUMN, icon_name,
- PRIMITIVE_COLUMN, primitive,
- TEXT_COLUMN, escaped_text,
- DATA_COLUMN, data,
- EMBLEM_VISIBLE_COLUMN, emblem_visible,
- -1);
+ creation_time = purple_savedstatus_get_creation_time(saved_status);
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button), "status.set-saved");
+ gtk_actionable_set_action_target(GTK_ACTIONABLE(button),
+ (const char *)G_VARIANT_TYPE_INT64,
+ creation_time);
+ *widget = button;
- g_free(escaped_text);
- g_free(id);
-}
-
-static gboolean
-pidgin_status_box_row_separator_func(GtkTreeModel *model, GtkTreeIter *iter,
- G_GNUC_UNUSED gpointer data)
-{
- PidginStatusBoxItemType type;
-
- gtk_tree_model_get(model, iter, TYPE_COLUMN, &type, -1);
-
- return type == PIDGIN_STATUS_BOX_TYPE_SEPARATOR;
+ return g_strdup_printf(SAVEDSTATUS_FORMAT, creation_time);
}
static void
-pidgin_status_box_add_separator(PidginStatusBox *status_box) {
- GtkTreeIter iter;
-
- gtk_list_store_append(status_box->model, &iter);
- gtk_list_store_set(status_box->model, &iter,
- TYPE_COLUMN, PIDGIN_STATUS_BOX_TYPE_SEPARATOR,
- -1);
-}
-
-static void
-pidgin_status_box_update_saved_statuses(PidginStatusBox *status_box) {
+pidgin_status_box_populate_saved_statuses(PidginStatusBox *status_box)
+{
+ GtkPopoverMenu *popover_menu = NULL;
+ GMenu *menu = NULL;
GList *list, *cur;
list = purple_savedstatuses_get_popular(6);
@@ -183,124 +142,84 @@
return;
}
+ popover_menu = GTK_POPOVER_MENU(status_box->popover);
+ menu = status_box->saved_statuses;
for(cur = list; cur != NULL; cur = cur->next) {
PurpleSavedStatus *saved = cur->data;
- GString *text = NULL;
- time_t creation_time;
-
- text = g_string_new(purple_savedstatus_get_title(saved));
-
- if(!purple_savedstatus_is_transient(saved)) {
- /*
- * Transient statuses do not have a title, so the savedstatus
- * API returns the message when purple_savedstatus_get_title() is
- * called, so we don't need to get the message a second time.
- */
- const gchar *message = NULL;
+ GtkWidget *widget = NULL;
+ char *id = NULL;
+ GMenuItem *item = NULL;
- message = purple_savedstatus_get_message(saved);
- if(message != NULL) {
- gchar *stripped = purple_markup_strip_html(message);
+ id = pidgin_status_box_make_savedstatus_widget(saved, &widget);
+ item = g_menu_item_new(NULL, NULL);
+ g_menu_item_set_attribute(item, "custom", "s", id);
+ g_menu_append_item(menu, item);
+ gtk_popover_menu_add_child(popover_menu, widget, id);
+ status_box->custom_widgets = g_list_prepend(status_box->custom_widgets,
+ widget);
- purple_util_chrreplace(stripped, '\n', ' ');
- g_string_append_printf(text, " - %s", stripped);
- g_free(stripped);
- }
- }
-
- creation_time = purple_savedstatus_get_creation_time(saved);
- pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_POPULAR,
- purple_savedstatus_get_primitive_type(saved),
- text->str, GINT_TO_POINTER(creation_time));
-
- g_string_free(text, TRUE);
+ g_free(id);
}
g_list_free(list);
-
- pidgin_status_box_add_separator(status_box);
-}
-
-static void
-pidgin_status_box_populate(PidginStatusBox *status_box) {
- pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_PRIMITIVE,
- PURPLE_STATUS_AVAILABLE, _("Available"), NULL);
- pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_PRIMITIVE,
- PURPLE_STATUS_AWAY, _("Away"), NULL);
- pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_PRIMITIVE,
- PURPLE_STATUS_UNAVAILABLE, _("Do not disturb"),
- NULL);
- pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_PRIMITIVE,
- PURPLE_STATUS_INVISIBLE, _("Invisible"), NULL);
- pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_PRIMITIVE,
- PURPLE_STATUS_OFFLINE, _("Offline"), NULL);
-
- pidgin_status_box_add_separator(status_box);
-
- pidgin_status_box_update_saved_statuses(status_box);
-
- pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_ACTION,
- PURPLE_STATUS_UNSET, _("New Status..."),
- "new-status");
- pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_ACTION,
- PURPLE_STATUS_UNSET, _("Saved Statuses..."),
- "status-manager");
}
/******************************************************************************
* Callbacks
*****************************************************************************/
static void
-pidgin_status_box_combo_changed_cb(GtkComboBox *combo, gpointer user_data) {
- PidginStatusBox *status_box = user_data;
- PidginStatusBoxItemType type;
+pidgin_status_box_set_primitive(G_GNUC_UNUSED GSimpleAction *action,
+ GVariant *parameter, gpointer data)
+{
+ PidginStatusBox *status_box = data;
PurpleSavedStatus *saved_status = NULL;
PurpleStatusPrimitive primitive;
- GtkTreeIter iter;
- gchar *id = NULL;
- gpointer data;
+
+ gtk_menu_button_popdown(GTK_MENU_BUTTON(status_box->button));
- if(!gtk_combo_box_get_active_iter(combo, &iter)) {
+ if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT32)) {
+ g_critical("status.set-primitive action parameter is of incorrect type %s",
+ g_variant_get_type_string(parameter));
return;
}
- gtk_tree_model_get(GTK_TREE_MODEL(status_box->model), &iter,
- ID_COLUMN, &id,
- TYPE_COLUMN, &type,
- PRIMITIVE_COLUMN, &primitive,
- DATA_COLUMN, &data,
- -1);
+ primitive = g_variant_get_int32(parameter);
- if(type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE) {
- saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, NULL);
- if(saved_status == NULL) {
- saved_status = purple_savedstatus_new(NULL, primitive);
- }
- } else if(type == PIDGIN_STATUS_BOX_TYPE_POPULAR) {
- time_t creation_time = GPOINTER_TO_INT(data);
-
- saved_status = purple_savedstatus_find_by_creation_time(creation_time);
- } else if(type == PIDGIN_STATUS_BOX_TYPE_ACTION) {
- GApplication *application = NULL;
- const gchar *action_name = (const gchar *)data;
-
- application = g_application_get_default();
-
- g_action_group_activate_action(G_ACTION_GROUP(application),
- action_name, NULL);
-
- gtk_combo_box_set_active_id(combo, status_box->active_id);
+ saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, NULL);
+ if(saved_status == NULL) {
+ saved_status = purple_savedstatus_new(NULL, primitive);
}
if(saved_status != NULL) {
if(saved_status != purple_savedstatus_get_current()) {
purple_savedstatus_activate(saved_status);
}
+ }
+}
- g_free(status_box->active_id);
- status_box->active_id = id;
- } else {
- g_free(id);
+static void
+pidgin_status_box_set_saved_status(G_GNUC_UNUSED GSimpleAction *action,
+ GVariant *parameter, gpointer data)
+{
+ PidginStatusBox *status_box = data;
+ PurpleSavedStatus *saved_status = NULL;
+ time_t creation_time = 0;
+
+ gtk_menu_button_popdown(GTK_MENU_BUTTON(status_box->button));
+
+ if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT64)) {
+ g_critical("status.set-saved action parameter is of incorrect type %s",
+ g_variant_get_type_string(parameter));
+ return;
+ }
+
+ creation_time = (time_t)g_variant_get_int64(parameter);
+ saved_status = purple_savedstatus_find_by_creation_time(creation_time);
+
+ if(saved_status != NULL) {
+ if(saved_status != purple_savedstatus_get_current()) {
+ purple_savedstatus_activate(saved_status);
+ }
}
}
@@ -316,7 +235,14 @@
return;
}
- pidgin_status_box_update_to_status(status_box, now);
+ pidgin_status_display_set_saved_status(status_box->display, now);
+}
+
+static void
+pidgin_status_box_remove_custom_widget(GtkWidget *widget, gpointer user_data) {
+ GtkPopoverMenu *popover_menu = user_data;
+
+ gtk_popover_menu_remove_child(popover_menu, widget);
}
static void
@@ -324,27 +250,14 @@
gpointer data)
{
PidginStatusBox *status_box = data;
- PurpleSavedStatus *current = NULL;
- static gboolean getting_current = FALSE;
- /* purple_status_get_current will create a new status if this is a brand
- * new install or the setting wasn't found. This leads to this handler
- * getting stuck in a loop until it segfaults because the stack smashed
- * into the heap. Anyways, we use this static boolean to check when this
- * function is called by purple_status_get_current so we can bail out and
- * break the loop.
- */
- if(getting_current) {
- return;
- }
+ g_list_foreach(status_box->custom_widgets,
+ (GFunc)pidgin_status_box_remove_custom_widget,
+ status_box->popover);
+ g_clear_list(&status_box->custom_widgets, NULL);
+ g_menu_remove_all(status_box->saved_statuses);
- gtk_list_store_clear(status_box->model);
- pidgin_status_box_populate(status_box);
-
- getting_current = TRUE;
- current = purple_savedstatus_get_current();
- pidgin_status_box_update_to_status(status_box, current);
- getting_current = FALSE;
+ pidgin_status_box_populate_saved_statuses(status_box);
}
/******************************************************************************
@@ -356,7 +269,7 @@
purple_signals_disconnect_by_handle(status_box);
- g_free(status_box->active_id);
+ g_clear_list(&status_box->custom_widgets, NULL);
G_OBJECT_CLASS(pidgin_status_box_parent_class)->finalize(obj);
}
@@ -364,14 +277,33 @@
static void
pidgin_status_box_init(PidginStatusBox *status_box) {
gpointer handle;
+ GSimpleActionGroup *action_group = NULL;
+ GActionEntry actions[] = {
+ {
+ .name = "set-primitive",
+ .activate = pidgin_status_box_set_primitive,
+ .parameter_type = (const char *)G_VARIANT_TYPE_INT32,
+ }, {
+ .name = "set-saved",
+ .activate = pidgin_status_box_set_saved_status,
+ .parameter_type = (const char *)G_VARIANT_TYPE_INT64,
+ },
+ };
gtk_widget_init_template(GTK_WIDGET(status_box));
- gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(status_box->combo),
- pidgin_status_box_row_separator_func,
- NULL, NULL);
+ action_group = g_simple_action_group_new();
+ g_action_map_add_action_entries(G_ACTION_MAP(action_group),
+ actions, G_N_ELEMENTS(actions),
+ status_box);
+ gtk_widget_insert_action_group(GTK_WIDGET(status_box), "status",
+ G_ACTION_GROUP(action_group));
- pidgin_status_box_populate(status_box);
+ status_box->popover = gtk_menu_button_get_popover(GTK_MENU_BUTTON(status_box->button));
+ gtk_popover_set_has_arrow(status_box->popover, FALSE);
+
+ pidgin_status_box_populate_primitives(status_box);
+ pidgin_status_box_populate_saved_statuses(status_box);
handle = purple_savedstatuses_get_handle();
purple_signal_connect(handle, "savedstatus-changed", status_box,
@@ -390,13 +322,13 @@
static void
pidgin_status_box_constructed(GObject *obj) {
+ PidginStatusBox *status_box = PIDGIN_STATUS_BOX(obj);
PurpleSavedStatus *status = NULL;
G_OBJECT_CLASS(pidgin_status_box_parent_class)->constructed(obj);
status = purple_savedstatus_get_current();
-
- pidgin_status_box_update_to_status(PIDGIN_STATUS_BOX(obj), status);
+ pidgin_status_display_set_saved_status(status_box->display, status);
}
static void
@@ -413,12 +345,13 @@
);
gtk_widget_class_bind_template_child(widget_class, PidginStatusBox,
- model);
+ button);
+ gtk_widget_class_bind_template_child(widget_class, PidginStatusBox,
+ display);
gtk_widget_class_bind_template_child(widget_class, PidginStatusBox,
- combo);
-
- gtk_widget_class_bind_template_callback(widget_class,
- pidgin_status_box_combo_changed_cb);
+ primitives);
+ gtk_widget_class_bind_template_child(widget_class, PidginStatusBox,
+ saved_statuses);
}
/******************************************************************************
--- a/pidgin/resources/Status/box.ui Mon Apr 03 23:09:36 2023 -0500
+++ b/pidgin/resources/Status/box.ui Mon Apr 03 23:11:34 2023 -0500
@@ -24,54 +24,59 @@
<!-- interface-name Pidgin -->
<!-- interface-description Internet Messenger -->
<!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
- <object class="GtkListStore" id="model">
- <columns>
- <!-- column-name id -->
- <column type="gchararray"/>
- <!-- column-name type -->
- <column type="gint"/>
- <!-- column-name icon-name -->
- <column type="gchararray"/>
- <!-- column-name primitive -->
- <column type="gint"/>
- <!-- column-name text -->
- <column type="gchararray"/>
- <!-- column-name data -->
- <column type="gpointer"/>
- <!-- column-name emblem-visible -->
- <column type="gboolean"/>
- </columns>
- </object>
<template class="PidginStatusBox" parent="GtkBox">
<property name="focusable">1</property>
<property name="orientation">vertical</property>
<child>
- <object class="GtkComboBox" id="combo">
+ <object class="GtkMenuButton" id="button">
<property name="focusable">1</property>
- <property name="model">model</property>
- <property name="id-column">0</property>
- <signal name="changed" handler="pidgin_status_box_combo_changed_cb" object="PidginStatusBox" swapped="no"/>
- <child>
- <object class="GtkCellRendererPixbuf" id="icon"/>
- <attributes>
- <attribute name="icon-name">2</attribute>
- </attributes>
- </child>
- <child>
- <object class="GtkCellRendererText" id="text"/>
- <attributes>
- <attribute name="markup">4</attribute>
- </attributes>
- </child>
- <child>
- <object class="GtkCellRendererPixbuf" id="emblem">
- <property name="icon-name">document-save</property>
- </object>
- <attributes>
- <attribute name="visible">6</attribute>
- </attributes>
- </child>
+ <property name="always-show-arrow">1</property>
+ <property name="child">
+ <object class="PidginStatusDisplay" id="display"/>
+ </property>
+ <property name="menu-model">menu</property>
</object>
</child>
</template>
+ <menu id="menu">
+ <section id="primitives">
+ <!-- NOTE: labels are automatically set for primitives from the target primitive. -->
+ <item>
+ <attribute name="action">status.set-primitive</attribute>
+ <attribute name="target">available</attribute>
+ <attribute name="custom">available</attribute>
+ </item>
+ <item>
+ <attribute name="action">status.set-primitive</attribute>
+ <attribute name="target">away</attribute>
+ <attribute name="custom">away</attribute>
+ </item>
+ <item>
+ <attribute name="action">status.set-primitive</attribute>
+ <attribute name="target">unavailable</attribute>
+ <attribute name="custom">unavailable</attribute>
+ </item>
+ <item>
+ <attribute name="action">status.set-primitive</attribute>
+ <attribute name="target">invisible</attribute>
+ <attribute name="custom">invisible</attribute>
+ </item>
+ <item>
+ <attribute name="action">status.set-primitive</attribute>
+ <attribute name="target">offline</attribute>
+ <attribute name="custom">offline</attribute>
+ </item>
+ </section>
+ <section id="saved_statuses"/>
+ <section id="actions">
+ <item>
+ <attribute name="label" translatable="1">New Status...</attribute>
+ <attribute name="action">app.new-status</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="1">Saved Statuses...</attribute>
+ <attribute name="action">app.status-manager</attribute>
+ </item>
+ </section>
+ </menu>
</interface>