3 * A file-manager extension which offers configurable context menu actions.
5 * Copyright (C) 2005 The GNOME Foundation
6 * Copyright (C) 2006-2008 Frederic Ruaudel and others (see AUTHORS)
7 * Copyright (C) 2009-2015 Pierre Wieser and others (see AUTHORS)
9 * FileManager-Actions is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of
12 * the License, or (at your option) any later version.
14 * FileManager-Actions is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with FileManager-Actions; see the file COPYING. If not, see
21 * <http://www.gnu.org/licenses/>.
24 * Frederic Ruaudel <grumz@grumz.net>
25 * Rodrigo Moya <rodrigo@gnome-db.org>
26 * Pierre Wieser <pwieser@trychlos.org>
27 * ... and many others (see AUTHORS)
34 #include "api/fma-object-api.h"
36 #include "core/fma-gtk-utils.h"
38 #include "base-keysyms.h"
39 #include "fma-application.h"
40 #include "fma-main-window.h"
41 #include "fma-tree-view.h"
42 #include "fma-tree-model.h"
43 #include "fma-tree-ieditable.h"
45 /* private instance data
47 struct _FMATreeViewPrivate
{
48 gboolean dispose_has_run
;
50 /* properties set at instanciation time
52 FMAMainWindow
*window
;
57 gboolean notify_allowed
;
61 GtkTreeView
*tree_view
;
77 /* when toggle collapse/expand rows, we want all rows have the same
78 * behavior, e.g. all rows collapse, or all rows expand
79 * this behavior is fixed by the first rows which will actually be
88 /* iter on selection prototype
90 typedef gboolean ( *FnIterOnSelection
)( FMATreeView
*, GtkTreeModel
*, GtkTreeIter
*, FMAObject
*, gpointer
);
92 static gint st_signals
[ LAST_SIGNAL
] = { 0 };
93 static GObjectClass
*st_parent_class
= NULL
;
95 static GType
register_type( void );
96 static void class_init( FMATreeViewClass
*klass
);
97 static void tree_ieditable_iface_init( FMATreeIEditableInterface
*iface
, void *user_data
);
98 static void instance_init( GTypeInstance
*instance
, gpointer klass
);
99 static void instance_dispose( GObject
*application
);
100 static void instance_finalize( GObject
*application
);
101 static void initialize_gtk( FMATreeView
*view
);
102 static gboolean
on_button_press_event( GtkWidget
*widget
, GdkEventButton
*event
, FMATreeView
*view
);
103 static gboolean
on_focus_in( GtkWidget
*widget
, GdkEventFocus
*event
, FMATreeView
*view
);
104 static gboolean
on_focus_out( GtkWidget
*widget
, GdkEventFocus
*event
, FMATreeView
*view
);
105 static gboolean
on_key_pressed_event( GtkWidget
*widget
, GdkEventKey
*event
, FMATreeView
*view
);
106 static gboolean
on_popup_menu( GtkWidget
*widget
, FMATreeView
*view
);
107 static void on_selection_changed( GtkTreeSelection
*selection
, FMATreeView
*view
);
108 static void on_tree_view_realized( FMATreeView
*treeview
, void *empty
);
109 static void clear_selection( FMATreeView
*view
);
110 static void on_selection_changed_cleanup_handler( FMATreeView
*tview
, GList
*selected_items
);
111 static void display_label( GtkTreeViewColumn
*column
, GtkCellRenderer
*cell
, GtkTreeModel
*model
, GtkTreeIter
*iter
, FMATreeView
*view
);
112 static void extend_selection_to_children( FMATreeView
*view
, GtkTreeModel
*model
, GtkTreeIter
*parent
);
113 static GList
*get_selected_items( FMATreeView
*view
);
114 static void iter_on_selection( FMATreeView
*view
, FnIterOnSelection fn_iter
, gpointer user_data
);
115 static void navigate_to_child( FMATreeView
*view
);
116 static void navigate_to_parent( FMATreeView
*view
);
117 static void do_open_popup( FMATreeView
*view
, GdkEventButton
*event
);
118 static void select_row_at_path_by_string( FMATreeView
*view
, const gchar
*path
);
119 static void toggle_collapse( FMATreeView
*view
);
120 static gboolean
toggle_collapse_iter( FMATreeView
*view
, GtkTreeModel
*model
, GtkTreeIter
*iter
, FMAObject
*object
, gpointer user_data
);
121 static void toggle_collapse_row( GtkTreeView
*treeview
, GtkTreePath
*path
, guint
*toggle
);
124 fma_tree_view_get_type( void )
126 static GType type
= 0;
128 static const GInterfaceInfo tree_ieditable_iface_info
= {
129 ( GInterfaceInitFunc
) tree_ieditable_iface_init
,
135 type
= register_type();
136 g_type_add_interface_static( type
, FMA_TREE_IEDITABLE_TYPE
, &tree_ieditable_iface_info
);
143 register_type( void )
145 static const gchar
*thisfn
= "fma_tree_view_register_type";
148 static GTypeInfo info
= {
149 sizeof( FMATreeViewClass
),
150 ( GBaseInitFunc
) NULL
,
151 ( GBaseFinalizeFunc
) NULL
,
152 ( GClassInitFunc
) class_init
,
155 sizeof( FMATreeView
),
157 ( GInstanceInitFunc
) instance_init
160 g_debug( "%s", thisfn
);
162 type
= g_type_register_static( GTK_TYPE_BIN
, "FMATreeView", &info
, 0 );
168 class_init( FMATreeViewClass
*klass
)
170 static const gchar
*thisfn
= "fma_tree_view_class_init";
171 GObjectClass
*object_class
;
173 g_debug( "%s: klass=%p", thisfn
, ( void * ) klass
);
175 st_parent_class
= g_type_class_peek_parent( klass
);
177 object_class
= G_OBJECT_CLASS( klass
);
178 object_class
->dispose
= instance_dispose
;
179 object_class
->finalize
= instance_finalize
;
182 * FMATreeView::tree-signal-count-changed:
184 * This signal is emitted on BaseWindow parent when the count of items
185 * has changed in the underlying tree store.
187 * Counting rows is needed to maintain action sensitivities in the
188 * menubar : at least 'Tools\Export' menu item depends if we have
189 * exportables, i.e. at least one menu or action.
190 * Also, the total count of menu/actions/profiles is displayed in the
193 * Rows are first counted when the treeview is primarily filled, or
194 * refilled on demand. Counters are then incremented (resp. decremented)
195 * when inserting or pasting (resp. deleting) rows in the list.
198 * - reset if %TRUE, add if %FALSE
199 * - menus count/increment (may be negative)
200 * - actions count/increment (may be negative)
201 * - profiles count/increment (may be negative)
204 * void ( *handler )( BaseWindow *window, gboolean reset,
205 * guint menus_count, guint actions_count, guint profiles_count, gpointer user_data );
207 st_signals
[ COUNT_CHANGED
] = g_signal_new(
208 TREE_SIGNAL_COUNT_CHANGED
,
211 0, /* no default handler */
217 G_TYPE_BOOLEAN
, G_TYPE_INT
, G_TYPE_INT
, G_TYPE_INT
);
220 * FMATreeView::tree-signal-focus-in:
222 * This signal is emitted on the window when the view gains the focus.
223 * In particular, edition menu is disabled outside of the treeview.
228 * void ( *handler )( BaseWindow *window, gpointer user_data )
230 st_signals
[ FOCUS_IN
] = g_signal_new(
231 TREE_SIGNAL_FOCUS_IN
,
242 * FMATreeView::tree-signal-focus-out:
244 * This signal is emitted on the window when the view loses the focus.
245 * In particular, edition menu is disabled outside of the treeview.
250 * void ( *handler )( BaseWindow *window, gpointer user_data )
252 st_signals
[ FOCUS_OUT
] = g_signal_new(
253 TREE_SIGNAL_FOCUS_OUT
,
264 * FMATreeView::tree-signal-level-zero-changed:
266 * This signal is emitted on the BaseWindow each time the level zero
267 * has changed because an item has been removed or inserted, or when
268 * the level zero has been saved.
271 * - whether the level zero has changed (%TRUE), or comes to be saved
275 * void ( *handler )( BaseWindow *window, gboolean is_modified, gpointer user_data );
277 st_signals
[ LEVEL_ZERO_CHANGED
] = g_signal_new(
278 TREE_SIGNAL_LEVEL_ZERO_CHANGED
,
281 0, /* no default handler */
290 * FMATreeView::tree-signal-modified-status-changed:
292 * This signal is emitted on the BaseWindow when the view detects that
293 * the count of modified FMAObjectItems has changed, when an item is
294 * deleted, or when the level zero is changed.
296 * The signal is actually emitted by the FMATreeIEditable interface
297 * which takes care of all edition features.
300 * - the new modification status of the tree
303 * void ( *handler )( BaseWindow *window, gboolean is_modified, gpointer user_data )
305 st_signals
[ MODIFIED_STATUS
] = g_signal_new(
306 TREE_SIGNAL_MODIFIED_STATUS_CHANGED
,
318 * FMATreeView::tree-selection-changed:
320 * This signal is emitted on the treeview each time the selection
321 * has changed after having set the current item/profile/context
324 * This way, we are sure that notebook edition tabs which required
325 * to have a current item/profile/context will have it, whenever
326 * they have connected to this 'selection-changed' signal.
329 * - a #GList of currently selected #FMAObjectItems.
332 * void handler( FMATreeView *tview,
336 st_signals
[ SELECTION_CHANGED
] = g_signal_new_class_handler(
337 TREE_SIGNAL_SELECTION_CHANGED
,
339 G_SIGNAL_RUN_CLEANUP
,
340 G_CALLBACK( on_selection_changed_cleanup_handler
),
349 * FMATreeView::tree-signal-open-popup
351 * This signal is emitted on the treeview when the user right
352 * clicks somewhere (on an active zone).
358 * void handler( FMATreeView *tview,
362 st_signals
[ OPEN_POPUP
] = g_signal_new(
363 TREE_SIGNAL_CONTEXT_MENU
,
376 tree_ieditable_iface_init( FMATreeIEditableInterface
*iface
, void *user_data
)
378 static const gchar
*thisfn
= "fma_main_window_tree_ieditable_iface_init";
380 g_debug( "%s: iface=%p, user_data=%p", thisfn
, ( void * ) iface
, ( void * ) user_data
);
384 instance_init( GTypeInstance
*instance
, gpointer klass
)
386 static const gchar
*thisfn
= "fma_tree_view_instance_init";
389 g_return_if_fail( FMA_IS_TREE_VIEW( instance
));
391 g_debug( "%s: instance=%p (%s), klass=%p",
392 thisfn
, ( void * ) instance
, G_OBJECT_TYPE_NAME( instance
), ( void * ) klass
);
394 self
= FMA_TREE_VIEW( instance
);
396 self
->private = g_new0( FMATreeViewPrivate
, 1 );
398 self
->private->dispose_has_run
= FALSE
;
402 instance_dispose( GObject
*object
)
404 static const gchar
*thisfn
= "fma_tree_view_instance_dispose";
407 g_return_if_fail( FMA_IS_TREE_VIEW( object
));
409 self
= FMA_TREE_VIEW( object
);
411 if( !self
->private->dispose_has_run
){
412 g_debug( "%s: object=%p (%s)", thisfn
, ( void * ) object
, G_OBJECT_TYPE_NAME( object
));
414 self
->private->dispose_has_run
= TRUE
;
416 if( self
->private->mode
== TREE_MODE_EDITION
){
417 fma_tree_ieditable_terminate( FMA_TREE_IEDITABLE( self
));
420 /* chain up to the parent class */
421 if( G_OBJECT_CLASS( st_parent_class
)->dispose
){
422 G_OBJECT_CLASS( st_parent_class
)->dispose( object
);
428 instance_finalize( GObject
*instance
)
430 static const gchar
*thisfn
= "fma_tree_view_instance_finalize";
433 g_return_if_fail( FMA_IS_TREE_VIEW( instance
));
435 g_debug( "%s: instance=%p (%s)", thisfn
, ( void * ) instance
, G_OBJECT_TYPE_NAME( instance
));
437 self
= FMA_TREE_VIEW( instance
);
439 g_free( self
->private );
441 /* chain call to parent class */
442 if( G_OBJECT_CLASS( st_parent_class
)->finalize
){
443 G_OBJECT_CLASS( st_parent_class
)->finalize( instance
);
450 * Returns: a newly allocated FMATreeView object, which will be owned
451 * by the caller. It is useless to unref it as it will be automatically
452 * destroyed at @window finalization.
455 fma_tree_view_new( FMAMainWindow
*main_window
)
459 view
= g_object_new( FMA_TYPE_TREE_VIEW
, NULL
);
460 view
->private->window
= main_window
;
462 initialize_gtk( view
);
464 /* delay all other signal connections until the widget be realized */
465 g_signal_connect( view
, "realize", G_CALLBACK( on_tree_view_realized
), NULL
);
471 initialize_gtk( FMATreeView
*view
)
473 static const gchar
*thisfn
= "fma_tree_view_initialize_gtk";
474 FMATreeViewPrivate
*priv
;
475 GtkWidget
*scrolled
, *tview
;
477 GtkTreeViewColumn
*column
;
478 GtkCellRenderer
*renderer
;
479 GtkTreeSelection
*selection
;
481 g_debug( "%s: view=%p", thisfn
, ( void * ) view
);
483 priv
= view
->private;
485 scrolled
= gtk_scrolled_window_new( NULL
, NULL
);
486 gtk_container_add( GTK_CONTAINER( view
), scrolled
);
487 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scrolled
), GTK_SHADOW_IN
);
489 tview
= gtk_tree_view_new();
490 gtk_widget_set_hexpand( tview
, TRUE
);
491 gtk_widget_set_vexpand( tview
, TRUE
);
492 gtk_container_add( GTK_CONTAINER( scrolled
), tview
);
493 priv
->tree_view
= GTK_TREE_VIEW( tview
);
494 gtk_tree_view_set_headers_visible( priv
->tree_view
, FALSE
);
496 model
= fma_tree_model_new( GTK_TREE_VIEW( tview
));
497 fma_tree_model_set_main_window( model
, priv
->window
);
498 gtk_tree_view_set_model( GTK_TREE_VIEW( tview
), GTK_TREE_MODEL( model
));
499 g_object_unref( model
);
501 /* create visible columns on the tree view
503 column
= gtk_tree_view_column_new_with_attributes(
505 gtk_cell_renderer_pixbuf_new(),
506 "pixbuf", TREE_COLUMN_ICON
,
508 gtk_tree_view_append_column( GTK_TREE_VIEW( tview
), column
);
510 renderer
= gtk_cell_renderer_text_new();
511 column
= gtk_tree_view_column_new_with_attributes(
514 "text", TREE_COLUMN_LABEL
,
516 gtk_tree_view_column_set_sort_column_id( column
, TREE_COLUMN_LABEL
);
517 gtk_tree_view_append_column( GTK_TREE_VIEW( tview
), column
);
519 /* always allow multiple selection
520 * - from main window: dnd to a file manager as a shortcut to export assistant
521 * - export assistant: selection of items
523 selection
= gtk_tree_view_get_selection( GTK_TREE_VIEW( tview
));
524 gtk_tree_selection_set_mode( selection
, GTK_SELECTION_MULTIPLE
);
525 g_signal_connect( selection
, "changed", G_CALLBACK( on_selection_changed
), view
);
529 gtk_tree_view_set_enable_tree_lines( GTK_TREE_VIEW( tview
), TRUE
);
533 * fma_tree_view_set_mnemonic:
534 * @view: this #FMATreeView
535 * @parent: a parent container of the mnemonic label
536 * @widget_name: the name of the mnemonic label
538 * Setup the mnemonic label
541 fma_tree_view_set_mnemonic( FMATreeView
*view
, GtkContainer
*parent
, const gchar
*widget_name
)
543 FMATreeViewPrivate
*priv
;
546 g_return_if_fail( view
&& FMA_IS_TREE_VIEW( view
));
547 g_return_if_fail( widget_name
&& g_utf8_strlen( widget_name
, -1 ));
549 priv
= view
->private;
551 if( !priv
->dispose_has_run
){
553 label
= fma_gtk_utils_find_widget_by_name( parent
, widget_name
);
554 g_return_if_fail( label
&& GTK_IS_LABEL( label
));
555 gtk_label_set_mnemonic_widget( GTK_LABEL( label
), GTK_WIDGET( priv
->tree_view
));
560 * fma_tree_view_set_edition_mode:
561 * @view: this #FMATreeView
562 * @mode: the edition mode
564 * Setup the edition mode
567 fma_tree_view_set_edition_mode( FMATreeView
*view
, guint mode
)
569 FMATreeViewPrivate
*priv
;
570 GtkTreeViewColumn
*column
;
572 GtkCellRenderer
*renderer
;
573 GtkTreeModel
*tmodel
;
575 g_return_if_fail( view
&& FMA_IS_TREE_VIEW( view
));
577 priv
= view
->private;
579 if( !priv
->dispose_has_run
){
583 switch( priv
->mode
){
586 case TREE_MODE_EDITION
:
588 column
= gtk_tree_view_get_column( priv
->tree_view
, TREE_COLUMN_LABEL
);
589 renderers
= gtk_cell_layout_get_cells( GTK_CELL_LAYOUT( column
));
590 renderer
= GTK_CELL_RENDERER( renderers
->data
);
591 gtk_tree_view_column_set_cell_data_func(
592 column
, renderer
, ( GtkTreeCellDataFunc
) display_label
, view
, NULL
);
594 fma_tree_ieditable_initialize(
595 FMA_TREE_IEDITABLE( view
), priv
->tree_view
, priv
->window
);
598 /* export assistant */
599 case TREE_MODE_SELECTION
:
603 tmodel
= gtk_tree_view_get_model( priv
->tree_view
);
604 g_return_if_fail( tmodel
&& FMA_IS_TREE_MODEL( tmodel
));
605 fma_tree_model_set_edition_mode( FMA_TREE_MODEL( tmodel
), mode
);
610 on_button_press_event( GtkWidget
*widget
, GdkEventButton
*event
, FMATreeView
*view
)
612 gboolean stop
= FALSE
;
614 /* single click on right button */
615 if( event
->type
== GDK_BUTTON_PRESS
&& event
->button
== 3 ){
616 do_open_popup( view
, event
);
624 * focus is monitored to avoid an accelerator being pressed while on a tab
625 * triggers an unwaited operation on the list
626 * e.g. when editing an entry field on the tab, pressing Del should _not_
627 * delete current row in the list!
630 on_focus_in( GtkWidget
*widget
, GdkEventFocus
*event
, FMATreeView
*view
)
632 gboolean stop
= FALSE
;
634 g_signal_emit_by_name( view
, TREE_SIGNAL_FOCUS_IN
);
640 on_focus_out( GtkWidget
*widget
, GdkEventFocus
*event
, FMATreeView
*view
)
642 gboolean stop
= FALSE
;
644 g_signal_emit_by_name( view
, TREE_SIGNAL_FOCUS_OUT
);
650 on_key_pressed_event( GtkWidget
*widget
, GdkEventKey
*event
, FMATreeView
*view
)
652 gboolean stop
= FALSE
;
654 if( event
->keyval
== FMA_KEY_Return
|| event
->keyval
== FMA_KEY_KP_Enter
){
655 toggle_collapse( view
);
658 if( event
->keyval
== FMA_KEY_Right
){
659 navigate_to_child( view
);
662 if( event
->keyval
== FMA_KEY_Left
){
663 navigate_to_parent( view
);
671 * triggered by the "popup-menu" signal, itself triggered by the keybindings
674 on_popup_menu( GtkWidget
*widget
, FMATreeView
*view
)
676 do_open_popup( view
, NULL
);
680 * handles the "changed" signal emitted on the GtkTreeSelection
683 on_selection_changed( GtkTreeSelection
*selection
, FMATreeView
*view
)
685 static const gchar
*thisfn
= "fma_tree_view_on_selection_changed";
686 GList
*selected_items
;
688 if( view
->private->notify_allowed
){
689 g_debug( "%s: selection=%p, view=%p", thisfn
, ( void * ) selection
, ( void * ) view
);
690 selected_items
= get_selected_items( view
);
691 g_signal_emit_by_name( view
, TREE_SIGNAL_SELECTION_CHANGED
, selected_items
);
696 * the FMATreeView is realized
699 on_tree_view_realized( FMATreeView
*treeview
, void *empty
)
701 FMATreeViewPrivate
*priv
;
703 g_debug( "fma_tree_view_on_tree_view_realized" );
705 priv
= treeview
->private;
707 /* expand/collapse with the keyboard */
709 priv
->tree_view
, "key-press-event", G_CALLBACK( on_key_pressed_event
), treeview
);
711 /* monitors whether the focus is on the view */
713 priv
->tree_view
, "focus-in-event", G_CALLBACK( on_focus_in
), treeview
);
716 priv
->tree_view
, "focus-out-event", G_CALLBACK( on_focus_out
), treeview
);
718 /* monitors mouse events */
720 priv
->tree_view
, "button-press-event", G_CALLBACK( on_button_press_event
), treeview
);
723 priv
->tree_view
, "popup-menu", G_CALLBACK( on_popup_menu
), treeview
);
725 /* force the treeview to have the focus at start
726 * and select the first row if it exists
728 gtk_widget_grab_focus( GTK_WIDGET( priv
->tree_view
));
732 * fma_tree_view_fill:
733 * @view: this #FMATreeView instance.
735 * Fill the tree view with the provided list of items.
737 * Notification of selection changes is temporary suspended during the
738 * fillup of the list, so that client is not cluttered with tons of
739 * selection-changed messages.
742 fma_tree_view_fill( FMATreeView
*view
, GList
*items
)
744 static const gchar
*thisfn
= "fma_tree_view_fill";
746 gint nb_menus
, nb_actions
, nb_profiles
;
748 g_return_if_fail( FMA_IS_TREE_VIEW( view
));
750 if( !view
->private->dispose_has_run
){
751 g_debug( "%s: view=%p, items=%p (count=%u)",
752 thisfn
, ( void * ) view
, ( void * ) items
, g_list_length( items
));
754 clear_selection( view
);
755 view
->private->notify_allowed
= FALSE
;
756 model
= FMA_TREE_MODEL( gtk_tree_view_get_model( view
->private->tree_view
));
757 fma_tree_model_fill( model
, items
);
758 g_debug( "%s: fma_tree_model_ref_count=%d", thisfn
, G_OBJECT( model
)->ref_count
);
760 view
->private->notify_allowed
= TRUE
;
761 fma_object_count_items( items
, &nb_menus
, &nb_actions
, &nb_profiles
);
762 g_signal_emit_by_name( view
, TREE_SIGNAL_COUNT_CHANGED
, TRUE
, nb_menus
, nb_actions
, nb_profiles
);
763 g_signal_emit_by_name( view
, TREE_SIGNAL_MODIFIED_STATUS_CHANGED
, FALSE
);
765 select_row_at_path_by_string( view
, "0" );
770 * fma_tree_view_are_notify_allowed:
771 * @view: this #FMATreeView instance.
773 * Returns: %TRUE if notifications are allowed, %FALSE else.
776 fma_tree_view_are_notify_allowed( const FMATreeView
*view
)
778 gboolean are_allowed
;
780 g_return_val_if_fail( FMA_IS_TREE_VIEW( view
), FALSE
);
784 if( !view
->private->dispose_has_run
){
786 are_allowed
= view
->private->notify_allowed
;
789 return( are_allowed
);
793 * fma_tree_view_set_notify_allowed:
794 * @view: this #FMATreeView instance.
795 * @allow: whether the notifications are to be allowed.
798 fma_tree_view_set_notify_allowed( FMATreeView
*view
, gboolean allow
)
800 g_return_if_fail( FMA_IS_TREE_VIEW( view
));
802 if( !view
->private->dispose_has_run
){
804 view
->private->notify_allowed
= allow
;
809 * fma_tree_view_collapse_all:
810 * @view: this #FMATreeView instance.
812 * Collapse all the tree hierarchy.
815 fma_tree_view_collapse_all( const FMATreeView
*view
)
817 g_return_if_fail( FMA_IS_TREE_VIEW( view
));
819 if( !view
->private->dispose_has_run
){
821 gtk_tree_view_collapse_all( view
->private->tree_view
);
826 * fma_tree_view_expand_all:
827 * @view: this #FMATreeView instance.
829 * Collapse all the tree hierarchy.
832 fma_tree_view_expand_all( const FMATreeView
*view
)
834 g_return_if_fail( FMA_IS_TREE_VIEW( view
));
836 if( !view
->private->dispose_has_run
){
838 gtk_tree_view_expand_all( view
->private->tree_view
);
843 * fma_tree_view_get_item_by_id:
844 * @view: this #FMATreeView instance.
845 * @id: the searched #FMAObjectItem.
847 * Returns: a pointer on the searched #FMAObjectItem if it exists, or %NULL.
849 * The returned pointer is owned by the underlying tree store, and should
850 * not be released by the caller.
853 fma_tree_view_get_item_by_id( const FMATreeView
*view
, const gchar
*id
)
858 g_return_val_if_fail( FMA_IS_TREE_VIEW( view
), NULL
);
862 if( !view
->private->dispose_has_run
){
864 model
= FMA_TREE_MODEL( gtk_tree_view_get_model( view
->private->tree_view
));
865 item
= fma_tree_model_get_item_by_id( model
, id
);
872 * fma_tree_view_get_items:
873 * @view: this #FMATreeView instance.
875 * Returns: the content of the current tree as a newly allocated list
876 * which should be fma_object_free_items() by the caller.
879 fma_tree_view_get_items( const FMATreeView
*view
)
881 return( fma_tree_view_get_items_ex( view
, TREE_LIST_ALL
));
885 * fma_tree_view_get_items_ex:
886 * @view: this #FMATreeView instance.
887 * @mode: the list content
889 * Returns: the content of the current tree as a newly allocated list
890 * which should be fma_object_free_items() by the caller.
893 fma_tree_view_get_items_ex( const FMATreeView
*view
, guint mode
)
899 g_return_val_if_fail( FMA_IS_TREE_VIEW( view
), NULL
);
903 if( !view
->private->dispose_has_run
){
907 if( view
->private->mode
== TREE_MODE_EDITION
){
908 if( mode
& TREE_LIST_DELETED
){
909 deleted
= fma_tree_ieditable_get_deleted( FMA_TREE_IEDITABLE( view
));
913 model
= FMA_TREE_MODEL( gtk_tree_view_get_model( view
->private->tree_view
));
914 items
= fma_tree_model_get_items( model
, mode
);
916 items
= g_list_concat( items
, deleted
);
923 * fma_tree_view_select_row_at_path:
924 * @view: this #FMATreeView object.
925 * @path: the #GtkTreePath to be selected.
927 * Select the row at the required path, or the immediate previous, or
928 * the next following, or eventually the immediate parent.
930 * If nothing can be selected (and notify is allowed), at least send a
931 * message with an empty selection.
934 fma_tree_view_select_row_at_path( FMATreeView
*view
, GtkTreePath
*path
)
936 static const gchar
*thisfn
= "fma_tree_view_select_row_at_path";
940 gboolean something
= FALSE
;
942 g_return_if_fail( FMA_IS_TREE_VIEW( view
));
944 if( !view
->private->dispose_has_run
){
946 path_str
= gtk_tree_path_to_string( path
);
947 g_debug( "%s: view=%p, path=%s", thisfn
, ( void * ) view
, path_str
);
951 gtk_tree_view_expand_to_path( view
->private->tree_view
, path
);
952 model
= gtk_tree_view_get_model( view
->private->tree_view
);
954 if( gtk_tree_model_get_iter( model
, &iter
, path
)){
955 gtk_tree_view_set_cursor( view
->private->tree_view
, path
, NULL
, FALSE
);
958 } else if( gtk_tree_path_prev( path
) && gtk_tree_model_get_iter( model
, &iter
, path
)){
959 gtk_tree_view_set_cursor( view
->private->tree_view
, path
, NULL
, FALSE
);
963 gtk_tree_path_next( path
);
964 if( gtk_tree_model_get_iter( model
, &iter
, path
)){
965 gtk_tree_view_set_cursor( view
->private->tree_view
, path
, NULL
, FALSE
);
968 } else if( gtk_tree_path_get_depth( path
) > 1 &&
969 gtk_tree_path_up( path
) &&
970 gtk_tree_model_get_iter( model
, &iter
, path
)){
972 gtk_tree_view_set_cursor( view
->private->tree_view
, path
, NULL
, FALSE
);
979 if( view
->private->notify_allowed
){
980 g_signal_emit_by_name( view
, TREE_SIGNAL_SELECTION_CHANGED
, NULL
);
987 clear_selection( FMATreeView
*view
)
989 GtkTreeSelection
*selection
;
991 selection
= gtk_tree_view_get_selection( view
->private->tree_view
);
992 gtk_tree_selection_unselect_all( selection
);
996 * signal cleanup handler
999 on_selection_changed_cleanup_handler( FMATreeView
*tview
, GList
*selected_items
)
1001 static const gchar
*thisfn
= "fma_tree_view_on_selection_changed_cleanup_handler";
1003 g_debug( "%s: tview=%p, selected_items=%p (count=%u)",
1004 thisfn
, ( void * ) tview
,
1005 ( void * ) selected_items
, g_list_length( selected_items
));
1007 fma_object_free_items( selected_items
);
1011 * item modified: italic
1012 * item not saveable (invalid): red
1015 display_label( GtkTreeViewColumn
*column
, GtkCellRenderer
*cell
, GtkTreeModel
*model
, GtkTreeIter
*iter
, FMATreeView
*view
)
1020 g_return_if_fail( view
->private->mode
== TREE_MODE_EDITION
);
1022 gtk_tree_model_get( model
, iter
, TREE_COLUMN_NAOBJECT
, &object
, -1 );
1025 g_object_unref( object
);
1026 g_return_if_fail( FMA_IS_OBJECT( object
));
1028 label
= fma_object_get_label( object
);
1029 g_object_set( cell
, "style-set", FALSE
, NULL
);
1030 g_object_set( cell
, "foreground-set", FALSE
, NULL
);
1032 if( fma_object_is_modified( object
)){
1033 g_object_set( cell
, "style", PANGO_STYLE_ITALIC
, "style-set", TRUE
, NULL
);
1036 if( !fma_object_is_valid( object
)){
1037 g_object_set( cell
, "foreground", "Red", "foreground-set", TRUE
, NULL
);
1040 g_object_set( cell
, "text", label
, NULL
);
1046 * when expanding a selected row which has children
1049 extend_selection_to_children( FMATreeView
*view
, GtkTreeModel
*model
, GtkTreeIter
*parent
)
1051 GtkTreeSelection
*selection
;
1055 selection
= gtk_tree_view_get_selection( view
->private->tree_view
);
1056 ok
= gtk_tree_model_iter_children( model
, &iter
, parent
);
1059 GtkTreePath
*path
= gtk_tree_model_get_path( model
, &iter
);
1060 gtk_tree_selection_select_path( selection
, path
);
1061 gtk_tree_path_free( path
);
1062 ok
= gtk_tree_model_iter_next( model
, &iter
);
1067 * get_selected_items:
1068 * @view: this #FMATreeView instance.
1070 * We acquire here a new reference on objects corresponding to actually
1071 * selected rows, and their children.
1073 * Returns: the currently selected rows as a #GList of #FMAObjectItems
1074 * which should be fma_object_free_items() by the caller.
1077 get_selected_items( FMATreeView
*view
)
1079 static const gchar
*thisfn
= "fma_tree_view_get_selected_items";
1081 GtkTreeSelection
*selection
;
1082 GtkTreeModel
*model
;
1085 GList
*it
, *listrows
;
1086 FMAObjectId
*object
;
1089 selection
= gtk_tree_view_get_selection( view
->private->tree_view
);
1090 listrows
= gtk_tree_selection_get_selected_rows( selection
, &model
);
1092 for( it
= listrows
; it
; it
= it
->next
){
1093 path
= ( GtkTreePath
* ) it
->data
;
1094 gtk_tree_model_get_iter( model
, &iter
, path
);
1095 gtk_tree_model_get( model
, &iter
, TREE_COLUMN_NAOBJECT
, &object
, -1 );
1096 items
= g_list_prepend( items
, fma_object_ref( object
));
1097 g_object_unref( object
);
1098 g_debug( "%s: object=%p (%s) ref_count=%d",
1100 ( void * ) object
, G_OBJECT_TYPE_NAME( object
), G_OBJECT( object
)->ref_count
);
1103 g_list_foreach( listrows
, ( GFunc
) gtk_tree_path_free
, NULL
);
1104 g_list_free( listrows
);
1106 return( g_list_reverse( items
));
1110 iter_on_selection( FMATreeView
*view
, FnIterOnSelection fn_iter
, gpointer user_data
)
1112 GtkTreeSelection
*selection
;
1113 GtkTreeModel
*model
;
1114 GList
*listrows
, *ipath
;
1121 selection
= gtk_tree_view_get_selection( view
->private->tree_view
);
1122 listrows
= gtk_tree_selection_get_selected_rows( selection
, &model
);
1123 listrows
= g_list_reverse( listrows
);
1125 for( ipath
= listrows
; !stop
&& ipath
; ipath
= ipath
->next
){
1126 path
= ( GtkTreePath
* ) ipath
->data
;
1127 gtk_tree_model_get_iter( model
, &iter
, path
);
1128 gtk_tree_model_get( model
, &iter
, TREE_COLUMN_NAOBJECT
, &object
, -1 );
1130 stop
= fn_iter( view
, model
, &iter
, object
, user_data
);
1132 g_object_unref( object
);
1135 g_list_foreach( listrows
, ( GFunc
) gtk_tree_path_free
, NULL
);
1136 g_list_free( listrows
);
1140 * navigate_to_child:
1141 * @view: this #FMATreeView object.
1143 * On right arrow, if collapsed, then expand
1144 * if already expanded, then goto first child
1147 * a row which does not have any child is always considered as collapsed;
1148 * trying to expand it has no visible effect.
1151 navigate_to_child( FMATreeView
*view
)
1153 GtkTreeSelection
*selection
;
1155 GtkTreeModel
*model
;
1158 GtkTreePath
*child_path
;
1160 selection
= gtk_tree_view_get_selection( view
->private->tree_view
);
1161 listrows
= gtk_tree_selection_get_selected_rows( selection
, &model
);
1163 if( g_list_length( listrows
) == 1 ){
1164 path
= ( GtkTreePath
* ) listrows
->data
;
1166 if( !gtk_tree_view_row_expanded( view
->private->tree_view
, path
)){
1167 gtk_tree_view_expand_row( view
->private->tree_view
, path
, FALSE
);
1170 gtk_tree_model_get_iter( model
, &iter
, path
);
1172 if( gtk_tree_model_iter_has_child( model
, &iter
)){
1173 child_path
= gtk_tree_path_copy( path
);
1174 gtk_tree_path_append_index( child_path
, 0 );
1175 fma_tree_view_select_row_at_path( view
, child_path
);
1176 gtk_tree_path_free( child_path
);
1181 g_list_foreach( listrows
, ( GFunc
) gtk_tree_path_free
, NULL
);
1182 g_list_free( listrows
);
1186 * navigate_to_parent:
1187 * @view: this #FMATreeView object.
1189 * On left arrow, go to the parent.
1190 * if already on a parent, collapse it
1191 * if already collapsed, up to the next parent
1193 * e.g. path="2:0", depth=2
1194 * this means that depth is always >= 1, with depth=1 being the root.
1197 navigate_to_parent( FMATreeView
*view
)
1199 GtkTreeSelection
*selection
;
1200 GtkTreeModel
*model
;
1203 GtkTreePath
*parent_path
;
1205 selection
= gtk_tree_view_get_selection( view
->private->tree_view
);
1206 listrows
= gtk_tree_selection_get_selected_rows( selection
, &model
);
1208 if( g_list_length( listrows
) == 1 ){
1209 path
= ( GtkTreePath
* ) listrows
->data
;
1211 if( gtk_tree_view_row_expanded( view
->private->tree_view
, path
)){
1212 gtk_tree_view_collapse_row( view
->private->tree_view
, path
);
1214 } else if( gtk_tree_path_get_depth( path
) > 1 ){
1215 parent_path
= gtk_tree_path_copy( path
);
1216 gtk_tree_path_up( parent_path
);
1217 fma_tree_view_select_row_at_path( view
, parent_path
);
1218 gtk_tree_path_free( parent_path
);
1222 g_list_foreach( listrows
, ( GFunc
) gtk_tree_path_free
, NULL
);
1223 g_list_free( listrows
);
1227 do_open_popup( FMATreeView
*view
, GdkEventButton
*event
)
1229 FMATreeViewPrivate
*priv
;
1232 priv
= view
->private;
1235 if( gtk_tree_view_get_path_at_pos( priv
->tree_view
, event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
)){
1236 fma_tree_view_select_row_at_path( view
, path
);
1237 gtk_tree_path_free( path
);
1241 g_signal_emit_by_name( view
, TREE_SIGNAL_CONTEXT_MENU
, event
);
1245 * fma_tree_view_select_row_at_path_by_string:
1246 * @view: this #FMATreeView object.
1247 * @path: the #GtkTreePath to be selected.
1249 * cf. fma_tree_view_select_row_at_path().
1252 select_row_at_path_by_string( FMATreeView
*view
, const gchar
*path_str
)
1256 path
= gtk_tree_path_new_from_string( path_str
);
1257 fma_tree_view_select_row_at_path( view
, path
);
1258 gtk_tree_path_free( path
);
1262 * Toggle or collapse the current subtree.
1265 toggle_collapse( FMATreeView
*view
)
1267 guint toggle
= TOGGLE_UNDEFINED
;
1269 iter_on_selection( view
, ( FnIterOnSelection
) toggle_collapse_iter
, &toggle
);
1273 toggle_collapse_iter( FMATreeView
*view
, GtkTreeModel
*model
,
1274 GtkTreeIter
*iter
, FMAObject
*object
, gpointer user_data
)
1279 toggle
= ( guint
* ) user_data
;
1281 if( FMA_IS_OBJECT_ITEM( object
)){
1282 GtkTreePath
*path
= gtk_tree_model_get_path( model
, iter
);
1283 count
= fma_object_get_items_count( object
);
1285 if(( count
> 1 && FMA_IS_OBJECT_ACTION( object
)) ||
1286 ( count
> 0 && FMA_IS_OBJECT_MENU( object
))){
1288 toggle_collapse_row( view
->private->tree_view
, path
, toggle
);
1291 gtk_tree_path_free( path
);
1293 /* do not extend selection to children */
1295 if( *toggle
== TOGGLE_EXPAND
){
1296 extend_selection_to_children( view
, model
, iter
);
1301 /* do not stop iteration */
1306 * toggle mode can be undefined, collapse or expand
1307 * it is set on the first row
1310 toggle_collapse_row( GtkTreeView
*treeview
, GtkTreePath
*path
, guint
*toggle
)
1312 if( *toggle
== TOGGLE_UNDEFINED
){
1313 *toggle
= gtk_tree_view_row_expanded( treeview
, path
) ? TOGGLE_COLLAPSE
: TOGGLE_EXPAND
;
1316 if( *toggle
== TOGGLE_COLLAPSE
){
1317 if( gtk_tree_view_row_expanded( treeview
, path
)){
1318 gtk_tree_view_collapse_row( treeview
, path
);
1321 if( !gtk_tree_view_row_expanded( treeview
, path
)){
1322 gtk_tree_view_expand_row( treeview
, path
, TRUE
);