pidgin/pidginstatusbox.c

changeset 42526
c73c0cf2b554
parent 42525
56a4fd2844ae
child 42527
1954265c38f0
equal deleted inserted replaced
42525:56a4fd2844ae 42526:c73c0cf2b554
1 /*
2 * Pidgin - Internet Messenger
3 * Copyright (C) Pidgin Developers <devel@pidgin.im>
4 *
5 * Pidgin is the legal property of its developers, whose names are too numerous
6 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * source distribution.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, see <https://www.gnu.org/licenses/>.
21 */
22
23 #include <glib/gi18n-lib.h>
24
25 #include <gtk/gtk.h>
26
27 #include <purple.h>
28
29 #include "pidginstatusbox.h"
30 #include "pidginiconname.h"
31 #include "pidginstatusdisplay.h"
32
33 #define SAVEDSTATUS_FORMAT "savedstatus_%lu"
34
35 struct _PidginStatusBox {
36 GtkBox parent;
37
38 GtkWidget *button;
39 PidginStatusDisplay *display;
40 GtkPopover *popover;
41
42 GMenu *primitives;
43 GMenu *saved_statuses;
44 GList *custom_widgets;
45 };
46
47 G_DEFINE_TYPE(PidginStatusBox, pidgin_status_box, GTK_TYPE_BOX)
48
49 /******************************************************************************
50 * Helpers
51 *****************************************************************************/
52 static GtkWidget *
53 pidgin_status_box_make_primitive_widget(const char *action, const char *id) {
54 GtkWidget *button = NULL;
55 GtkWidget *display = NULL;
56 PurpleStatusPrimitive primitive = PURPLE_STATUS_UNSET;
57
58 primitive = purple_primitive_get_type_from_id(id);
59 display = pidgin_status_display_new_for_primitive(primitive);
60
61 button = gtk_button_new();
62 gtk_widget_add_css_class(button, "flat");
63 gtk_button_set_child(GTK_BUTTON(button), display);
64
65 gtk_actionable_set_action_name(GTK_ACTIONABLE(button), action);
66 gtk_actionable_set_action_target(GTK_ACTIONABLE(button),
67 (const char *)G_VARIANT_TYPE_INT32,
68 primitive);
69
70 return button;
71 }
72
73 static void
74 pidgin_status_box_populate_primitives(PidginStatusBox *status_box) {
75 GtkPopoverMenu *popover = GTK_POPOVER_MENU(status_box->popover);
76 GMenuModel *menu = G_MENU_MODEL(status_box->primitives);
77 gint n_items = 0;
78
79 n_items = g_menu_model_get_n_items(menu);
80 for(gint index = 0; index < n_items; index++) {
81 GtkWidget *button = NULL;
82 char *action = NULL;
83 char *target = NULL;
84 char *custom_id = NULL;
85
86 g_menu_model_get_item_attribute(menu, index, G_MENU_ATTRIBUTE_ACTION,
87 "s", &action);
88 g_menu_model_get_item_attribute(menu, index, G_MENU_ATTRIBUTE_TARGET,
89 "s", &target);
90 g_menu_model_get_item_attribute(menu, index, "custom", "s", &custom_id);
91
92 button = pidgin_status_box_make_primitive_widget(action, target);
93 gtk_popover_menu_add_child(popover, button, custom_id);
94
95 g_free(action);
96 g_free(target);
97 g_free(custom_id);
98 }
99 }
100
101 static char *
102 pidgin_status_box_make_savedstatus_widget(PurpleSavedStatus *saved_status,
103 GtkWidget **widget)
104 {
105 GtkWidget *button = NULL;
106 GtkWidget *display = NULL;
107 time_t creation_time = 0;
108
109 display = pidgin_status_display_new_for_saved_status(saved_status);
110
111 if(!purple_savedstatus_is_transient(saved_status)) {
112 GtkWidget *image = gtk_image_new_from_icon_name("document-save");
113 gtk_widget_set_halign(image, GTK_ALIGN_END);
114 gtk_widget_set_hexpand(image, TRUE);
115 gtk_box_append(GTK_BOX(display), image);
116 }
117
118 button = gtk_button_new();
119 gtk_widget_add_css_class(button, "flat");
120 gtk_button_set_child(GTK_BUTTON(button), display);
121
122 creation_time = purple_savedstatus_get_creation_time(saved_status);
123 gtk_actionable_set_action_name(GTK_ACTIONABLE(button), "status.set-saved");
124 gtk_actionable_set_action_target(GTK_ACTIONABLE(button),
125 (const char *)G_VARIANT_TYPE_INT64,
126 creation_time);
127 *widget = button;
128
129 return g_strdup_printf(SAVEDSTATUS_FORMAT, creation_time);
130 }
131
132 static void
133 pidgin_status_box_populate_saved_statuses(PidginStatusBox *status_box)
134 {
135 GtkPopoverMenu *popover_menu = NULL;
136 GMenu *menu = NULL;
137 GList *list, *cur;
138
139 list = purple_savedstatuses_get_popular(6);
140 if (list == NULL) {
141 /* Odd... oh well, nothing we can do about it. */
142 return;
143 }
144
145 popover_menu = GTK_POPOVER_MENU(status_box->popover);
146 menu = status_box->saved_statuses;
147 for(cur = list; cur != NULL; cur = cur->next) {
148 PurpleSavedStatus *saved = cur->data;
149 GtkWidget *widget = NULL;
150 char *id = NULL;
151 GMenuItem *item = NULL;
152
153 id = pidgin_status_box_make_savedstatus_widget(saved, &widget);
154 item = g_menu_item_new(NULL, NULL);
155 g_menu_item_set_attribute(item, "custom", "s", id);
156 g_menu_append_item(menu, item);
157 g_object_unref(item);
158
159 gtk_popover_menu_add_child(popover_menu, widget, id);
160 status_box->custom_widgets = g_list_prepend(status_box->custom_widgets,
161 widget);
162
163 g_free(id);
164 }
165
166 g_list_free(list);
167 }
168
169 /******************************************************************************
170 * Callbacks
171 *****************************************************************************/
172 static void
173 pidgin_status_box_set_primitive(G_GNUC_UNUSED GSimpleAction *action,
174 GVariant *parameter, gpointer data)
175 {
176 PidginStatusBox *status_box = data;
177 PurpleSavedStatus *saved_status = NULL;
178 PurpleStatusPrimitive primitive;
179
180 gtk_menu_button_popdown(GTK_MENU_BUTTON(status_box->button));
181
182 if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT32)) {
183 g_critical("status.set-primitive action parameter is of incorrect type %s",
184 g_variant_get_type_string(parameter));
185 return;
186 }
187
188 primitive = g_variant_get_int32(parameter);
189
190 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, NULL);
191 if(saved_status == NULL) {
192 saved_status = purple_savedstatus_new(NULL, primitive);
193 }
194
195 if(saved_status != NULL) {
196 if(saved_status != purple_savedstatus_get_current()) {
197 purple_savedstatus_activate(saved_status);
198 }
199 }
200 }
201
202 static void
203 pidgin_status_box_set_saved_status(G_GNUC_UNUSED GSimpleAction *action,
204 GVariant *parameter, gpointer data)
205 {
206 PidginStatusBox *status_box = data;
207 PurpleSavedStatus *saved_status = NULL;
208 time_t creation_time = 0;
209
210 gtk_menu_button_popdown(GTK_MENU_BUTTON(status_box->button));
211
212 if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT64)) {
213 g_critical("status.set-saved action parameter is of incorrect type %s",
214 g_variant_get_type_string(parameter));
215 return;
216 }
217
218 creation_time = (time_t)g_variant_get_int64(parameter);
219 saved_status = purple_savedstatus_find_by_creation_time(creation_time);
220
221 if(saved_status != NULL) {
222 if(saved_status != purple_savedstatus_get_current()) {
223 purple_savedstatus_activate(saved_status);
224 }
225 }
226 }
227
228 static void
229 pidgin_status_box_savedstatus_changed_cb(PurpleSavedStatus *now,
230 G_GNUC_UNUSED PurpleSavedStatus *old,
231 gpointer data)
232 {
233 PidginStatusBox *status_box = data;
234
235 /* If we don't have a status, we have to bail. */
236 if(now == NULL) {
237 return;
238 }
239
240 pidgin_status_display_set_saved_status(status_box->display, now);
241 }
242
243 static void
244 pidgin_status_box_remove_custom_widget(GtkWidget *widget, gpointer user_data) {
245 GtkPopoverMenu *popover_menu = user_data;
246
247 gtk_popover_menu_remove_child(popover_menu, widget);
248 }
249
250 static void
251 pidgin_status_box_savedstatus_updated_cb(G_GNUC_UNUSED PurpleSavedStatus *status,
252 gpointer data)
253 {
254 PidginStatusBox *status_box = data;
255
256 g_list_foreach(status_box->custom_widgets,
257 (GFunc)pidgin_status_box_remove_custom_widget,
258 status_box->popover);
259 g_clear_list(&status_box->custom_widgets, NULL);
260 g_menu_remove_all(status_box->saved_statuses);
261
262 pidgin_status_box_populate_saved_statuses(status_box);
263 }
264
265 /******************************************************************************
266 * GObject Implementation
267 *****************************************************************************/
268 static void
269 pidgin_status_box_finalize(GObject *obj) {
270 PidginStatusBox *status_box = PIDGIN_STATUS_BOX(obj);
271
272 purple_signals_disconnect_by_handle(status_box);
273
274 g_clear_list(&status_box->custom_widgets, NULL);
275
276 G_OBJECT_CLASS(pidgin_status_box_parent_class)->finalize(obj);
277 }
278
279 static void
280 pidgin_status_box_init(PidginStatusBox *status_box) {
281 gpointer handle;
282 GSimpleActionGroup *action_group = NULL;
283 GActionEntry actions[] = {
284 {
285 .name = "set-primitive",
286 .activate = pidgin_status_box_set_primitive,
287 .parameter_type = (const char *)G_VARIANT_TYPE_INT32,
288 }, {
289 .name = "set-saved",
290 .activate = pidgin_status_box_set_saved_status,
291 .parameter_type = (const char *)G_VARIANT_TYPE_INT64,
292 },
293 };
294
295 gtk_widget_init_template(GTK_WIDGET(status_box));
296
297 action_group = g_simple_action_group_new();
298 g_action_map_add_action_entries(G_ACTION_MAP(action_group),
299 actions, G_N_ELEMENTS(actions),
300 status_box);
301 gtk_widget_insert_action_group(GTK_WIDGET(status_box), "status",
302 G_ACTION_GROUP(action_group));
303
304 status_box->popover = gtk_menu_button_get_popover(GTK_MENU_BUTTON(status_box->button));
305 gtk_popover_set_has_arrow(status_box->popover, FALSE);
306
307 pidgin_status_box_populate_primitives(status_box);
308 pidgin_status_box_populate_saved_statuses(status_box);
309
310 handle = purple_savedstatuses_get_handle();
311 purple_signal_connect(handle, "savedstatus-changed", status_box,
312 G_CALLBACK(pidgin_status_box_savedstatus_changed_cb),
313 status_box);
314 purple_signal_connect(handle, "savedstatus-added", status_box,
315 G_CALLBACK(pidgin_status_box_savedstatus_updated_cb),
316 status_box);
317 purple_signal_connect(handle, "savedstatus-deleted", status_box,
318 G_CALLBACK(pidgin_status_box_savedstatus_updated_cb),
319 status_box);
320 purple_signal_connect(handle, "savedstatus-modified", status_box,
321 G_CALLBACK(pidgin_status_box_savedstatus_updated_cb),
322 status_box);
323 }
324
325 static void
326 pidgin_status_box_constructed(GObject *obj) {
327 PidginStatusBox *status_box = PIDGIN_STATUS_BOX(obj);
328 PurpleSavedStatus *status = NULL;
329
330 G_OBJECT_CLASS(pidgin_status_box_parent_class)->constructed(obj);
331
332 status = purple_savedstatus_get_current();
333 pidgin_status_display_set_saved_status(status_box->display, status);
334 }
335
336 static void
337 pidgin_status_box_class_init(PidginStatusBoxClass *klass) {
338 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
339 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
340
341 obj_class->finalize = pidgin_status_box_finalize;
342 obj_class->constructed = pidgin_status_box_constructed;
343
344 gtk_widget_class_set_template_from_resource(
345 widget_class,
346 "/im/pidgin/Pidgin3/Status/box.ui"
347 );
348
349 gtk_widget_class_bind_template_child(widget_class, PidginStatusBox,
350 button);
351 gtk_widget_class_bind_template_child(widget_class, PidginStatusBox,
352 display);
353 gtk_widget_class_bind_template_child(widget_class, PidginStatusBox,
354 primitives);
355 gtk_widget_class_bind_template_child(widget_class, PidginStatusBox,
356 saved_statuses);
357 }
358
359 /******************************************************************************
360 * Public API
361 *****************************************************************************/
362 GtkWidget *
363 pidgin_status_box_new(void) {
364 return g_object_new(PIDGIN_TYPE_STATUS_BOX, NULL);
365 }

mercurial