/* * evolution-source-viewer.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * */ #include #include #include #include /* XXX Even though this is all one file, I'm still being pedantic about data * encapsulation (except for a private struct, even I'm not that anal!). * I expect this program will eventually be too complex for one file * and we'll want to split off an e-source-viewer.[ch]. */ /* Standard GObject macros */ #define E_TYPE_SOURCE_VIEWER \ (e_source_viewer_get_type ()) #define E_SOURCE_VIEWER(obj) \ (G_TYPE_CHECK_INSTANCE_CAST \ ((obj), E_TYPE_SOURCE_VIEWER, ESourceViewer)) #define E_SOURCE_VIEWER_CLASS(cls) \ (G_TYPE_CHECK_CLASS_CAST \ ((cls), E_TYPE_SOURCE_VIEWER, ESourceViewerClass)) #define E_IS_SOURCE_VIEWER(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE \ ((obj), E_TYPE_SOURCE_VIEWER)) #define E_IS_SOURCE_VIEWER_CLASS(cls) \ (G_TYPE_CHECK_CLASS_TYPE \ ((cls), E_TYPE_SOURCE_VIEWER)) #define E_SOURCE_VIEWER_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS \ ((obj), E_TYPE_SOURCE_VIEWER, ESourceViewerClass)) typedef struct _ESourceViewer ESourceViewer; typedef struct _ESourceViewerClass ESourceViewerClass; struct _ESourceViewer { GtkWindow parent; ESourceRegistry *registry; GtkTreeStore *tree_store; GHashTable *source_index; GCancellable *delete_operation; GtkWidget *tree_view; /* not referenced */ GtkWidget *text_view; /* not referenced */ GtkWidget *top_panel; /* not referenced */ /* Viewing Page */ GtkWidget *viewing_label; /* not referenced */ GtkWidget *delete_button; /* not referenced */ /* Deleting Page */ GtkWidget *deleting_label; /* not referenced */ GtkWidget *deleting_cancel; /* not referenced */ }; struct _ESourceViewerClass { GtkWindowClass parent_class; }; enum { PAGE_VIEWING, PAGE_DELETING }; enum { PROP_0, PROP_REGISTRY }; enum { COLUMN_DISPLAY_NAME, COLUMN_SOURCE_UID, COLUMN_REMOVABLE, COLUMN_WRITABLE, COLUMN_REMOTE_CREATABLE, COLUMN_REMOTE_DELETABLE, COLUMN_SOURCE, NUM_COLUMNS }; /* Forward Declarations */ GType e_source_viewer_get_type (void) G_GNUC_CONST; GtkWidget * e_source_viewer_new (GCancellable *cancellable, GError **error); ESourceRegistry * e_source_viewer_get_registry (ESourceViewer *viewer); GtkTreePath * e_source_viewer_dup_selected_path (ESourceViewer *viewer); gboolean e_source_viewer_set_selected_path (ESourceViewer *viewer, GtkTreePath *path); ESource * e_source_viewer_ref_selected_source (ESourceViewer *viewer); gboolean e_source_viewer_set_selected_source (ESourceViewer *viewer, ESource *source); GNode * e_source_viewer_build_display_tree (ESourceViewer *viewer); static void e_source_viewer_initable_init (GInitableIface *interface); G_DEFINE_TYPE_WITH_CODE ( ESourceViewer, e_source_viewer, GTK_TYPE_WINDOW, G_IMPLEMENT_INTERFACE ( G_TYPE_INITABLE, e_source_viewer_initable_init)); static GIcon * source_view_new_remote_creatable_icon (void) { GEmblem *emblem; GIcon *emblem_icon; GIcon *folder_icon; GIcon *icon; emblem_icon = g_themed_icon_new ("emblem-new"); folder_icon = g_themed_icon_new ("folder-remote"); emblem = g_emblem_new (emblem_icon); icon = g_emblemed_icon_new (folder_icon, emblem); g_object_unref (emblem); g_object_unref (folder_icon); g_object_unref (emblem_icon); return icon; } static GIcon * source_view_new_remote_deletable_icon (void) { GEmblem *emblem; GIcon *emblem_icon; GIcon *folder_icon; GIcon *icon; emblem_icon = g_themed_icon_new ("edit-delete"); folder_icon = g_themed_icon_new ("folder-remote"); emblem = g_emblem_new (emblem_icon); icon = g_emblemed_icon_new (folder_icon, emblem); g_object_unref (emblem); g_object_unref (folder_icon); g_object_unref (emblem_icon); return icon; } static gchar * source_viewer_get_monospace_font_name (void) { GSettings *settings; gchar *font_name; settings = g_settings_new ("org.gnome.desktop.interface"); font_name = g_settings_get_string (settings, "monospace-font-name"); g_object_unref (settings); /* Fallback to a reasonable default. */ if (font_name == NULL) font_name = g_strdup ("Monospace 10"); return font_name; } static void source_viewer_set_text (ESourceViewer *viewer, ESource *source) { GtkTextView *text_view; GtkTextBuffer *buffer; GtkTextIter start; GtkTextIter end; text_view = GTK_TEXT_VIEW (viewer->text_view); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_start_iter (buffer, &start); gtk_text_buffer_get_end_iter (buffer, &end); gtk_text_buffer_delete (buffer, &start, &end); if (source != NULL) { gchar *string; gsize length; gtk_text_buffer_get_start_iter (buffer, &start); string = e_source_to_string (source, &length); gtk_text_buffer_insert (buffer, &start, string, length); g_free (string); } } static void source_viewer_update_row (ESourceViewer *viewer, ESource *source) { GHashTable *source_index; GtkTreeRowReference *reference; GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; const gchar *display_name; const gchar *source_uid; gboolean removable; gboolean writable; gboolean remote_creatable; gboolean remote_deletable; source_index = viewer->source_index; reference = g_hash_table_lookup (source_index, source); /* We show all sources, so the reference should be valid. */ g_return_if_fail (gtk_tree_row_reference_valid (reference)); model = gtk_tree_row_reference_get_model (reference); path = gtk_tree_row_reference_get_path (reference); gtk_tree_model_get_iter (model, &iter, path); gtk_tree_path_free (path); source_uid = e_source_get_uid (source); display_name = e_source_get_display_name (source); removable = e_source_get_removable (source); writable = e_source_get_writable (source); remote_creatable = e_source_get_remote_creatable (source); remote_deletable = e_source_get_remote_deletable (source); gtk_tree_store_set ( GTK_TREE_STORE (model), &iter, COLUMN_DISPLAY_NAME, display_name, COLUMN_SOURCE_UID, source_uid, COLUMN_REMOVABLE, removable, COLUMN_WRITABLE, writable, COLUMN_REMOTE_CREATABLE, remote_creatable, COLUMN_REMOTE_DELETABLE, remote_deletable, COLUMN_SOURCE, source, -1); } static gboolean source_viewer_traverse (GNode *node, gpointer user_data) { ESourceViewer *viewer; ESource *source; GHashTable *source_index; GtkTreeRowReference *reference = NULL; GtkTreeView *tree_view; GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; /* Skip the root node. */ if (G_NODE_IS_ROOT (node)) return FALSE; viewer = E_SOURCE_VIEWER (user_data); source_index = viewer->source_index; tree_view = GTK_TREE_VIEW (viewer->tree_view); model = gtk_tree_view_get_model (tree_view); if (node->parent != NULL && node->parent->data != NULL) reference = g_hash_table_lookup ( source_index, node->parent->data); if (gtk_tree_row_reference_valid (reference)) { GtkTreeIter parent; path = gtk_tree_row_reference_get_path (reference); gtk_tree_model_get_iter (model, &parent, path); gtk_tree_path_free (path); gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent); } else gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL); /* Source index takes ownership. */ source = g_object_ref (node->data); path = gtk_tree_model_get_path (model, &iter); reference = gtk_tree_row_reference_new (model, path); g_hash_table_insert (source_index, source, reference); gtk_tree_path_free (path); source_viewer_update_row (viewer, source); return FALSE; } static void source_viewer_save_expanded (GtkTreeView *tree_view, GtkTreePath *path, GQueue *queue) { GtkTreeModel *model; GtkTreeIter iter; ESource *source; model = gtk_tree_view_get_model (tree_view); gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1); g_queue_push_tail (queue, source); } static void source_viewer_build_model (ESourceViewer *viewer) { GQueue queue = G_QUEUE_INIT; GHashTable *source_index; GtkTreeView *tree_view; GtkTreeModel *model; GtkTreePath *sel_path; ESource *sel_source; GNode *root; tree_view = GTK_TREE_VIEW (viewer->tree_view); source_index = viewer->source_index; sel_path = e_source_viewer_dup_selected_path (viewer); sel_source = e_source_viewer_ref_selected_source (viewer); /* Save expanded sources to restore later. */ gtk_tree_view_map_expanded_rows ( tree_view, (GtkTreeViewMappingFunc) source_viewer_save_expanded, &queue); model = gtk_tree_view_get_model (tree_view); gtk_tree_store_clear (GTK_TREE_STORE (model)); g_hash_table_remove_all (source_index); root = e_source_viewer_build_display_tree (viewer); g_node_traverse ( root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc) source_viewer_traverse, viewer); e_source_registry_free_display_tree (root); /* Restore previously expanded sources. */ while (!g_queue_is_empty (&queue)) { GtkTreeRowReference *reference; ESource *source; source = g_queue_pop_head (&queue); reference = g_hash_table_lookup (source_index, source); if (gtk_tree_row_reference_valid (reference)) { GtkTreePath *path; path = gtk_tree_row_reference_get_path (reference); gtk_tree_view_expand_to_path (tree_view, path); gtk_tree_path_free (path); } g_object_unref (source); } /* Restore the selection. */ if (sel_source != NULL && sel_path != NULL) { if (!e_source_viewer_set_selected_source (viewer, sel_source)) e_source_viewer_set_selected_path (viewer, sel_path); } if (sel_path != NULL) gtk_tree_path_free (sel_path); if (sel_source != NULL) g_object_unref (sel_source); } static void source_viewer_expand_to_source (ESourceViewer *viewer, ESource *source) { GHashTable *source_index; GtkTreeRowReference *reference; GtkTreeView *tree_view; GtkTreePath *path; source_index = viewer->source_index; reference = g_hash_table_lookup (source_index, source); /* We show all sources, so the reference should be valid. */ g_return_if_fail (gtk_tree_row_reference_valid (reference)); /* Expand the tree view to the path containing the ESource. */ tree_view = GTK_TREE_VIEW (viewer->tree_view); path = gtk_tree_row_reference_get_path (reference); gtk_tree_view_expand_to_path (tree_view, path); gtk_tree_path_free (path); } static void source_viewer_source_added_cb (ESourceRegistry *registry, ESource *source, ESourceViewer *viewer) { source_viewer_build_model (viewer); source_viewer_expand_to_source (viewer, source); } static void source_viewer_source_changed_cb (ESourceRegistry *registry, ESource *source, ESourceViewer *viewer) { ESource *selected; source_viewer_update_row (viewer, source); selected = e_source_viewer_ref_selected_source (viewer); if (selected != NULL) { if (e_source_equal (source, selected)) source_viewer_set_text (viewer, source); g_object_unref (selected); } } static void source_viewer_source_removed_cb (ESourceRegistry *registry, ESource *source, ESourceViewer *viewer) { source_viewer_build_model (viewer); } static void source_viewer_selection_changed_cb (GtkTreeSelection *selection, ESourceViewer *viewer) { ESource *source; const gchar *uid = NULL; gboolean removable = FALSE; source = e_source_viewer_ref_selected_source (viewer); source_viewer_set_text (viewer, source); if (source != NULL) { uid = e_source_get_uid (source); removable = e_source_get_removable (source); } gtk_label_set_text (GTK_LABEL (viewer->viewing_label), uid); gtk_widget_set_visible (viewer->delete_button, removable); if (source != NULL) g_object_unref (source); } static void source_viewer_delete_done_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { ESource *source; ESourceViewer *viewer; GError *error = NULL; source = E_SOURCE (source_object); viewer = E_SOURCE_VIEWER (user_data); e_source_remove_finish (source, result, &error); /* Ignore cancellations. */ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_clear_error (&error); /* FIXME Show an info bar with the error message. */ } else if (error != NULL) { g_warning ("%s: %s", G_STRFUNC, error->message); g_clear_error (&error); } gtk_notebook_set_current_page ( GTK_NOTEBOOK (viewer->top_panel), PAGE_VIEWING); gtk_widget_set_sensitive (viewer->tree_view, TRUE); g_object_unref (viewer->delete_operation); viewer->delete_operation = NULL; g_object_unref (viewer); } static void source_viewer_delete_button_clicked_cb (GtkButton *delete_button, ESourceViewer *viewer) { ESource *source; const gchar *uid; g_return_if_fail (viewer->delete_operation == NULL); source = e_source_viewer_ref_selected_source (viewer); g_return_if_fail (source != NULL); uid = e_source_get_uid (source); gtk_label_set_text (GTK_LABEL (viewer->deleting_label), uid); gtk_notebook_set_current_page ( GTK_NOTEBOOK (viewer->top_panel), PAGE_DELETING); gtk_widget_set_sensitive (viewer->tree_view, FALSE); viewer->delete_operation = g_cancellable_new (); e_source_remove ( source, viewer->delete_operation, source_viewer_delete_done_cb, g_object_ref (viewer)); g_object_unref (source); } static void source_viewer_deleting_cancel_clicked_cb (GtkButton *deleting_cancel, ESourceViewer *viewer) { g_return_if_fail (viewer->delete_operation != NULL); g_cancellable_cancel (viewer->delete_operation); } static void source_viewer_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_REGISTRY: g_value_set_object ( value, e_source_viewer_get_registry ( E_SOURCE_VIEWER (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void source_viewer_dispose (GObject *object) { ESourceViewer *viewer = E_SOURCE_VIEWER (object); if (viewer->registry != NULL) { g_signal_handlers_disconnect_matched ( viewer->registry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object); g_object_unref (viewer->registry); viewer->registry = NULL; } if (viewer->tree_store != NULL) { g_object_unref (viewer->tree_store); viewer->tree_store = NULL; } g_hash_table_remove_all (viewer->source_index); if (viewer->delete_operation != NULL) { g_object_unref (viewer->delete_operation); viewer->delete_operation = NULL; } /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_source_viewer_parent_class)->dispose (object); } static void source_viewer_finalize (GObject *object) { ESourceViewer *viewer = E_SOURCE_VIEWER (object); g_hash_table_destroy (viewer->source_index); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_source_viewer_parent_class)->finalize (object); } static void source_viewer_constructed (GObject *object) { ESourceViewer *viewer; GtkTreeViewColumn *column; GtkTreeSelection *selection; GtkCellRenderer *renderer; GtkWidget *container; GtkWidget *paned; GtkWidget *widget; PangoAttribute *attr; PangoAttrList *bold; PangoFontDescription *desc; GIcon *icon; const gchar *title; gchar *font_name; gint page_num; viewer = E_SOURCE_VIEWER (object); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_source_viewer_parent_class)->constructed (object); bold = pango_attr_list_new (); attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); pango_attr_list_insert (bold, attr); title = _("Evolution Source Viewer"); gtk_window_set_title (GTK_WINDOW (viewer), title); gtk_window_set_default_size (GTK_WINDOW (viewer), 800, 600); paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); gtk_paned_set_position (GTK_PANED (paned), 400); gtk_container_add (GTK_CONTAINER (viewer), paned); gtk_widget_show (paned); /* Left panel */ widget = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW (widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); gtk_paned_add1 (GTK_PANED (paned), widget); gtk_widget_show (widget); container = widget; widget = gtk_tree_view_new_with_model ( GTK_TREE_MODEL (viewer->tree_store)); gtk_container_add (GTK_CONTAINER (container), widget); viewer->tree_view = widget; /* do not reference */ gtk_widget_show (widget); column = gtk_tree_view_column_new (); /* Translators: The name that is displayed in the user interface */ gtk_tree_view_column_set_title (column, _("Display Name")); gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column); renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (column, renderer, TRUE); gtk_tree_view_column_add_attribute ( column, renderer, "text", COLUMN_DISPLAY_NAME); column = gtk_tree_view_column_new (); gtk_tree_view_column_set_title (column, _("Flags")); gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column); renderer = gtk_cell_renderer_pixbuf_new (); g_object_set ( renderer, "stock-id", GTK_STOCK_EDIT, "stock-size", GTK_ICON_SIZE_MENU, NULL); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_add_attribute ( column, renderer, "visible", COLUMN_WRITABLE); renderer = gtk_cell_renderer_pixbuf_new (); g_object_set ( renderer, "stock-id", GTK_STOCK_DELETE, "stock-size", GTK_ICON_SIZE_MENU, NULL); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_add_attribute ( column, renderer, "visible", COLUMN_REMOVABLE); icon = source_view_new_remote_creatable_icon (); renderer = gtk_cell_renderer_pixbuf_new (); g_object_set ( renderer, "gicon", icon, "stock-size", GTK_ICON_SIZE_MENU, NULL); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_add_attribute ( column, renderer, "visible", COLUMN_REMOTE_CREATABLE); g_object_unref (icon); icon = source_view_new_remote_deletable_icon (); renderer = gtk_cell_renderer_pixbuf_new (); g_object_set ( renderer, "gicon", icon, "stock-size", GTK_ICON_SIZE_MENU, NULL); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_add_attribute ( column, renderer, "visible", COLUMN_REMOTE_DELETABLE); g_object_unref (icon); /* Append an empty pixbuf renderer to fill leftover space. */ renderer = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (column, renderer, TRUE); column = gtk_tree_view_column_new (); gtk_tree_view_column_set_title (column, _("Identity")); gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column); renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_add_attribute ( column, renderer, "text", COLUMN_SOURCE_UID); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); /* Right panel */ widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_paned_add2 (GTK_PANED (paned), widget); gtk_widget_show (widget); container = widget; widget = gtk_notebook_new (); gtk_widget_set_margin_top (widget, 3); gtk_widget_set_margin_right (widget, 3); gtk_widget_set_margin_bottom (widget, 3); /* leave left margin at zero */ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); viewer->top_panel = widget; /* do not reference */ gtk_widget_show (widget); widget = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW (widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); gtk_widget_show (widget); container = widget; widget = gtk_text_view_new (); gtk_text_view_set_editable (GTK_TEXT_VIEW (widget), FALSE); gtk_container_add (GTK_CONTAINER (container), widget); viewer->text_view = widget; /* do not reference */ gtk_widget_show (widget); font_name = source_viewer_get_monospace_font_name (); desc = pango_font_description_from_string (font_name); gtk_widget_override_font (widget, desc); pango_font_description_free (desc); g_free (font_name); /* Top panel: Viewing */ container = viewer->top_panel; widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); page_num = gtk_notebook_append_page ( GTK_NOTEBOOK (container), widget, NULL); g_warn_if_fail (page_num == PAGE_VIEWING); gtk_widget_show (widget); container = widget; widget = gtk_label_new ("Identity:"); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); gtk_widget_show (widget); widget = gtk_label_new (NULL); gtk_label_set_attributes (GTK_LABEL (widget), bold); gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END); gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); viewer->viewing_label = widget; /* do not reference */ gtk_widget_show (widget); widget = gtk_button_new_from_stock (GTK_STOCK_DELETE); gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0); viewer->delete_button = widget; /* do not reference */ gtk_widget_hide (widget); /* Top panel: Deleting */ container = viewer->top_panel; widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); page_num = gtk_notebook_append_page ( GTK_NOTEBOOK (container), widget, NULL); g_warn_if_fail (page_num == PAGE_DELETING); gtk_widget_show (widget); container = widget; widget = gtk_label_new ("Deleting"); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); gtk_widget_show (widget); widget = gtk_label_new (NULL); gtk_label_set_attributes (GTK_LABEL (widget), bold); gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END); gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); viewer->deleting_label = widget; /* do not reference */ gtk_widget_show (widget); widget = gtk_button_new_from_stock (GTK_STOCK_CANCEL); gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0); viewer->deleting_cancel = widget; /* do not reference */ gtk_widget_show (widget); pango_attr_list_unref (bold); g_signal_connect ( selection, "changed", G_CALLBACK (source_viewer_selection_changed_cb), viewer); g_signal_connect ( viewer->delete_button, "clicked", G_CALLBACK (source_viewer_delete_button_clicked_cb), viewer); g_signal_connect ( viewer->deleting_cancel, "clicked", G_CALLBACK (source_viewer_deleting_cancel_clicked_cb), viewer); } static gboolean source_viewer_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { ESourceViewer *viewer; ESourceRegistry *registry; viewer = E_SOURCE_VIEWER (initable); registry = e_source_registry_new_sync (cancellable, error); if (registry == NULL) return FALSE; viewer->registry = registry; /* takes ownership */ g_signal_connect ( registry, "source-added", G_CALLBACK (source_viewer_source_added_cb), viewer); g_signal_connect ( registry, "source-changed", G_CALLBACK (source_viewer_source_changed_cb), viewer); g_signal_connect ( registry, "source-removed", G_CALLBACK (source_viewer_source_removed_cb), viewer); source_viewer_build_model (viewer); gtk_tree_view_expand_all (GTK_TREE_VIEW (viewer->tree_view)); return TRUE; } static void e_source_viewer_class_init (ESourceViewerClass *class) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (class); object_class->get_property = source_viewer_get_property; object_class->dispose = source_viewer_dispose; object_class->finalize = source_viewer_finalize; object_class->constructed = source_viewer_constructed; g_object_class_install_property ( object_class, PROP_REGISTRY, g_param_spec_object ( "registry", "Registry", "Data source registry", E_TYPE_SOURCE_REGISTRY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); } static void e_source_viewer_initable_init (GInitableIface *interface) { interface->init = source_viewer_initable_init; } static void e_source_viewer_init (ESourceViewer *viewer) { viewer->tree_store = gtk_tree_store_new ( NUM_COLUMNS, G_TYPE_STRING, /* COLUMN_DISPLAY_NAME */ G_TYPE_STRING, /* COLUMN_SOURCE_UID */ G_TYPE_BOOLEAN, /* COLUMN_REMOVABLE */ G_TYPE_BOOLEAN, /* COLUMN_WRITABLE */ G_TYPE_BOOLEAN, /* COLUMN_REMOTE_CREATABLE */ G_TYPE_BOOLEAN, /* COLUMN_REMOTE_DELETABLE */ E_TYPE_SOURCE); /* COLUMN_SOURCE */ viewer->source_index = g_hash_table_new_full ( (GHashFunc) e_source_hash, (GEqualFunc) e_source_equal, (GDestroyNotify) g_object_unref, (GDestroyNotify) gtk_tree_row_reference_free); } GtkWidget * e_source_viewer_new (GCancellable *cancellable, GError **error) { return g_initable_new ( E_TYPE_SOURCE_VIEWER, cancellable, error, NULL); } ESourceRegistry * e_source_viewer_get_registry (ESourceViewer *viewer) { g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), NULL); return viewer->registry; } GtkTreePath * e_source_viewer_dup_selected_path (ESourceViewer *viewer) { GtkTreeSelection *selection; GtkTreeView *tree_view; GtkTreeModel *model; GtkTreeIter iter; g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), NULL); tree_view = GTK_TREE_VIEW (viewer->tree_view); selection = gtk_tree_view_get_selection (tree_view); if (!gtk_tree_selection_get_selected (selection, &model, &iter)) return NULL; return gtk_tree_model_get_path (model, &iter); } gboolean e_source_viewer_set_selected_path (ESourceViewer *viewer, GtkTreePath *path) { GtkTreeSelection *selection; GtkTreeView *tree_view; GtkTreeModel *model; GtkTreeIter iter; g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), FALSE); g_return_val_if_fail (path != NULL, FALSE); tree_view = GTK_TREE_VIEW (viewer->tree_view); selection = gtk_tree_view_get_selection (tree_view); /* Check that the path is valid. */ model = gtk_tree_view_get_model (tree_view); if (!gtk_tree_model_get_iter (model, &iter, path)) return FALSE; gtk_tree_selection_unselect_all (selection); gtk_tree_view_expand_to_path (tree_view, path); gtk_tree_selection_select_path (selection, path); return TRUE; } ESource * e_source_viewer_ref_selected_source (ESourceViewer *viewer) { ESource *source; GtkTreeSelection *selection; GtkTreeView *tree_view; GtkTreeModel *model; GtkTreeIter iter; g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), NULL); tree_view = GTK_TREE_VIEW (viewer->tree_view); selection = gtk_tree_view_get_selection (tree_view); if (!gtk_tree_selection_get_selected (selection, &model, &iter)) return NULL; gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1); return source; } gboolean e_source_viewer_set_selected_source (ESourceViewer *viewer, ESource *source) { GHashTable *source_index; GtkTreeRowReference *reference; GtkTreePath *path; gboolean success; g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), FALSE); g_return_val_if_fail (E_IS_SOURCE (source), FALSE); source_index = viewer->source_index; reference = g_hash_table_lookup (source_index, source); if (!gtk_tree_row_reference_valid (reference)) return FALSE; path = gtk_tree_row_reference_get_path (reference); success = e_source_viewer_set_selected_path (viewer, path); gtk_tree_path_free (path); return success; } /* Helper for e_source_viewer_build_display_tree() */ static gint source_viewer_compare_nodes (GNode *node_a, GNode *node_b) { ESource *source_a = E_SOURCE (node_a->data); ESource *source_b = E_SOURCE (node_b->data); return e_source_compare_by_display_name (source_a, source_b); } /* Helper for e_source_viewer_build_display_tree() */ static gboolean source_viewer_sort_nodes (GNode *node, gpointer unused) { GQueue queue = G_QUEUE_INIT; GNode *child_node; /* Unlink all the child nodes and place them in a queue. */ while ((child_node = g_node_first_child (node)) != NULL) { g_node_unlink (child_node); g_queue_push_tail (&queue, child_node); } /* Sort the queue by source name. */ g_queue_sort ( &queue, (GCompareDataFunc) source_viewer_compare_nodes, NULL); /* Pop nodes off the head of the queue and put them back * under the parent node (preserving the sorted order). */ while ((child_node = g_queue_pop_head (&queue)) != NULL) g_node_append (node, child_node); return FALSE; } GNode * e_source_viewer_build_display_tree (ESourceViewer *viewer) { GNode *root; GHashTable *index; GList *list, *link; GHashTableIter iter; gpointer value; /* This is just like e_source_registry_build_display_tree() * except it includes all data sources, even disabled ones. * Free the tree with e_source_registry_free_display_tree(). */ g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), NULL); root = g_node_new (NULL); index = g_hash_table_new (g_str_hash, g_str_equal); /* Add a GNode for each ESource to the index. * The GNodes take ownership of the ESource references. */ list = e_source_registry_list_sources (viewer->registry, NULL); for (link = list; link != NULL; link = g_list_next (link)) { ESource *source = E_SOURCE (link->data); gpointer key = (gpointer) e_source_get_uid (source); g_hash_table_insert (index, key, g_node_new (source)); } g_list_free (list); /* Traverse the index and link the nodes together. */ g_hash_table_iter_init (&iter, index); while (g_hash_table_iter_next (&iter, NULL, &value)) { ESource *source; GNode *source_node; GNode *parent_node; const gchar *parent_uid; source_node = (GNode *) value; source = E_SOURCE (source_node->data); parent_uid = e_source_get_parent (source); if (parent_uid == NULL || *parent_uid == '\0') { parent_node = root; } else { parent_node = g_hash_table_lookup (index, parent_uid); } /* This could be NULL if the registry service was * shutdown or reloaded. All sources will vanish. */ if (parent_node != NULL) g_node_append (parent_node, source_node); } /* Sort nodes by display name in post order. */ g_node_traverse ( root, G_POST_ORDER, G_TRAVERSE_ALL, -1, source_viewer_sort_nodes, NULL); g_hash_table_destroy (index); return root; } gint main (gint argc, gchar **argv) { GtkWidget *viewer; GError *error = NULL; bindtextdomain (GETTEXT_PACKAGE, EVOLUTION_LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); gtk_init (&argc, &argv); viewer = e_source_viewer_new (NULL, &error); if (error != NULL) { g_warn_if_fail (viewer == NULL); g_error ("%s", error->message); g_assert_not_reached (); } g_signal_connect ( viewer, "delete-event", G_CALLBACK (gtk_main_quit), NULL); gtk_widget_show (viewer); gtk_main (); return 0; }