/* Screem: screem-tree-view.c * * The tree view widget * * Copyright (C) 2002 David A Knight * * This program 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 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "screem-tree-view.h" #include "screem-window.h" #include "screem-markup.h" #include "screem-dtd.h" #include "screemmarshal.h" #include enum { SELECT_CONTEXT, SET_CURSOR, LAST_SIGNAL }; static guint screem_tree_view_signals[ LAST_SIGNAL ] = { 0 }; static void screem_tree_view_display( ScreemView *view ); static void screem_tree_view_page( ScreemTreeView *view, ScreemPage *page ); static void page_change( ScreemPage *page, GParamSpec *spec, gpointer data ); static void tree_building( ScreemPage *page, gpointer data ); static void tree_built( ScreemPage *page, gpointer data ); static gboolean build_tree( ScreemTreeView *view ); static void screem_tree_view_create_mini_view( ScreemTreeView *view ); static void screem_tree_view_mini_activate( GtkTreeView *mini, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data ); static void screem_tree_view_sel_changed( GtkTreeSelection *sel, ScreemTreeView *view ); struct ScreemTreeViewPrivate { GtkTreeModel *model; GtkTreeView *view; GtkTreeView *mini; guint timeout; ScreemPage *current; guint built; GtkWidget *sw; }; ScreemTreeView *screem_tree_view_new( ScreemWindow *window ) { ScreemTreeView *view; GType type; type = screem_tree_view_get_type(); view = SCREEM_TREE_VIEW( g_object_new( type, "window", window, NULL) ); return view; } /* static stuff */ static void screem_tree_view_display( ScreemView *view ) { ScreemPage *page; g_object_get( G_OBJECT( view ), "page", &page, NULL ); screem_tree_view_page( SCREEM_TREE_VIEW( view ), page ); if( page ) { g_object_unref( page ); } } static void screem_tree_view_page( ScreemTreeView *view, ScreemPage *page ) { if( view->priv->current == page ) { return; } gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( view->priv->sw ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC ); if( view->priv->timeout ) { g_source_remove( view->priv->timeout ); view->priv->timeout = 0; } if( view->priv->current ) { g_signal_handlers_disconnect_matched( G_OBJECT( view->priv->current ), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, view ); } view->priv->current = page; if( view->priv->built ) { g_source_remove( view->priv->built ); view->priv->built = 0; } if( page ) { view->priv->model = NULL; page_change( page, NULL, view ); g_signal_connect( G_OBJECT( page ), "notify::changed", G_CALLBACK( page_change ), view ); g_signal_connect( G_OBJECT( page ), "notify::dtd", G_CALLBACK( page_change ), view ); g_signal_connect( G_OBJECT( page ), "building", G_CALLBACK( tree_building ), view ); g_signal_connect( G_OBJECT( page ), "built", G_CALLBACK( tree_built ), view ); } } static void page_change( ScreemPage *page, GParamSpec *spec, gpointer data ) { ScreemTreeView *view; gboolean status; view = SCREEM_TREE_VIEW( data ); status = screem_page_get_changed( page ); status |= ( spec == NULL ); if( view->priv->timeout ) { g_source_remove( view->priv->timeout ); } view->priv->timeout = g_timeout_add( 1000, (GSourceFunc)build_tree, view ); } static void tree_building( ScreemPage *page, gpointer data ) { ScreemTreeView *view; ScreemTreeViewPrivate *priv; view = SCREEM_TREE_VIEW( data ); priv = view->priv; /* terminate timeout */ if( view->priv->timeout ) { g_source_remove( view->priv->timeout ); view->priv->timeout = 0; } gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( priv->sw ), GTK_POLICY_ALWAYS, GTK_POLICY_ALWAYS ); gtk_tree_view_set_model( priv->view, NULL ); gtk_tree_view_set_model( priv->mini, NULL ); } static gboolean tree_built_idle( gpointer data ) { ScreemPage *page; ScreemTreeView *view; ScreemTreeViewPrivate *priv; GtkTreeModel *model; GtkTreePath *path; GtkTreeIter it; gboolean collapse; GtkWidget *widget; gdk_threads_enter(); view = SCREEM_TREE_VIEW( data ); priv = view->priv; page = priv->current; model = screem_page_get_model( page ); priv->model = model; path = gtk_tree_path_new_first(); gtk_tree_path_next( path ); collapse = gtk_tree_model_get_iter( model, &it, path ); gtk_tree_view_set_model( priv->mini, priv->model ); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( priv->sw ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC ); widget = GTK_WIDGET( priv->mini ); gtk_tree_view_expand_all( GTK_TREE_VIEW( widget ) ); if( collapse && screem_page_is_markup( page ) ) { gtk_tree_view_collapse_row( GTK_TREE_VIEW( widget ), path ); gtk_tree_path_next( path ); gtk_tree_view_collapse_row( GTK_TREE_VIEW( widget ), path ); } widget = GTK_WIDGET( priv->view ); if( GTK_WIDGET_VISIBLE( widget ) ) { gtk_tree_view_set_model( priv->view, priv->model ); gtk_tree_view_expand_all( GTK_TREE_VIEW( widget ) ); if( collapse && screem_page_is_markup( page ) ) { gtk_tree_view_collapse_row( GTK_TREE_VIEW( widget ), path ); gtk_tree_path_prev( path ); gtk_tree_view_collapse_row( GTK_TREE_VIEW( widget ), path ); } } gtk_tree_path_free( path ); priv->built = 0; gdk_threads_leave(); return FALSE; } static void tree_built( ScreemPage *page, gpointer data ) { ScreemTreeView *view; ScreemTreeViewPrivate *priv; view = SCREEM_TREE_VIEW( data ); priv = view->priv; if( priv->built ) { g_source_remove( priv->built ); } priv->built = g_idle_add_full( G_PRIORITY_LOW, (GSourceFunc)tree_built_idle, data, NULL ); } static gboolean build_tree( ScreemTreeView *view ) { ScreemTreeViewPrivate *priv; ScreemPage *page; priv = view->priv; /* called via timeout, do not claim the gdk * lock though, as this will be handled * by the ScreemPageModel as needed */ page = priv->current; if( page ) { screem_page_build_model( page ); view->priv->timeout = 0; } return FALSE; } static void screem_tree_view_create_mini_view( ScreemTreeView *view ) { ScreemTreeViewPrivate *priv; GtkWidget *box; GtkWidget *tree; GtkWidget *sw; GtkCellRenderer *rend; GtkCellRenderer *prend; GtkTreeViewColumn *col; ScreemWindow *window; priv = view->priv; box = gtk_vbox_new( FALSE, 0 ); gtk_widget_show( box ); tree = gtk_tree_view_new_with_model( GTK_TREE_MODEL(priv->model) ); priv->mini = GTK_TREE_VIEW( tree ); gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( tree ), TRUE ); priv->sw = sw = gtk_scrolled_window_new( NULL, NULL ); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC ); gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( sw ), GTK_SHADOW_IN ); prend = gtk_cell_renderer_pixbuf_new(); rend = gtk_cell_renderer_text_new (); col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title( col, _( "Element" ) ); gtk_tree_view_column_pack_start( col, prend, FALSE ); gtk_tree_view_column_pack_start( col, rend, TRUE ); gtk_tree_view_column_set_attributes( col, rend, "text", SCREEM_PAGE_MODEL_NAME, NULL ); gtk_tree_view_column_set_attributes( col, prend, "pixbuf", SCREEM_PAGE_MODEL_ICON, NULL ); gtk_tree_view_column_set_resizable( col, TRUE ); gtk_tree_view_append_column( GTK_TREE_VIEW( tree ), col ); rend = gtk_cell_renderer_toggle_new(); col = gtk_tree_view_column_new_with_attributes( _( "Valid" ), rend, "active", SCREEM_PAGE_MODEL_VALID, NULL ); gtk_tree_view_append_column( GTK_TREE_VIEW( tree ), col ); gtk_container_add( GTK_CONTAINER( sw ), tree ); gtk_widget_show( GTK_WIDGET( tree ) ); gtk_widget_show( sw ); gtk_box_pack_start( GTK_BOX( box ), sw, TRUE, TRUE, 0 ); g_object_get( G_OBJECT( view ), "window", &window, NULL ); screem_window_add_dock_item( window, _( "Tree" ), GTK_STOCK_INDEX, "Structure", box, GTK_POS_RIGHT ); g_signal_connect( G_OBJECT( tree ), "row_activated", G_CALLBACK( screem_tree_view_mini_activate ), view ); } static void screem_tree_view_mini_activate( GtkTreeView *mini, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data ) { ScreemTreeView *view; GtkTreeModel *model; GtkTreeIter it; guint start; guint end; view = SCREEM_TREE_VIEW( data ); model = gtk_tree_view_get_model( mini ); gtk_tree_model_get_iter( GTK_TREE_MODEL( model ), &it, path ); gtk_tree_model_get( model, &it, SCREEM_PAGE_MODEL_START, &start, SCREEM_PAGE_MODEL_END, &end, -1 ); /* emit signal */ if( ( start != end ) || start != 0 ) { g_signal_emit( G_OBJECT( view ), screem_tree_view_signals[ SELECT_CONTEXT ], 0, start, end ); } } static void screem_tree_view_sel_changed( GtkTreeSelection *sel, ScreemTreeView *view ) { ScreemTreeViewPrivate *priv; GtkTreeModel *model; GtkTreeIter it; guint start; guint end; priv = view->priv; if( gtk_tree_selection_get_selected( sel, &model, &it ) ) { gtk_tree_model_get( model, &it, SCREEM_PAGE_MODEL_START, &start, SCREEM_PAGE_MODEL_END, &end, -1 ); g_signal_emit( G_OBJECT( view ), screem_tree_view_signals[ SET_CURSOR ], 0, start ); } } /* G Object stuff */ static void screem_view_iface_init( ScreemView *iface ); G_DEFINE_TYPE( ScreemTreeView, screem_tree_view, SCREEM_TYPE_VIEW ) static void screem_tree_view_finalize( GObject *tree_view ); static void screem_tree_view_show( GtkWidget *widget ); static void screem_tree_view_hide( GtkWidget *widget ); static void screem_tree_view_window_set( ScreemView *view ); static void screem_tree_view_class_init( ScreemTreeViewClass *klass ) { GObjectClass *object_class; GtkWidgetClass *widget_class; object_class = G_OBJECT_CLASS( klass ); widget_class = (GtkWidgetClass *)klass; object_class->finalize = screem_tree_view_finalize; widget_class->show = screem_tree_view_show; widget_class->hide = screem_tree_view_hide; screem_tree_view_signals[ SELECT_CONTEXT ] = g_signal_new( "select_context", G_OBJECT_CLASS_TYPE( object_class ), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET( ScreemTreeViewClass, select_context), NULL, NULL, screem_marshal_VOID__UINT_UINT, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT ); screem_tree_view_signals[ SET_CURSOR ] = g_signal_new( "set_cursor", G_OBJECT_CLASS_TYPE( object_class ), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET( ScreemTreeViewClass, set_cursor ), NULL, NULL, screem_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT ); } static void screem_view_iface_init( ScreemView *iface ) { /* we only support display */ iface->display = screem_tree_view_display; } static void screem_tree_view_init( ScreemTreeView *tree_view ) { ScreemTreeViewPrivate *priv; GtkWidget *sw; GtkCellRenderer *rend; GtkCellRenderer *prend; GtkTreeViewColumn *col; GtkTreeSelection *sel; screem_view_iface_init( SCREEM_VIEW( tree_view ) ); g_signal_connect( G_OBJECT( tree_view ), "notify::window", G_CALLBACK( screem_tree_view_window_set ), NULL ); priv = tree_view->priv = g_new0( ScreemTreeViewPrivate, 1 ); sw = gtk_scrolled_window_new( NULL, NULL ); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC ); gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( sw ), GTK_SHADOW_IN ); priv->model = NULL; priv->view = GTK_TREE_VIEW( gtk_tree_view_new_with_model( GTK_TREE_MODEL(priv->model) ) ); gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( priv->view ), TRUE ); prend = gtk_cell_renderer_pixbuf_new(); rend = gtk_cell_renderer_text_new (); col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title( col, _( "Element" ) ); gtk_tree_view_column_pack_start( col, prend, FALSE ); gtk_tree_view_column_pack_start( col, rend, TRUE ); gtk_tree_view_column_set_attributes( col, rend, "text", SCREEM_PAGE_MODEL_NAME, NULL ); gtk_tree_view_column_set_attributes( col, prend, "pixbuf", SCREEM_PAGE_MODEL_ICON, NULL ); gtk_tree_view_column_set_resizable( col, TRUE ); gtk_tree_view_append_column( GTK_TREE_VIEW( priv->view ), col ); rend = gtk_cell_renderer_text_new (); col = gtk_tree_view_column_new_with_attributes( _( "Text" ), rend, "text", SCREEM_PAGE_MODEL_TEXT, NULL ); gtk_tree_view_column_set_resizable( col, TRUE ); gtk_tree_view_append_column( GTK_TREE_VIEW( priv->view ), col ); rend = gtk_cell_renderer_toggle_new(); col = gtk_tree_view_column_new_with_attributes( _( "Valid" ), rend, "active", SCREEM_PAGE_MODEL_VALID, NULL ); gtk_tree_view_append_column( GTK_TREE_VIEW( priv->view ), col ); gtk_container_add( GTK_CONTAINER( sw ), GTK_WIDGET( priv->view ) ); gtk_container_add( GTK_CONTAINER( tree_view ), sw ); gtk_widget_show( GTK_WIDGET( priv->view ) ); gtk_widget_show( sw ); priv->timeout = 0; sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( priv->view ) ); g_signal_connect( G_OBJECT( sel ), "changed", G_CALLBACK( screem_tree_view_sel_changed ), tree_view ); } static void screem_tree_view_finalize( GObject *tree_view ) { ScreemTreeView *view; view = SCREEM_TREE_VIEW( tree_view ); if( view->priv->timeout ) { g_source_remove( view->priv->timeout ); } g_free( view->priv ); G_OBJECT_CLASS( screem_tree_view_parent_class )->finalize( tree_view ); } static void screem_tree_view_window_set( ScreemView *view ) { /* add miniview tab */ screem_tree_view_create_mini_view( SCREEM_TREE_VIEW( view ) ); } static void screem_tree_view_show( GtkWidget *widget ) { ScreemTreeView *view; GtkTreePath *path; GtkTreeIter it; view = SCREEM_TREE_VIEW( widget ); GTK_WIDGET_CLASS( screem_tree_view_parent_class )->show( widget ); gtk_tree_view_set_model( view->priv->view, view->priv->model ); gtk_widget_set_sensitive( GTK_WIDGET( view->priv->mini ), FALSE ); gtk_widget_show( GTK_WIDGET( view->priv->view ) ); gtk_tree_view_expand_all( GTK_TREE_VIEW( view->priv->view ) ); path = gtk_tree_path_new_first(); gtk_tree_path_next( path ); if( view->priv->model && screem_page_is_markup( view->priv->current ) && gtk_tree_model_get_iter( view->priv->model, &it, path ) ) { gtk_tree_view_collapse_row( GTK_TREE_VIEW( view->priv->view ), path ); gtk_tree_path_next( path ); gtk_tree_view_collapse_row( GTK_TREE_VIEW( view->priv->view ), path ); } gtk_tree_path_free( path ); } static void screem_tree_view_hide( GtkWidget *widget ) { ScreemTreeView *view; view = SCREEM_TREE_VIEW( widget ); GTK_WIDGET_CLASS( screem_tree_view_parent_class )->hide( widget ); gtk_widget_set_sensitive( GTK_WIDGET( view->priv->mini ), TRUE ); gtk_widget_hide( GTK_WIDGET( view->priv->view ) ); gtk_tree_view_set_model( view->priv->view, NULL ); }