contacts.c (7663B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2021--2024 GNUnet e.V. 4 5 GNUnet is free software: you can redistribute it and/or modify it 6 under the terms of the GNU Affero General Public License as published 7 by the Free Software Foundation, either version 3 of the License, 8 or (at your option) any later version. 9 10 GNUnet is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 SPDX-License-Identifier: AGPL3.0-or-later 19 */ 20 /* 21 * @author Tobias Frisch 22 * @file ui/contacts.c 23 */ 24 25 #include "contacts.h" 26 27 #include "contact_entry.h" 28 #include "../application.h" 29 #include "../ui.h" 30 31 static void 32 handle_close_button_click(UNUSED GtkButton *button, 33 gpointer user_data) 34 { 35 g_assert(user_data); 36 37 GtkDialog *dialog = GTK_DIALOG(user_data); 38 gtk_window_close(GTK_WINDOW(dialog)); 39 } 40 41 static gboolean 42 _open_new_contact_dialog(gpointer user_data) 43 { 44 g_assert(user_data); 45 46 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 47 48 ui_new_contact_dialog_init(app, &(app->ui.new_contact)); 49 50 gtk_widget_show(GTK_WIDGET(app->ui.new_contact.dialog)); 51 return FALSE; 52 } 53 54 static void 55 handle_contacts_listbox_row_activated(UNUSED GtkListBox* listbox, 56 GtkListBoxRow* row, 57 gpointer user_data) 58 { 59 g_assert((row) && (user_data)); 60 61 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 62 63 if (!gtk_list_box_row_get_selectable(row)) 64 { 65 util_idle_add(G_SOURCE_FUNC(_open_new_contact_dialog), app); 66 goto close_dialog; 67 } 68 69 struct GNUNET_CHAT_Contact *contact = (struct GNUNET_CHAT_Contact*) ( 70 g_object_get_qdata(G_OBJECT(row), app->quarks.data) 71 ); 72 73 if (!contact) 74 goto close_dialog; 75 76 application_chat_lock(app); 77 const gboolean closing = ( 78 (!GNUNET_CHAT_contact_get_key(contact)) || 79 (GNUNET_YES == GNUNET_CHAT_contact_is_owned(contact)) 80 ); 81 application_chat_unlock(app); 82 83 if (closing) 84 goto close_dialog; 85 86 application_chat_lock(app); 87 struct GNUNET_CHAT_Context *context = GNUNET_CHAT_contact_get_context( 88 contact 89 ); 90 application_chat_unlock(app); 91 92 if (!context) 93 goto close_dialog; 94 95 application_chat_lock(app); 96 if (GNUNET_SYSERR == GNUNET_CHAT_context_get_status(context)) 97 GNUNET_CHAT_context_request(context); 98 application_chat_unlock(app); 99 100 close_dialog: 101 gtk_window_close(GTK_WINDOW(app->ui.contacts.dialog)); 102 } 103 104 struct FilterTags 105 { 106 const gchar *filter; 107 gboolean matching; 108 }; 109 110 static enum GNUNET_GenericReturnValue 111 _iterate_contact_tags(void *cls, 112 struct GNUNET_CHAT_Contact *contact, 113 const char *tag) 114 { 115 g_assert((cls) && (contact) && (tag)); 116 117 struct FilterTags *filterTags = (struct FilterTags*) cls; 118 119 gchar *_tag = g_locale_to_utf8(tag, -1, NULL, NULL, NULL); 120 if (!_tag) 121 return GNUNET_YES; 122 123 if (g_strstr_len(_tag, -1, filterTags->filter)) 124 filterTags->matching = TRUE; 125 126 g_free(_tag); 127 return filterTags->matching? GNUNET_NO : GNUNET_YES; 128 } 129 130 static gboolean 131 handle_contacts_listbox_filter_func(GtkListBoxRow *row, 132 gpointer user_data) 133 { 134 g_assert((row) && (user_data)); 135 136 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 137 138 if (!gtk_list_box_row_get_selectable(row)) 139 return TRUE; 140 141 const gchar *filter = gtk_entry_get_text( 142 GTK_ENTRY(app->ui.contacts.contact_search_entry) 143 ); 144 145 if (!filter) 146 return TRUE; 147 148 UI_CONTACT_ENTRY_Handle *entry = (UI_CONTACT_ENTRY_Handle*) ( 149 g_object_get_qdata(G_OBJECT(row), app->quarks.ui) 150 ); 151 152 if (!entry) 153 return FALSE; 154 155 const gchar *name = gtk_label_get_text(entry->title_label); 156 157 gboolean result = FALSE; 158 159 if (name) 160 result |= g_str_match_string(filter, name, TRUE); 161 162 if (('#' == *filter) && (entry->contact)) 163 { 164 struct FilterTags filterTags; 165 filterTags.filter = &(filter[1]); 166 filterTags.matching = FALSE; 167 168 application_chat_lock(app); 169 170 GNUNET_CHAT_contact_iterate_tags( 171 entry->contact, 172 _iterate_contact_tags, 173 &filterTags 174 ); 175 176 application_chat_unlock(app); 177 178 result |= filterTags.matching; 179 } 180 181 return result; 182 } 183 184 static void 185 handle_contact_search_entry_search_changed(UNUSED GtkSearchEntry* search_entry, 186 gpointer user_data) 187 { 188 g_assert(user_data); 189 190 GtkListBox *listbox = GTK_LIST_BOX(user_data); 191 192 gtk_list_box_invalidate_filter(listbox); 193 } 194 195 static void 196 handle_dialog_destroy(UNUSED GtkWidget *window, 197 gpointer user_data) 198 { 199 g_assert(user_data); 200 201 ui_contacts_dialog_cleanup((UI_CONTACTS_Handle*) user_data); 202 } 203 204 static enum GNUNET_GenericReturnValue 205 _iterate_contacts(void *cls, 206 UNUSED struct GNUNET_CHAT_Handle *handle, 207 struct GNUNET_CHAT_Contact *contact) 208 { 209 g_assert((cls) && (contact)); 210 211 if (GNUNET_YES == GNUNET_CHAT_contact_is_owned(contact)) 212 return GNUNET_YES; 213 214 MESSENGER_Application *app = (MESSENGER_Application*) cls; 215 216 UI_CONTACT_ENTRY_Handle *entry = ui_contact_entry_new(app); 217 ui_contact_entry_set_contact(entry, contact); 218 219 gtk_list_box_prepend( 220 app->ui.contacts.contacts_listbox, 221 entry->entry_box 222 ); 223 224 GtkListBoxRow *row = GTK_LIST_BOX_ROW( 225 gtk_widget_get_parent(entry->entry_box) 226 ); 227 228 g_object_set_qdata(G_OBJECT(row), app->quarks.data, contact); 229 230 g_object_set_qdata_full( 231 G_OBJECT(row), 232 app->quarks.ui, 233 entry, 234 (GDestroyNotify) ui_contact_entry_delete 235 ); 236 237 return GNUNET_YES; 238 } 239 240 void 241 ui_contacts_dialog_init(MESSENGER_Application *app, 242 UI_CONTACTS_Handle *handle) 243 { 244 g_assert((app) && (handle)); 245 246 handle->builder = ui_builder_from_resource( 247 application_get_resource_path(app, "ui/contacts.ui") 248 ); 249 250 handle->dialog = GTK_DIALOG( 251 gtk_builder_get_object(handle->builder, "contacts_dialog") 252 ); 253 254 gtk_window_set_transient_for( 255 GTK_WINDOW(handle->dialog), 256 GTK_WINDOW(app->ui.messenger.main_window) 257 ); 258 259 handle->contact_search_entry = GTK_SEARCH_ENTRY( 260 gtk_builder_get_object(handle->builder, "contact_search_entry") 261 ); 262 263 handle->contacts_listbox = GTK_LIST_BOX( 264 gtk_builder_get_object(handle->builder, "contacts_listbox") 265 ); 266 267 gtk_list_box_set_filter_func( 268 handle->contacts_listbox, 269 handle_contacts_listbox_filter_func, 270 app, 271 NULL 272 ); 273 274 g_signal_connect( 275 handle->contact_search_entry, 276 "search-changed", 277 G_CALLBACK(handle_contact_search_entry_search_changed), 278 handle->contacts_listbox 279 ); 280 281 g_signal_connect( 282 handle->contacts_listbox, 283 "row-activated", 284 G_CALLBACK(handle_contacts_listbox_row_activated), 285 app 286 ); 287 288 handle->close_button = GTK_BUTTON( 289 gtk_builder_get_object(handle->builder, "close_button") 290 ); 291 292 g_signal_connect( 293 handle->close_button, 294 "clicked", 295 G_CALLBACK(handle_close_button_click), 296 handle->dialog 297 ); 298 299 g_signal_connect( 300 handle->dialog, 301 "destroy", 302 G_CALLBACK(handle_dialog_destroy), 303 handle 304 ); 305 306 GNUNET_CHAT_iterate_contacts( 307 app->chat.messenger.handle, 308 _iterate_contacts, 309 app 310 ); 311 312 gtk_list_box_invalidate_filter(handle->contacts_listbox); 313 } 314 315 void 316 ui_contacts_dialog_cleanup(UI_CONTACTS_Handle *handle) 317 { 318 g_assert(handle); 319 320 if (handle->builder) 321 g_object_unref(handle->builder); 322 323 memset(handle, 0, sizeof(*handle)); 324 }