/* This file is part of GNUnet. (C) 2010 Christian Grothoff (and other contributing authors) 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 2, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file src/fs/gnunet-fs-gtk.c * @brief Main function of gnunet-fs-gtk * @author Christian Grothoff */ #include "gnunet-fs-gtk-common.h" #include "gnunet-fs-gtk-event_handler.h" /** * Should gnunet-fs-gtk start in tray mode? */ static int tray_only; /** * Handle to our main loop. */ static struct GNUNET_GTK_MainLoop *ml; /** * Handle for file-sharing operations. */ static struct GNUNET_FS_Handle *fs; /** * List of plugins for meta data extraction. */ static struct EXTRACTOR_PluginList *plugins; guint namespace_selector_window_leave_timeout_source = 0; /** * Return handle for file-sharing operations. * * @return NULL on error */ struct GNUNET_FS_Handle * GNUNET_FS_GTK_get_fs_handle () { return fs; } /** * Get LE plugin list. */ struct EXTRACTOR_PluginList * GNUNET_FS_GTK_get_le_plugins () { return plugins; } /** * Get cfg. */ const struct GNUNET_CONFIGURATION_Handle * GNUNET_FS_GTK_get_configuration (void) { return GNUNET_GTK_main_loop_get_configuration (ml); } /** * Get an object from the main window. * * @param name name of the object * @return NULL on error */ GObject * GNUNET_FS_GTK_get_main_window_object (const char *name) { return GNUNET_GTK_main_loop_get_object (ml, name); } /** * Task run on shutdown. * FIXME: does this need to be a separate task!? */ static void shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { GNUNET_GTK_main_loop_quit (ml); if (fs != NULL) { GNUNET_FS_stop (fs); fs = NULL; } EXTRACTOR_plugin_remove_all (plugins); plugins = NULL; } /** * Callback invoked if the application is supposed to exit. */ void GNUNET_GTK_quit_cb (GObject * object, gpointer user_data) { GNUNET_GTK_tray_icon_destroy (); GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE, &shutdown_task, NULL); } /** * Search selected in 'file' menu. (from main_window_file_search.c) */ void GNUNET_GTK_main_menu_file_search_activate_cb (GtkWidget * dummy, gpointer data); void main_window_search_namespace_dropdown_button_toggled_cb (GtkToggleButton *togglebutton, gpointer user_data) { gboolean active; GtkBuilder *builder = GTK_BUILDER (user_data); GtkWidget *namespace_selector_window; GtkWidget *namespace_selector_treeview; namespace_selector_window = GTK_WIDGET (gtk_builder_get_object (builder, "namespace_selector_window")); namespace_selector_treeview = GTK_WIDGET (gtk_builder_get_object (builder, "namespace_selector_treeview")); g_object_get (G_OBJECT (togglebutton), "active", &active, NULL); if (active) { GtkAllocation togglebutton_allocation; GdkWindow *main_window_gdk; gint mwg_x, mwg_y, tgb_x, tgb_y, popup_x, popup_y; gtk_widget_get_allocation (GTK_WIDGET (togglebutton), &togglebutton_allocation); main_window_gdk = gtk_widget_get_window (GTK_WIDGET (togglebutton)); gdk_window_get_origin (main_window_gdk, &mwg_x, &mwg_y); /* FIXME: this might become a problem in other window managers, * where reference point is not in the top-left corner. * We want to show the window below the button. */ tgb_x = mwg_x + togglebutton_allocation.x; tgb_y = mwg_y + togglebutton_allocation.y; popup_x = tgb_x; popup_y = tgb_y + togglebutton_allocation.height; gtk_window_move (GTK_WINDOW (namespace_selector_window), popup_x, popup_y); gtk_widget_show_all (namespace_selector_window); gtk_widget_grab_focus (namespace_selector_treeview); } else { gtk_widget_hide (namespace_selector_window); gtk_widget_grab_focus (GTK_WIDGET (togglebutton)); } } gboolean namespace_selector_window_leave_timeout_cb (gpointer user_data) { GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (user_data); /* This will eventually hide the namespace selector */ gtk_toggle_button_set_active (toggle_button, FALSE); return FALSE; } gboolean main_window_search_namespace_dropdown_button_enter_notify_event_cb ( GtkWidget *widget, GdkEvent *event, gpointer user_data) { if (namespace_selector_window_leave_timeout_source > 0) g_source_remove (namespace_selector_window_leave_timeout_source); return FALSE; } gboolean namespace_selector_window_leave_notify_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { GtkBuilder *builder; GtkToggleButton *toggle_button; guint timeout_id; builder = GTK_BUILDER (user_data); toggle_button = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "main_window_search_namespace_dropdown_button")); /* Place a timeout to hide the window. It will be cancelled if the cursor * enters the namespace selector window or the toggle button within 100ms. */ timeout_id = g_timeout_add (100, &namespace_selector_window_leave_timeout_cb, toggle_button); if (namespace_selector_window_leave_timeout_source > 0) g_source_remove (namespace_selector_window_leave_timeout_source); namespace_selector_window_leave_timeout_source = timeout_id; return FALSE; } gboolean GNUNET_GTK_get_tree_string (GtkTreeView *treeview, GtkTreePath *treepath, guint column, gchar **value) { gboolean ok; GtkTreeModel *model; model = gtk_tree_view_get_model (treeview); if (!model) return FALSE; GtkTreeIter iter; ok = gtk_tree_model_get_iter (model, &iter, treepath); if (!ok) return FALSE; *value = NULL; gtk_tree_model_get (model, &iter, column, value, -1); if (*value == NULL) return FALSE; return TRUE; } gboolean get_selected_anonymity_level (GtkBuilder *builder, guint *p_level) { GtkComboBox *combo; GtkTreeIter iter; GtkTreeModel *model; guint level; combo = GTK_COMBO_BOX(gtk_builder_get_object (builder, "main_window_search_anonymity_combobox")); if (!combo) return FALSE; if (!gtk_combo_box_get_active_iter (combo, &iter)) return FALSE; model = gtk_combo_box_get_model (combo); if (!model) return FALSE; gtk_tree_model_get (model, &iter, 1, &level, -1); if (p_level) *p_level = level; return TRUE; } gboolean get_selected_namespace_treepath_iter_model_widget (GtkBuilder *builder, GtkTreePath **p_treepath, GtkTreeIter *p_iter, GtkTreeModel **p_model, GtkWidget **p_widget) { GtkTreeSelection *selection; GtkTreeModel *model; GList *selected; GtkTreePath *treepath; GtkWidget *widget; widget = GTK_WIDGET (gtk_builder_get_object (builder, "namespace_selector_treeview")); if (!widget) return FALSE; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget)); if (!selection || !model) return FALSE; selected = gtk_tree_selection_get_selected_rows (selection, NULL); if (!selected) return FALSE; if (selected->data == NULL) { g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL); g_list_free (selected); return FALSE; } /* Free everything except the first path, keep it */ treepath = (GtkTreePath *) selected->data; selected = g_list_remove (selected, selected->data); g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL); g_list_free (selected); if (p_iter && !gtk_tree_model_get_iter (model, p_iter, treepath)) { gtk_tree_path_free (treepath); return FALSE; } *p_treepath = treepath; if (p_model) *p_model = model; if (p_widget) *p_widget = widget; return TRUE; } void namespace_selector_treeview_cursor_changed_cb (GtkWidget *widget, gpointer user_data) { GtkBuilder *builder; GtkToggleButton *toggle_button; GtkLabel *sel_namespace_label; GtkTreeModel *model; gchar *value; GtkTreePath *treepath; GtkTreeRowReference *ref, *old; builder = GTK_BUILDER (user_data); toggle_button = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "main_window_search_namespace_dropdown_button")); if (!toggle_button) return; if (!get_selected_namespace_treepath_iter_model_widget (builder, &treepath, NULL, &model, NULL)) return; ref = gtk_tree_row_reference_new (model, treepath); old = g_object_get_data (G_OBJECT (toggle_button), "selected-row-reference"); if (old) gtk_tree_row_reference_free (old); g_object_set_data (G_OBJECT (toggle_button), "selected-row-reference", ref); sel_namespace_label = GTK_LABEL (gtk_builder_get_object (builder, "main_window_search_selected_namespace_label")); if (!sel_namespace_label) return; if (GNUNET_GTK_get_tree_string (GTK_TREE_VIEW (widget), treepath, 0, &value)) gtk_label_set_text (sel_namespace_label, value); gtk_tree_path_free (treepath); /* This will eventually hide the namespace selector */ gtk_toggle_button_set_active (toggle_button, FALSE); } GtkTreeRowReference * get_ns_selected_row (GtkTreeView *tree) { GtkTreeSelection *sel; GList *rows, *row; GtkTreeModel *model; GtkTreeRowReference *ref = NULL; sel = gtk_tree_view_get_selection (tree); rows = gtk_tree_selection_get_selected_rows (sel, &model); for (row = rows; row; row = row->next) { ref = gtk_tree_row_reference_new (model, row->data); if (ref != NULL) break; } g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL); g_list_free (rows); return ref; } gboolean namespace_selector_treeview_button_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { GtkTreeRowReference *ref = NULL; ref = get_ns_selected_row (GTK_TREE_VIEW (widget)); if (ref != NULL) { gpointer old = g_object_get_data (G_OBJECT (widget), "pushed-rowreference"); if (old) gtk_tree_row_reference_free (old); g_object_set_data (G_OBJECT (widget), "pushed-rowreference", ref); } return FALSE; } gboolean namespace_selector_treeview_button_release_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { GtkTreeRowReference *ref = NULL; gpointer old = g_object_get_data (G_OBJECT (widget), "pushed-rowreference"); ref = get_ns_selected_row (GTK_TREE_VIEW (widget)); if (ref && old) { GtkTreePath *path_ref, *path_old; path_ref = gtk_tree_row_reference_get_path (ref); path_old = gtk_tree_row_reference_get_path (old); if (gtk_tree_path_compare (path_ref, path_old) == 0) namespace_selector_treeview_cursor_changed_cb (widget, user_data); if (path_ref) gtk_tree_path_free (path_ref); if (path_old) gtk_tree_path_free (path_old); } if (ref) gtk_tree_row_reference_free (ref); if (old) gtk_tree_row_reference_free (old); g_object_set_data (G_OBJECT (widget), "pushed-rowreference", NULL); return FALSE; } void main_window_search_button_clicked_cb (GtkButton *button, gpointer user_data) { GtkBuilder *builder; GtkTreePath *namespace_treepath = NULL; GtkTreeModel *namespace_model = NULL; GtkComboBox *mime_combo; GtkTreeModel *mime_model; GtkEntry *query_entry; guint anonymity_level; GtkTreeRowReference *ref = NULL; GtkTreeIter iter; GtkToggleButton *toggle_button; const char *entry_keywords; gchar *keywords; gchar *mime_keyword; GNUNET_HashCode *nsid = NULL; struct GNUNET_FS_Uri *uri; gchar *root = NULL; char *emsg; builder = GTK_BUILDER (user_data); toggle_button = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "main_window_search_namespace_dropdown_button")); if (!get_selected_anonymity_level (builder, &anonymity_level)) return; mime_combo = GTK_COMBO_BOX (GNUNET_FS_GTK_get_main_window_object ("main_window_search_mime_combobox")); mime_model = gtk_combo_box_get_model (mime_combo); if (mime_model && gtk_combo_box_get_active_iter (mime_combo, &iter)) { mime_keyword = NULL; gtk_tree_model_get (mime_model, &iter, 0, &mime_keyword, -1); } if (mime_keyword == NULL) mime_keyword = g_strdup (""); ref = g_object_get_data (G_OBJECT (toggle_button), "selected-row-reference"); if (ref) { namespace_model = gtk_tree_row_reference_get_model (ref); namespace_treepath = gtk_tree_row_reference_get_path (ref); gtk_tree_model_get_iter (namespace_model, &iter, namespace_treepath); } query_entry = GTK_ENTRY (gtk_builder_get_object (builder, "main_window_search_entry")); if (namespace_treepath != NULL) gtk_tree_model_get (namespace_model, &iter, 1, &nsid, 2, &root, -1); if (root == NULL) root = g_strdup (""); entry_keywords = gtk_entry_get_text (query_entry); keywords = g_strdup_printf ("%s %s %s", entry_keywords, mime_keyword, root); g_free (mime_keyword); g_free (root); if (nsid != NULL) { uri = GNUNET_FS_uri_sks_create_from_nsid (nsid, keywords); GNUNET_assert (uri != NULL); } else { emsg = NULL; uri = GNUNET_FS_uri_ksk_create (keywords, &emsg); if (uri == NULL) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Invalid keyword string `%s': %s"), keywords, emsg); GNUNET_free_non_null (emsg); return; } } GNUNET_FS_search_start (GNUNET_FS_GTK_get_fs_handle (), uri, anonymity_level, GNUNET_FS_SEARCH_OPTION_NONE, NULL); g_free (keywords); GNUNET_FS_uri_destroy (uri); } /** * Add pseudonym data to tree store * * @param cls closure (the 'GtkListStore') * @param pseudonym hash code of public key of pseudonym * @param md meta data known about the pseudonym * @param rating the local rating of the pseudonym * @return GNUNET_OK to continue iteration, GNUNET_SYSERR to abort */ static int add_namespace_to_ts (void *cls, const GNUNET_HashCode * pseudonym, const struct GNUNET_CONTAINER_MetaData *md, int rating) { GtkTreeStore *ts = cls; char *root; char *ns_name; GNUNET_HashCode *nsid; char *description; char *uris; char *emsg; struct GNUNET_FS_Uri *uri; GtkTreeIter iter; ns_name = GNUNET_PSEUDONYM_id_to_name (GNUNET_FS_GTK_get_configuration (), pseudonym); nsid = GNUNET_malloc (sizeof (GNUNET_HashCode)); *nsid = *pseudonym; root = NULL; uris = GNUNET_CONTAINER_meta_data_get_by_type (md, EXTRACTOR_METATYPE_URI); if (uris != NULL) { emsg = NULL; uri = GNUNET_FS_uri_parse (uris, &emsg); if (uri == NULL) GNUNET_free (emsg); root = GNUNET_FS_uri_sks_get_content_id (uri); GNUNET_FS_uri_destroy (uri); } description = GNUNET_CONTAINER_meta_data_get_first_by_types (md, EXTRACTOR_METATYPE_TITLE, EXTRACTOR_METATYPE_BOOK_TITLE, EXTRACTOR_METATYPE_DESCRIPTION, EXTRACTOR_METATYPE_SUMMARY, EXTRACTOR_METATYPE_ALBUM, EXTRACTOR_METATYPE_COMMENT, EXTRACTOR_METATYPE_SUBJECT, EXTRACTOR_METATYPE_KEYWORDS, -1); gtk_tree_store_insert_with_values (ts, &iter, NULL, G_MAXINT, 0, ns_name, 1, nsid, 2, root, 3, description, -1); GNUNET_free (ns_name); GNUNET_free_non_null (root); GNUNET_free_non_null (description); return GNUNET_OK; } void GNUNET_GTK_main_window_realize_cb (GtkWidget *widget, gpointer user_data) { GtkTreeIter iter; GtkTreeView *namespace_tree; GtkTreeStore *namespace_treestore; GtkBuilder *builder; GtkWidget *namespace_selector_window; builder = GTK_BUILDER (user_data); namespace_treestore = GTK_TREE_STORE (GNUNET_FS_GTK_get_main_window_object ("main_window_search_namespace_treestore")); namespace_tree = GTK_TREE_VIEW (GNUNET_FS_GTK_get_main_window_object ("namespace_selector_treeview")); /* FIXME: find a way to manage pseudonyms. * Right now the list will be filled with ALL and ANY pseudonyms that we * find, these are held as files in a special directory. * I don't see an easy way to ignore certain pseudonyms in that directory, * and that require for pseudonym management. Also, pseudonyms are presented * in arbitrary order. We must either sort them (by name?) or let the user * drag them around to change the order in which they appear in the list. * All that is not possible with a simple "files in a directory" concept. */ gtk_tree_store_insert_with_values (namespace_treestore, &iter, NULL, G_MAXINT, 0, "Any", 1, NULL, 2, "", 3, "Do not search in any particular namespace", -1); /* GNUNET_PSEUDONYM_list_all (GNUNET_FS_GTK_get_configuration (), &add_namespace_to_ts, namespace_treestore); */ GNUNET_PSEUDONYM_discovery_callback_register ( GNUNET_FS_GTK_get_configuration (), &add_namespace_to_ts, namespace_treestore); /* FIXME: read currently selected namespace from somewhere instead of selecting 0th item */ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (namespace_treestore), &iter)) { gchar *value; GtkLabel *sel_namespace_label; GtkTreePath *treepath = gtk_tree_path_new_first (); gtk_tree_selection_select_iter (gtk_tree_view_get_selection ( namespace_tree), &iter); sel_namespace_label = GTK_LABEL (gtk_builder_get_object (builder, "main_window_search_selected_namespace_label")); if (GNUNET_GTK_get_tree_string (namespace_tree, treepath, 0, &value)) gtk_label_set_text (sel_namespace_label, value); gtk_tree_path_free (treepath); } /* How the window (to trigger certain events) and immediately hide it */ namespace_selector_window = GTK_WIDGET (gtk_builder_get_object (builder, "namespace_selector_window")); gtk_widget_show (namespace_selector_window); gtk_widget_hide (namespace_selector_window); } /** * Actual main function run right after GNUnet's scheduler * is initialized. Initializes up GTK and Glade. */ static void run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { GtkWidget *main_window; ml = cls; GNUNET_GTK_set_icon_search_path (); GNUNET_GTK_setup_nls (); /* setup main window */ main_window = GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window")); gtk_window_maximize (GTK_WINDOW (main_window)); GNUNET_GTK_tray_icon_create (GTK_WINDOW (main_window), "gnunet-gtk" /* FIXME: rename icon? */ , "gnunet-fs-gtk"); /* initialize file-sharing */ plugins = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY); fs = GNUNET_FS_start (GNUNET_GTK_main_loop_get_configuration (ml), "gnunet-gtk", &GNUNET_GTK_fs_event_handler, NULL, GNUNET_FS_FLAGS_PERSISTENCE | GNUNET_FS_FLAGS_DO_PROBES, GNUNET_FS_OPTIONS_END); if (fs != NULL) { /* Searches are now started from the search bar */ /* add_new_tab (); */ } else { gtk_widget_hide (GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_menu_file_create_pseudonym"))); gtk_widget_hide (GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_menu_file_advertise_pseudonym"))); gtk_widget_hide (GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_menu_file_publish"))); gtk_widget_hide (GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_menu_file_search"))); gtk_widget_hide (GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_menu_file_download_uri"))); gtk_widget_hide (GTK_WIDGET (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_menu_file_open_gnunet_directory"))); /* FIXME: set warning in status bar... */ } /* make GUI visible */ if (!tray_only) { gtk_widget_show (main_window); gtk_window_present (GTK_WINDOW (main_window)); } } int main (int argc, char *const *argv) { static struct GNUNET_GETOPT_CommandLineOption options[] = { {'t', "tray", NULL, gettext_noop ("start in tray mode"), 0, &GNUNET_GETOPT_set_one, &tray_only}, GNUNET_GETOPT_OPTION_END }; if (GNUNET_OK != GNUNET_GTK_main_loop_start ("gnunet-fs-gtk", "GTK GUI for GNUnet", argc, argv, options, "gnunet_fs_gtk_main_window.glade", &run)) return 1; return 0; } /* end of gnunet-fs-gtk.c */