/* This file is part of GNUnet. Copyright (C) 2010-2014, 2018 GNUnet e.V. GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file src/conversation/gnunet-conversation-gtk_contacts.c * @brief display the address book with the list of known contacts * and launch calls if user activates entry in address book * @author yids * @author hark * @author Christian Grothoff */ #include "gnunet-conversation-gtk.h" #include "gnunet-conversation-gtk_contacts.h" #include "gnunet-conversation-gtk_get_label.h" #include "gnunet-conversation-gtk_phone.h" #include "gnunet-conversation-gtk_zones.h" /** * Columns in the #contacts_liststore. */ enum ContactsListstoreValues { /** * Human-readable name of the label in the zone. */ CONTACTS_LS_NAME = 0, /** * Type of the label (as a `const char *`) */ CONTACTS_LS_TYPE = 1, /** * Value stored under the label (as a `const char *`) */ CONTACTS_LS_VALUE = 2, /** * Is the record public (as a `gboolean`) */ CONTACTS_LS_IS_PUBLIC = 3, /** * Is the 'public' flag editable (once implemented, negation * of #CONTACT_IS_PUBLIC), a `gboolean`. */ CONTACTS_LS_IS_EDITABLE = 4 }; /** * List of contacts (records). */ static GtkListStore *contacts_liststore; /** * Tree model of the contacts treeview, same objects as #contacts_liststore. */ static GtkTreeModel *contacts_treemodel; /** * Monitor to view information in our current zone. */ static struct GNUNET_NAMESTORE_ZoneMonitor *zone_mon; /** * The tree view widget. */ static GtkWidget *contacts_treeview; /** * The "paste" button. */ static GtkWidget *b_paste; /** * A row was activated in the contacts list. Initiate call. * * @param tree_view view where the row was activated * @param path path to the activated element * @param column column that was activated * @param user_data builder context (unused) */ void gnunet_conversation_gtk_contact_list_treeview_row_activated_cb ( GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) { char *address; gchar *type; GtkTreeIter iter; gchar *name; GtkEntry *address_entry; const char *tld; GNUNET_assert (NULL != GCG_ZONES_get_selected_zone (&tld)); if (! gtk_tree_model_get_iter (contacts_treemodel, &iter, path)) { GNUNET_break (0); return; } gtk_tree_model_get (contacts_treemodel, &iter, CONTACTS_LS_NAME, &name, CONTACTS_LS_TYPE, &type, -1); if (0 == strcmp (type, "PKEY")) { GNUNET_asprintf (&address, "phone.%s.%s", name, tld); } else { GNUNET_asprintf (&address, "%s.%s", name, tld); } g_free (name); g_free (type); address_entry = GTK_ENTRY ( GCG_get_main_window_object ("gnunet_conversation_gtk_address_entry")); gtk_entry_set_text (address_entry, address); GNUNET_free (address); } /** * Process a record that was stored or modified the namestore by * adding/modifying/removing it in the liststore. * * @param cls closure * @param zone_key private key of the zone; NULL on disconnect * @param rname label of the records; NULL on disconnect * @param rd_len number of entries in @a rd array, 0 if label was deleted * @param rd array of records with data to store */ static void display_record (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *rname, unsigned int rd_len, const struct GNUNET_GNSRECORD_Data *rd) { GtkTreeIter iter; gboolean do_display; const char *type; gchar *lname; gboolean update; gboolean is_public; char *value; GNUNET_NAMESTORE_zone_monitor_next (zone_mon, 1); do_display = FALSE; value = NULL; for (unsigned int i = 0; i < rd_len; i++) { switch (rd[i].record_type) { case GNUNET_GNSRECORD_TYPE_PKEY: type = "PKEY"; do_display = TRUE; break; case GNUNET_DNSPARSER_TYPE_CNAME: type = "CNAME"; do_display = TRUE; break; case GNUNET_GNSRECORD_TYPE_PHONE: type = "PHONE"; do_display = TRUE; break; default: /* ignore, not useful for conversation */ type = NULL; /* make compiler happy */ break; } if (do_display) { value = GNUNET_GNSRECORD_value_to_string (rd[i].record_type, rd[i].data, rd[i].data_size); if (NULL == value) { GNUNET_break (0); return; } is_public = (0 == (rd[i].flags & GNUNET_GNSRECORD_RF_PRIVATE)); break; } else { /* make compiler happy */ is_public = FALSE; } } /* check if exists, if so, update or remove */ update = FALSE; if (gtk_tree_model_get_iter_first (contacts_treemodel, &iter)) { do { gtk_tree_model_get (contacts_treemodel, &iter, CONTACTS_LS_NAME, &lname, -1); if (0 == strcmp (lname, rname)) { if (! do_display) { /* remove */ gtk_list_store_remove (contacts_liststore, &iter); g_free (lname); GNUNET_free (value); return; } /* update */ update = TRUE; break; } g_free (lname); } while (gtk_tree_model_iter_next (contacts_treemodel, &iter)); } /* insert new record */ if (! do_display) { GNUNET_assert (NULL == value); return; } if (! update) gtk_list_store_append (contacts_liststore, &iter); gtk_list_store_set (contacts_liststore, &iter, CONTACTS_LS_NAME, rname, CONTACTS_LS_TYPE, type, CONTACTS_LS_VALUE, value, CONTACTS_LS_IS_PUBLIC, is_public, CONTACTS_LS_IS_EDITABLE, FALSE /* FIXME: editing to be implemented */, -1); GNUNET_free (value); } /** * Function called once the monitor has caught up with the current * state of the database. Will be called AGAIN after each disconnect * (record monitor called with 'NULL' for zone_key) once we're again * in sync. * * Could be used to optimize visuals if we block GTK updates while the * list is not in sync. * * @param cls closure */ static void unfreeze_view (void *cls) { gtk_widget_show (contacts_treeview); } /** * Function called once the monitor has disconnected from the * database. * * @param cls closure */ static void freeze_view (void *cls) { gtk_list_store_clear (contacts_liststore); gtk_widget_hide (contacts_treeview); } /** * A different zone was selected in the zone toggle bar. Load the * appropriate zone. * * @param widget combobox that was changed, unused * @param user_data builder, unused */ void gnunet_conversation_gtk_contacts_zone_combobox_changed_cb (GtkComboBox *widget, gpointer user_data) { struct GNUNET_IDENTITY_Ego *ego; const struct GNUNET_IDENTITY_PrivateKey *temp_zone_pkey; const char *tld; if (NULL != zone_mon) { GNUNET_NAMESTORE_zone_monitor_stop (zone_mon); zone_mon = NULL; } gtk_list_store_clear (contacts_liststore); ego = GCG_ZONES_get_selected_zone (&tld); if (NULL == ego) { /* ego deselected (likely shutdown) */ gtk_widget_set_sensitive (b_paste, FALSE); return; } gtk_widget_hide (contacts_treeview); gtk_widget_set_sensitive (b_paste, TRUE); temp_zone_pkey = GNUNET_IDENTITY_ego_get_private_key (ego); zone_mon = GNUNET_NAMESTORE_zone_monitor_start (GCG_get_configuration (), temp_zone_pkey, GNUNET_YES, &freeze_view, NULL, &display_record, NULL, &unfreeze_view, NULL); } /** * User clicked the "paste" button. Handle data from the clipboard, * if it is a "gnunet://gns/"-URI, import it into our current zone / * address book. * * @param clipboard the clipboard * @param text the text from the board * @param data NULL */ static void handle_paste_data (GtkClipboard *clipboard, const gchar *text, gpointer data) { char *name; char *slash; const char *label; if (NULL == text) return; /* clipboard empty */ if (0 != strncasecmp ("gnunet://gns/", text, strlen ("gnunet://gns/"))) { GCG_log (_ ("Invalid URI `%s'\n"), text); return; } name = GNUNET_strdup (&text[strlen ("gnunet://gns/")]); slash = strchr (name, '/'); if ((NULL != slash) && ('\0' != slash[1])) { *slash = '\0'; label = &slash[1]; if (GNUNET_OK != GNUNET_DNSPARSER_check_label (label)) { GCG_log (_ ("Invalid label `%s' in URI `%s'\n"), label, text); GNUNET_free (name); return; } else { /* got a label already, try to use it */ GSC_get_label_for_name (name, label); GNUNET_free (name); return; } } if (NULL != slash) *slash = '\0'; /* don't have a label yet, need to prompt! */ GSC_get_label_for_name (name, NULL); GNUNET_free (name); } /** * User clicked the "paste" button. Copy address information * from the clipboard into our current zone / address book. * * @param button the button * @param user_data builder (unused) */ void gnunet_conversation_gtk_contacts_paste_button_clicked_cb (GtkButton *button, gpointer user_data) { GtkClipboard *cb; cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_request_text (cb, &handle_paste_data, NULL); } /** * Initialize the contact list */ void GCG_CONTACTS_init () { contacts_treeview = GTK_WIDGET (GCG_get_main_window_object ( "gnunet_conversation_gtk_contact_list_treeview")); #if GTK_CHECK_VERSION(3, 10, 0) gtk_tree_view_set_activate_on_single_click (GTK_TREE_VIEW (contacts_treeview), TRUE); #endif contacts_liststore = GTK_LIST_STORE ( GCG_get_main_window_object ("gnunet_conversation_gtk_contacts_liststore")); contacts_treemodel = GTK_TREE_MODEL (contacts_liststore); b_paste = GTK_WIDGET (GCG_get_main_window_object ( "gnunet_conversation_gtk_contacts_paste_button")); } /** * Shutdown the contact list */ void GCG_CONTACTS_shutdown () { if (NULL != zone_mon) { GNUNET_NAMESTORE_zone_monitor_stop (zone_mon); zone_mon = NULL; } gtk_list_store_clear (contacts_liststore); contacts_liststore = NULL; contacts_treemodel = NULL; } /* end of gnunet-conversation-gtk_contacts.c */