pidgin/pidginstatusmanager.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 "pidginstatusmanager.h"
24
25 #include "pidginiconname.h"
26 #include "pidginstatuseditor.h"
27
28 enum {
29 RESPONSE_USE,
30 RESPONSE_ADD,
31 RESPONSE_MODIFY,
32 RESPONSE_REMOVE
33 };
34
35 enum {
36 COLUMN_TITLE,
37 COLUMN_ICON_NAME,
38 COLUMN_TYPE,
39 COLUMN_MESSAGE,
40 COLUMN_STATUS,
41 COLUMN_EDITOR
42 };
43
44 struct _PidginStatusManager {
45 GtkDialog parent;
46
47 GListStore *model;
48 GtkSingleSelection *selection;
49
50 GtkWidget *use_button;
51 GtkWidget *modify_button;
52 GtkWidget *remove_button;
53 };
54
55 G_DEFINE_TYPE(PidginStatusManager, pidgin_status_manager, GTK_TYPE_DIALOG)
56
57 /* Ugh, prototypes :,( */
58 static void pidgin_status_editor_destroy_cb(GtkWidget *widget, gpointer data);
59
60 /******************************************************************************
61 * Helpers
62 *****************************************************************************/
63 static void
64 pidgin_status_manager_show_editor(PidginStatusManager *manager) {
65 GObject *wrapper = NULL;
66 PurpleSavedStatus *status = NULL;
67 GtkWidget *editor = NULL;
68
69 wrapper = gtk_single_selection_get_selected_item(manager->selection);
70 status = g_object_get_data(wrapper, "savedstatus");
71 editor = g_object_get_data(wrapper, "editor");
72
73 if(status == NULL) {
74 return;
75 }
76
77 if(!PIDGIN_IS_STATUS_EDITOR(editor)) {
78 editor = pidgin_status_editor_new(status);
79
80 gtk_window_set_transient_for(GTK_WINDOW(editor), GTK_WINDOW(manager));
81
82 g_object_set_data(wrapper, "editor", editor);
83 g_signal_connect_object(editor, "destroy",
84 G_CALLBACK(pidgin_status_editor_destroy_cb),
85 manager, 0);
86
87 gtk_widget_set_visible(editor, TRUE);
88 } else {
89 gtk_window_present_with_time(GTK_WINDOW(editor), GDK_CURRENT_TIME);
90 }
91 }
92
93 static void
94 pidgin_status_manager_remove_selected(PidginStatusManager *manager) {
95 GObject *wrapper = NULL;
96 PurpleSavedStatus *status = NULL;
97 GtkWidget *editor = NULL;
98
99 wrapper = gtk_single_selection_get_selected_item(manager->selection);
100 status = g_object_get_data(wrapper, "savedstatus");
101 editor = g_object_get_data(wrapper, "editor");
102
103 if(GTK_IS_WIDGET(editor)) {
104 gtk_window_destroy(GTK_WINDOW(editor));
105 }
106
107 purple_savedstatus_delete_by_status(status);
108 }
109
110 static PurpleSavedStatus *
111 pidgin_status_manager_get_selected_status(PidginStatusManager *manager) {
112 GObject *wrapper = NULL;
113 PurpleSavedStatus *status = NULL;
114
115 wrapper = gtk_single_selection_get_selected_item(manager->selection);
116 status = g_object_get_data(wrapper, "savedstatus");
117
118 return status;
119 }
120
121 static void
122 pidgin_status_manager_add(PidginStatusManager *manager,
123 PurpleSavedStatus *status)
124 {
125 GObject *wrapper = NULL;
126 PurpleStatusPrimitive primitive;
127 gchar *message = NULL;
128 const char *type = NULL;
129
130 message = purple_markup_strip_html(purple_savedstatus_get_message(status));
131
132 primitive = purple_savedstatus_get_primitive_type(status);
133 type = purple_primitive_get_name_from_type(primitive);
134
135 /* PurpleSavedStatus is a boxed type, so it can't be put in a GListModel;
136 * instead create a wrapper GObject instance to hold its information. */
137 wrapper = g_object_new(G_TYPE_OBJECT, NULL);
138 g_object_set_data(wrapper, "savedstatus", status);
139 g_object_set_data_full(wrapper, "title",
140 g_strdup(purple_savedstatus_get_title(status)),
141 g_free);
142 g_object_set_data_full(wrapper, "type", g_strdup(type), g_free);
143 g_object_set_data_full(wrapper, "message", g_strdup(message), g_free);
144
145 g_list_store_append(manager->model, wrapper);
146
147 g_free(message);
148 g_object_unref(wrapper);
149 }
150
151 static void
152 pidgin_status_manager_populate_helper(gpointer data, gpointer user_data) {
153 PidginStatusManager *manager = user_data;
154 PurpleSavedStatus *status = data;
155
156 if(!purple_savedstatus_is_transient(status)) {
157 pidgin_status_manager_add(manager, status);
158 }
159 }
160
161 static void
162 pidgin_status_manager_refresh(PidginStatusManager *manager) {
163 GList *statuses = NULL;
164
165 g_list_store_remove_all(manager->model);
166
167 statuses = purple_savedstatuses_get_all();
168 g_list_foreach(statuses, pidgin_status_manager_populate_helper, manager);
169 }
170
171 /******************************************************************************
172 * Callbacks
173 *****************************************************************************/
174 static void
175 pidgin_status_manager_response_cb(GtkDialog *dialog, gint response_id,
176 gpointer data)
177 {
178 PidginStatusManager *manager = data;
179 PurpleSavedStatus *status = NULL;
180 GtkWidget *editor = NULL;
181
182 switch(response_id) {
183 case RESPONSE_USE:
184 status = pidgin_status_manager_get_selected_status(manager);
185
186 purple_savedstatus_activate(status);
187
188 break;
189 case RESPONSE_ADD:
190 editor = pidgin_status_editor_new(NULL);
191 gtk_window_set_transient_for(GTK_WINDOW(editor),
192 GTK_WINDOW(manager));
193 gtk_widget_set_visible(editor, TRUE);
194 break;
195 case RESPONSE_MODIFY:
196 pidgin_status_manager_show_editor(manager);
197
198 break;
199 case RESPONSE_REMOVE:
200 pidgin_status_manager_remove_selected(manager);
201 break;
202 case GTK_RESPONSE_CLOSE:
203 case GTK_RESPONSE_DELETE_EVENT:
204 gtk_window_destroy(GTK_WINDOW(dialog));
205 break;
206 }
207 }
208
209 static PurpleStatusPrimitive
210 pidgin_status_manager_lookup_primitive_cb(G_GNUC_UNUSED GObject *self,
211 GObject *wrapper,
212 G_GNUC_UNUSED gpointer data)
213 {
214 PurpleStatusPrimitive primitive = PURPLE_STATUS_UNSET;
215
216 if(G_IS_OBJECT(wrapper)) {
217 PurpleSavedStatus *status = g_object_get_data(wrapper, "savedstatus");
218 primitive = purple_savedstatus_get_primitive_type(status);
219 }
220
221 return primitive;
222 }
223
224 static char *
225 pidgin_status_manager_sort_data_cb(GObject *wrapper, const char *name,
226 G_GNUC_UNUSED gpointer data)
227 {
228 const char *value = NULL;
229
230 if(G_IS_OBJECT(wrapper)) {
231 value = g_object_get_data(wrapper, name);
232 }
233
234 /* NOTE: Most GTK widget properties don't care if you return NULL, but the
235 * GtkStringSorter does some string comparisons without checking for NULL,
236 * so we need to ensure that non-NULL is returned to prevent runtime
237 * warnings. */
238 return g_strdup(value ? value : "");
239 }
240
241 /* A closure from within a GtkBuilderListItemFactory passes an extra first
242 * argument, so we need to drop that to re-use the above callback. */
243 static char *
244 pidgin_status_manager_lookup_text_data_cb(G_GNUC_UNUSED GObject *self,
245 GObject *wrapper, const char *name,
246 gpointer data)
247 {
248 return pidgin_status_manager_sort_data_cb(wrapper, name, data);
249 }
250
251 static void
252 pidgin_status_manager_row_activated_cb(G_GNUC_UNUSED GtkColumnView *self,
253 guint position, gpointer data)
254 {
255 PidginStatusManager *manager = data;
256
257 gtk_single_selection_set_selected(manager->selection, position);
258 pidgin_status_manager_show_editor(manager);
259 }
260
261 static void
262 pidgin_status_manager_selection_changed_cb(G_GNUC_UNUSED GObject *object,
263 G_GNUC_UNUSED GParamSpec *pspec,
264 gpointer data)
265 {
266 PidginStatusManager *manager = data;
267 gboolean sensitive = TRUE;
268
269 if(g_list_model_get_n_items(G_LIST_MODEL(manager->model)) == 0) {
270 sensitive = FALSE;
271 }
272
273 gtk_widget_set_sensitive(manager->use_button, sensitive);
274 gtk_widget_set_sensitive(manager->modify_button, sensitive);
275
276 /* Only enable the remove button if the currently selected row is not the
277 * currently active status.
278 */
279 if(sensitive) {
280 PurpleSavedStatus *status = NULL;
281
282 status = pidgin_status_manager_get_selected_status(manager);
283
284 sensitive = status != purple_savedstatus_get_current();
285 }
286
287 gtk_widget_set_sensitive(manager->remove_button, sensitive);
288 }
289
290 static void
291 pidgin_status_manager_savedstatus_changed_cb(PurpleSavedStatus *new_status,
292 G_GNUC_UNUSED PurpleSavedStatus *old_status,
293 gpointer data)
294 {
295 PidginStatusManager *manager = data;
296 PurpleSavedStatus *selected = NULL;
297
298 /* Disable the remove button if the selected status is the currently active
299 * status.
300 */
301 selected = pidgin_status_manager_get_selected_status(manager);
302 if(selected != NULL) {
303 gboolean sensitive = selected != new_status;
304
305 gtk_widget_set_sensitive(manager->remove_button, sensitive);
306 }
307 }
308
309 static void
310 pidgin_status_manager_savedstatus_updated_cb(G_GNUC_UNUSED PurpleSavedStatus *status,
311 gpointer data)
312 {
313 PidginStatusManager *manager = data;
314
315 pidgin_status_manager_refresh(manager);
316 }
317
318 static void
319 pidgin_status_editor_destroy_cb(GtkWidget *widget, gpointer data) {
320 PidginStatusManager *manager = data;
321 GListModel *model = G_LIST_MODEL(manager->model);
322 guint n_items = 0;
323
324 n_items = g_list_model_get_n_items(model);
325 for(guint index = 0; index < n_items; index++) {
326 GObject *wrapper = NULL;
327 GtkWidget *editor = NULL;
328
329 wrapper = g_list_model_get_item(model, index);
330 editor = g_object_get_data(wrapper, "editor");
331
332 /* Check if editor is the widget being destroyed. */
333 if(editor == widget) {
334 /* It is, so set it back to NULL to remove it from the wrapper. */
335 g_object_set_data(wrapper, "editor", NULL);
336 g_object_unref(wrapper);
337
338 break;
339 }
340
341 g_object_unref(wrapper);
342 }
343 }
344
345 /******************************************************************************
346 * GObject Implementation
347 *****************************************************************************/
348 static void
349 pidgin_status_manager_finalize(GObject *obj) {
350 purple_signals_disconnect_by_handle(obj);
351
352 G_OBJECT_CLASS(pidgin_status_manager_parent_class)->finalize(obj);
353 }
354
355 static void
356 pidgin_status_manager_init(PidginStatusManager *manager) {
357 gpointer handle = NULL;
358
359 gtk_widget_init_template(GTK_WIDGET(manager));
360
361 pidgin_status_manager_refresh(manager);
362
363 handle = purple_savedstatuses_get_handle();
364 purple_signal_connect(handle, "savedstatus-changed", manager,
365 G_CALLBACK(pidgin_status_manager_savedstatus_changed_cb),
366 manager);
367 purple_signal_connect(handle, "savedstatus-added", manager,
368 G_CALLBACK(pidgin_status_manager_savedstatus_updated_cb),
369 manager);
370 purple_signal_connect(handle, "savedstatus-deleted", manager,
371 G_CALLBACK(pidgin_status_manager_savedstatus_updated_cb),
372 manager);
373 purple_signal_connect(handle, "savedstatus-modified", manager,
374 G_CALLBACK(pidgin_status_manager_savedstatus_updated_cb),
375 manager);
376 }
377
378 static void
379 pidgin_status_manager_class_init(PidginStatusManagerClass *klass) {
380 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
381 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
382
383 obj_class->finalize = pidgin_status_manager_finalize;
384
385 gtk_widget_class_set_template_from_resource(
386 widget_class,
387 "/im/pidgin/Pidgin3/Status/manager.ui"
388 );
389
390 gtk_widget_class_bind_template_child(widget_class, PidginStatusManager,
391 model);
392 gtk_widget_class_bind_template_child(widget_class, PidginStatusManager,
393 selection);
394 gtk_widget_class_bind_template_child(widget_class, PidginStatusManager,
395 use_button);
396 gtk_widget_class_bind_template_child(widget_class, PidginStatusManager,
397 modify_button);
398 gtk_widget_class_bind_template_child(widget_class, PidginStatusManager,
399 remove_button);
400
401 gtk_widget_class_bind_template_callback(widget_class,
402 pidgin_status_manager_response_cb);
403 gtk_widget_class_bind_template_callback(widget_class,
404 pidgin_status_manager_lookup_primitive_cb);
405 gtk_widget_class_bind_template_callback(widget_class,
406 pidgin_status_manager_lookup_text_data_cb);
407 gtk_widget_class_bind_template_callback(widget_class,
408 pidgin_status_manager_sort_data_cb);
409 gtk_widget_class_bind_template_callback(widget_class,
410 pidgin_status_manager_row_activated_cb);
411 gtk_widget_class_bind_template_callback(widget_class,
412 pidgin_status_manager_selection_changed_cb);
413 }
414
415 /******************************************************************************
416 * Public API
417 *****************************************************************************/
418 GtkWidget *
419 pidgin_status_manager_new(void) {
420 return g_object_new(PIDGIN_TYPE_STATUS_MANAGER, NULL);
421 }

mercurial