README: add deprecation notice
[nautilus-actions.git] / src / ui / fma-tree-view.c
blob641e2b132a3e98250d004bd4d8efce540aafc3ba
1 /*
2 * FileManager-Actions
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/>.
23 * Authors:
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)
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
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;
54 /* initialization
56 guint mode;
57 gboolean notify_allowed;
59 /* runtime data
61 GtkTreeView *tree_view;
64 /* signals
66 enum {
67 COUNT_CHANGED,
68 FOCUS_IN,
69 FOCUS_OUT,
70 LEVEL_ZERO_CHANGED,
71 MODIFIED_STATUS,
72 SELECTION_CHANGED,
73 OPEN_POPUP,
74 LAST_SIGNAL
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
80 * toggled
82 enum {
83 TOGGLE_UNDEFINED,
84 TOGGLE_COLLAPSE,
85 TOGGLE_EXPAND
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 );
123 GType
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,
130 NULL,
131 NULL
134 if( !type ){
135 type = register_type();
136 g_type_add_interface_static( type, FMA_TREE_IEDITABLE_TYPE, &tree_ieditable_iface_info );
139 return( type );
142 static GType
143 register_type( void )
145 static const gchar *thisfn = "fma_tree_view_register_type";
146 GType type;
148 static GTypeInfo info = {
149 sizeof( FMATreeViewClass ),
150 ( GBaseInitFunc ) NULL,
151 ( GBaseFinalizeFunc ) NULL,
152 ( GClassInitFunc ) class_init,
153 NULL,
154 NULL,
155 sizeof( FMATreeView ),
157 ( GInstanceInitFunc ) instance_init
160 g_debug( "%s", thisfn );
162 type = g_type_register_static( GTK_TYPE_BIN, "FMATreeView", &info, 0 );
164 return( type );
167 static void
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
191 * statusbar.
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.
197 * Signal args:
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)
203 * Handler prototype:
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,
209 FMA_TYPE_TREE_VIEW,
210 G_SIGNAL_RUN_LAST,
211 0, /* no default handler */
212 NULL,
213 NULL,
214 NULL,
215 G_TYPE_NONE,
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.
225 * Signal args: none
227 * Handler prototype:
228 * void ( *handler )( BaseWindow *window, gpointer user_data )
230 st_signals[ FOCUS_IN ] = g_signal_new(
231 TREE_SIGNAL_FOCUS_IN,
232 FMA_TYPE_TREE_VIEW,
233 G_SIGNAL_RUN_LAST,
235 NULL,
236 NULL,
237 NULL,
238 G_TYPE_NONE,
239 0 );
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.
247 * Signal args: none
249 * Handler prototype:
250 * void ( *handler )( BaseWindow *window, gpointer user_data )
252 st_signals[ FOCUS_OUT ] = g_signal_new(
253 TREE_SIGNAL_FOCUS_OUT,
254 FMA_TYPE_TREE_VIEW,
255 G_SIGNAL_RUN_LAST,
257 NULL,
258 NULL,
259 NULL,
260 G_TYPE_NONE,
261 0 );
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.
270 * Signal args:
271 * - whether the level zero has changed (%TRUE), or comes to be saved
272 * (%FALSE).
274 * Handler prototype:
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,
279 FMA_TYPE_TREE_VIEW,
280 G_SIGNAL_RUN_LAST,
281 0, /* no default handler */
282 NULL,
283 NULL,
284 NULL,
285 G_TYPE_NONE,
287 G_TYPE_BOOLEAN );
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.
299 * Signal args:
300 * - the new modification status of the tree
302 * Handler prototype:
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,
307 FMA_TYPE_TREE_VIEW,
308 G_SIGNAL_RUN_LAST,
310 NULL,
311 NULL,
312 NULL,
313 G_TYPE_NONE,
315 G_TYPE_BOOLEAN );
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
322 * properties.
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.
328 * Signal args:
329 * - a #GList of currently selected #FMAObjectItems.
331 * Handler prototype:
332 * void handler( FMATreeView *tview,
333 * GList *selected,
334 * void *user_data );
336 st_signals[ SELECTION_CHANGED ] = g_signal_new_class_handler(
337 TREE_SIGNAL_SELECTION_CHANGED,
338 FMA_TYPE_TREE_VIEW,
339 G_SIGNAL_RUN_CLEANUP,
340 G_CALLBACK( on_selection_changed_cleanup_handler ),
341 NULL,
342 NULL,
343 NULL,
344 G_TYPE_NONE,
346 G_TYPE_POINTER );
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).
354 * Signal args:
355 * - the GdkEvent
357 * Handler prototype:
358 * void handler( FMATreeView *tview,
359 * GdkEvent *event,
360 * void *user_data );
362 st_signals[ OPEN_POPUP ] = g_signal_new(
363 TREE_SIGNAL_CONTEXT_MENU,
364 FMA_TYPE_TREE_VIEW,
365 G_SIGNAL_RUN_LAST,
367 NULL,
368 NULL,
369 NULL,
370 G_TYPE_NONE,
372 G_TYPE_POINTER );
375 static void
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 );
383 static void
384 instance_init( GTypeInstance *instance, gpointer klass )
386 static const gchar *thisfn = "fma_tree_view_instance_init";
387 FMATreeView *self;
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;
401 static void
402 instance_dispose( GObject *object )
404 static const gchar *thisfn = "fma_tree_view_instance_dispose";
405 FMATreeView *self;
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 );
427 static void
428 instance_finalize( GObject *instance )
430 static const gchar *thisfn = "fma_tree_view_instance_finalize";
431 FMATreeView *self;
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 );
448 * fma_tree_view_new:
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.
454 FMATreeView *
455 fma_tree_view_new( FMAMainWindow *main_window )
457 FMATreeView *view;
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 );
467 return( view );
470 static void
471 initialize_gtk( FMATreeView *view )
473 static const gchar *thisfn = "fma_tree_view_initialize_gtk";
474 FMATreeViewPrivate *priv;
475 GtkWidget *scrolled, *tview;
476 FMATreeModel *model;
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(
504 "icon",
505 gtk_cell_renderer_pixbuf_new(),
506 "pixbuf", TREE_COLUMN_ICON,
507 NULL );
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(
512 "label",
513 renderer,
514 "text", TREE_COLUMN_LABEL,
515 NULL );
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 );
527 /* misc properties
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
540 void
541 fma_tree_view_set_mnemonic( FMATreeView *view, GtkContainer *parent, const gchar *widget_name )
543 FMATreeViewPrivate *priv;
544 GtkWidget *label;
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
566 void
567 fma_tree_view_set_edition_mode( FMATreeView *view, guint mode )
569 FMATreeViewPrivate *priv;
570 GtkTreeViewColumn *column;
571 GList *renderers;
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 ){
581 priv->mode = mode;
583 switch( priv->mode ){
585 /* main window */
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 );
596 break;
598 /* export assistant */
599 case TREE_MODE_SELECTION:
600 break;
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 );
609 static gboolean
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 );
617 stop = TRUE;
620 return( stop );
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!
629 static gboolean
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 );
636 return( stop );
639 static gboolean
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 );
646 return( stop );
649 static gboolean
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 );
656 stop = TRUE;
658 if( event->keyval == FMA_KEY_Right ){
659 navigate_to_child( view );
660 stop = TRUE;
662 if( event->keyval == FMA_KEY_Left ){
663 navigate_to_parent( view );
664 stop = TRUE;
667 return( stop );
671 * triggered by the "popup-menu" signal, itself triggered by the keybindings
673 static gboolean
674 on_popup_menu( GtkWidget *widget, FMATreeView *view )
676 do_open_popup( view, NULL );
677 return( TRUE );
680 * handles the "changed" signal emitted on the GtkTreeSelection
682 static void
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
698 static void
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 */
708 g_signal_connect(
709 priv->tree_view, "key-press-event", G_CALLBACK( on_key_pressed_event ), treeview );
711 /* monitors whether the focus is on the view */
712 g_signal_connect(
713 priv->tree_view, "focus-in-event", G_CALLBACK( on_focus_in ), treeview );
715 g_signal_connect(
716 priv->tree_view, "focus-out-event", G_CALLBACK( on_focus_out ), treeview );
718 /* monitors mouse events */
719 g_signal_connect(
720 priv->tree_view, "button-press-event", G_CALLBACK( on_button_press_event ), treeview );
722 g_signal_connect(
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.
741 void
742 fma_tree_view_fill( FMATreeView *view, GList *items )
744 static const gchar *thisfn = "fma_tree_view_fill";
745 FMATreeModel *model;
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.
775 gboolean
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 );
782 are_allowed = 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.
797 void
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.
814 void
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.
831 void
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.
852 FMAObjectItem *
853 fma_tree_view_get_item_by_id( const FMATreeView *view, const gchar *id )
855 FMAObjectItem *item;
856 FMATreeModel *model;
858 g_return_val_if_fail( FMA_IS_TREE_VIEW( view ), NULL );
860 item = 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 );
868 return( item );
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.
878 GList *
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.
892 GList *
893 fma_tree_view_get_items_ex( const FMATreeView *view, guint mode )
895 GList *items;
896 FMATreeModel *model;
897 GList *deleted;
899 g_return_val_if_fail( FMA_IS_TREE_VIEW( view ), NULL );
901 items = NULL;
903 if( !view->private->dispose_has_run ){
905 deleted = NULL;
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 );
919 return( items );
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.
933 void
934 fma_tree_view_select_row_at_path( FMATreeView *view, GtkTreePath *path )
936 static const gchar *thisfn = "fma_tree_view_select_row_at_path";
937 gchar *path_str;
938 GtkTreeIter iter;
939 GtkTreeModel *model;
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 );
948 g_free( path_str );
950 if( path ){
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 );
956 something = TRUE;
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 );
960 something = TRUE;
962 } else {
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 );
966 something = TRUE;
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 );
973 something = TRUE;
978 if( !something ){
979 if( view->private->notify_allowed ){
980 g_signal_emit_by_name( view, TREE_SIGNAL_SELECTION_CHANGED, NULL );
986 static void
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
998 static void
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
1014 static void
1015 display_label( GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, FMATreeView *view )
1017 FMAObject *object;
1018 gchar *label;
1020 g_return_if_fail( view->private->mode == TREE_MODE_EDITION );
1022 gtk_tree_model_get( model, iter, TREE_COLUMN_NAOBJECT, &object, -1 );
1024 if( object ){
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 );
1041 g_free( label );
1046 * when expanding a selected row which has children
1048 static void
1049 extend_selection_to_children( FMATreeView *view, GtkTreeModel *model, GtkTreeIter *parent )
1051 GtkTreeSelection *selection;
1052 GtkTreeIter iter;
1053 gboolean ok;
1055 selection = gtk_tree_view_get_selection( view->private->tree_view );
1056 ok = gtk_tree_model_iter_children( model, &iter, parent );
1058 while( ok ){
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.
1076 static GList *
1077 get_selected_items( FMATreeView *view )
1079 static const gchar *thisfn = "fma_tree_view_get_selected_items";
1080 GList *items;
1081 GtkTreeSelection *selection;
1082 GtkTreeModel *model;
1083 GtkTreePath *path;
1084 GtkTreeIter iter;
1085 GList *it, *listrows;
1086 FMAObjectId *object;
1088 items = NULL;
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",
1099 thisfn,
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 ));
1109 static void
1110 iter_on_selection( FMATreeView *view, FnIterOnSelection fn_iter, gpointer user_data )
1112 GtkTreeSelection *selection;
1113 GtkTreeModel *model;
1114 GList *listrows, *ipath;
1115 GtkTreePath *path;
1116 GtkTreeIter iter;
1117 FMAObject *object;
1118 gboolean stop;
1120 stop = FALSE;
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
1146 * Note:
1147 * a row which does not have any child is always considered as collapsed;
1148 * trying to expand it has no visible effect.
1150 static void
1151 navigate_to_child( FMATreeView *view )
1153 GtkTreeSelection *selection;
1154 GList *listrows;
1155 GtkTreeModel *model;
1156 GtkTreePath *path;
1157 GtkTreeIter iter;
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 );
1169 } else {
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.
1196 static void
1197 navigate_to_parent( FMATreeView *view )
1199 GtkTreeSelection *selection;
1200 GtkTreeModel *model;
1201 GList *listrows;
1202 GtkTreePath *path;
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 );
1226 static void
1227 do_open_popup( FMATreeView *view, GdkEventButton *event )
1229 FMATreeViewPrivate *priv;
1230 GtkTreePath *path;
1232 priv = view->private;
1234 if( event ){
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().
1251 static void
1252 select_row_at_path_by_string( FMATreeView *view, const gchar *path_str )
1254 GtkTreePath *path;
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.
1264 static void
1265 toggle_collapse( FMATreeView *view )
1267 guint toggle = TOGGLE_UNDEFINED;
1269 iter_on_selection( view, ( FnIterOnSelection ) toggle_collapse_iter, &toggle );
1272 static gboolean
1273 toggle_collapse_iter( FMATreeView *view, GtkTreeModel *model,
1274 GtkTreeIter *iter, FMAObject *object, gpointer user_data )
1276 guint count;
1277 guint *toggle;
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 */
1294 if( 0 ){
1295 if( *toggle == TOGGLE_EXPAND ){
1296 extend_selection_to_children( view, model, iter );
1301 /* do not stop iteration */
1302 return( FALSE );
1306 * toggle mode can be undefined, collapse or expand
1307 * it is set on the first row
1309 static void
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 );
1320 } else {
1321 if( !gtk_tree_view_row_expanded( treeview, path )){
1322 gtk_tree_view_expand_row( treeview, path, TRUE );