| 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 } |
|