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 <glib/gi18n.h>
37 #include "api/fma-object-api.h"
38 #include "api/fma-core-utils.h"
40 #include "base-keysyms.h"
41 #include "base-gtk-utils.h"
42 #include "fma-main-tab.h"
43 #include "fma-main-window.h"
44 #include "fma-match-list.h"
51 MUST_NOT_MATCH_COLUMN
,
61 /* internal data set against the instance,
62 * addressed with the tab name
65 FMAMainWindow
*window
;
68 GtkTreeView
*listview
;
70 GtkWidget
*removebutton
;
74 pon_remove_cb pon_remove
;
77 gboolean editable_filter
;
79 gboolean on_selection_change
;
80 gboolean editable_item
;
86 /* i18n: label of the header of the column which let the user select a positive filter
88 static ColumnHeaderStruct st_match_headers
[] = {
89 { MATCH_LIST_MUST_MATCH_ONE_OF
, N_( "Must match one of" ) },
90 { MATCH_LIST_MUST_MATCH_ALL_OF
, N_( "Must match all of" ) },
94 static void create_tree_model( MatchListData
*data
);
95 static void initialize_window( MatchListData
*data
);
96 static void on_tree_selection_changed( FMATreeView
*treeview
, GList
*selected_items
, MatchListData
*data
);
98 static void on_add_filter_clicked( GtkButton
*button
, MatchListData
*data
);
99 static void on_filter_clicked( GtkTreeViewColumn
*treeviewcolumn
, MatchListData
*data
);
100 static void on_filter_edited( GtkCellRendererText
*renderer
, const gchar
*path
, const gchar
*text
, MatchListData
*data
);
101 static gboolean
on_key_pressed_event( GtkWidget
*widget
, GdkEventKey
*event
, MatchListData
*data
);
102 static void on_must_match_clicked( GtkTreeViewColumn
*treeviewcolumn
, MatchListData
*data
);
103 static void on_must_match_toggled( GtkCellRendererToggle
*cell_renderer
, gchar
*path
, MatchListData
*data
);
104 static void on_must_not_match_clicked( GtkTreeViewColumn
*treeviewcolumn
, MatchListData
*data
);
105 static void on_must_not_match_toggled( GtkCellRendererToggle
*cell_renderer
, gchar
*path
, MatchListData
*data
);
106 static void on_remove_filter_clicked( GtkButton
*button
, MatchListData
*data
);
107 static void on_selection_changed( GtkTreeSelection
*selection
, MatchListData
*data
);
109 static void add_filter( MatchListData
*data
, const gchar
*filter
, const gchar
*prefix
);
110 static guint
count_filters( const gchar
*filter
, MatchListData
*data
);
111 static void delete_current_row( MatchListData
*data
);
112 static void delete_row_at_path( GtkTreeView
*treeview
, GtkTreeModel
*model
, GtkTreePath
*path
);
113 static void dump_current_rows( MatchListData
*data
);
114 static void edit_inline( MatchListData
*data
);
115 static gchar
*get_filter_from_path( const gchar
*path_str
, MatchListData
*data
);
116 static const gchar
*get_must_match_header( guint id
);
117 static gboolean
get_rows_iter( GtkTreeModel
*model
, GtkTreePath
*path
, GtkTreeIter
* iter
, GSList
**filters
);
118 static void insert_new_row( MatchListData
*data
);
119 static void insert_new_row_data( MatchListData
*data
, const gchar
*filter
, gboolean match
, gboolean no_match
);
120 static void iter_for_setup( gchar
*filter
, GtkTreeModel
*model
);
121 static gchar
*search_for_unique_label( const gchar
*propal
, MatchListData
*data
);
122 static void set_match_status( const gchar
*path_str
, gboolean must_match
, gboolean must_not_match
, MatchListData
*data
);
123 static void sort_on_column( GtkTreeViewColumn
*treeviewcolumn
, MatchListData
*data
, guint colid
);
125 static void on_instance_finalized( MatchListData
*data
, BaseWindow
*window
);
128 * fma_match_list_init_with_args:
129 * @window: the #FMAMainWindow window which contains the view.
130 * @tab_name: a string constant which identifies this page.
131 * @tab_id: our id for this page.
132 * @listview: the #GtkTreeView widget.
133 * @addbutton: the #GtkButton widget.
134 * @removebutton: the #GtkButton widget.
135 * @pget: a pointer to the function to get the list of filters.
136 * @pset: a pointer to the function to set the list of filters.
137 * @pon_add: an optional pointer to a function which handles the Add button.
138 * @pon_remove: an optional pointer to a function which handles the Remove button.
139 * @item_header: the title of the item header.
141 * Initialize this pseudo-interface, and creates the tree model.
143 * This function can only be called when (and so should be called immediately
144 * after) the Gtk toplevel has been first initialized. This is because we need
145 * here pointers to GtkTreeView and GtkButton widgets.
148 fma_match_list_init_with_args( FMAMainWindow
*window
, const gchar
*tab_name
,
151 GtkWidget
*addbutton
,
152 GtkWidget
*removebutton
,
156 pon_remove_cb pon_remove
,
158 const gchar
*item_header
,
159 gboolean editable_filter
)
161 static const gchar
*thisfn
= "fma_match_list_init_with_args";
164 g_return_if_fail( window
&& FMA_IS_MAIN_WINDOW( window
));
166 g_debug( "%s: window=%p, tab_name=%s", thisfn
, ( void * ) window
, tab_name
);
168 data
= g_new0( MatchListData
, 1 );
172 data
->window
= window
;
173 data
->tab_name
= g_strdup( tab_name
);
174 data
->tab_id
= tab_id
;
175 data
->listview
= GTK_TREE_VIEW( listview
);
176 data
->addbutton
= addbutton
;
177 data
->removebutton
= removebutton
;
180 data
->pon_add
= pon_add
;
181 data
->pon_remove
= pon_remove
;
182 data
->match_header
= match_header
;
183 data
->item_header
= g_strdup( item_header
);
184 data
->editable_filter
= editable_filter
;
188 data
->on_selection_change
= FALSE
;
189 data
->editable_item
= FALSE
;
190 data
->sort_column
= 0;
191 data
->sort_order
= 0;
193 g_object_set_data( G_OBJECT( window
), tab_name
, data
);
194 g_object_weak_ref( G_OBJECT( window
), ( GWeakNotify
) on_instance_finalized
, data
);
196 create_tree_model( data
);
197 initialize_window( data
);
201 create_tree_model( MatchListData
*data
)
204 GtkCellRenderer
*text_cell
, *radio_cell
;
205 GtkTreeViewColumn
*column
;
206 GtkTreeSelection
*selection
;
208 model
= gtk_list_store_new( N_COLUMN
, G_TYPE_STRING
, G_TYPE_BOOLEAN
, G_TYPE_BOOLEAN
);
209 gtk_tree_view_set_model( data
->listview
, GTK_TREE_MODEL( model
));
210 g_object_unref( model
);
212 text_cell
= gtk_cell_renderer_text_new();
213 column
= gtk_tree_view_column_new_with_attributes(
218 gtk_tree_view_append_column( data
->listview
, column
);
220 radio_cell
= gtk_cell_renderer_toggle_new();
221 gtk_cell_renderer_toggle_set_radio( GTK_CELL_RENDERER_TOGGLE( radio_cell
), TRUE
);
222 column
= gtk_tree_view_column_new_with_attributes(
223 get_must_match_header( data
->match_header
),
225 "active", MUST_MATCH_COLUMN
,
227 gtk_tree_view_append_column( data
->listview
, column
);
229 radio_cell
= gtk_cell_renderer_toggle_new();
230 gtk_cell_renderer_toggle_set_radio( GTK_CELL_RENDERER_TOGGLE( radio_cell
), TRUE
);
231 column
= gtk_tree_view_column_new_with_attributes(
232 /* i18n: label of the header of a column which let the user select a negative filter */
233 _( "Must not match any of" ),
235 "active", MUST_NOT_MATCH_COLUMN
,
237 gtk_tree_view_append_column( data
->listview
, column
);
239 /* an empty column to fill out the view
241 column
= gtk_tree_view_column_new();
242 gtk_tree_view_append_column( data
->listview
, column
);
244 gtk_tree_view_set_headers_visible( data
->listview
, TRUE
);
245 gtk_tree_view_set_headers_clickable( data
->listview
, TRUE
);
247 selection
= gtk_tree_view_get_selection( data
->listview
);
248 gtk_tree_selection_set_mode( selection
, GTK_SELECTION_BROWSE
);
252 * Initializes the tab widget at each time the widget will be displayed.
256 initialize_window( MatchListData
*data
)
258 GtkTreeViewColumn
*column
;
261 FMATreeView
*treeview
;
263 g_return_if_fail( data
!= NULL
);
265 column
= gtk_tree_view_get_column( data
->listview
, ITEM_COLUMN
);
266 g_signal_connect( column
, "clicked", G_CALLBACK( on_filter_clicked
), data
);
268 renderers
= gtk_cell_layout_get_cells( GTK_CELL_LAYOUT( column
));
269 g_signal_connect( renderers
->data
, "edited", G_CALLBACK( on_filter_edited
), data
);
271 column
= gtk_tree_view_get_column( data
->listview
, MUST_MATCH_COLUMN
);
272 g_signal_connect( column
, "clicked", G_CALLBACK( on_must_match_clicked
), data
);
274 renderers
= gtk_cell_layout_get_cells( GTK_CELL_LAYOUT( column
));
275 g_signal_connect( renderers
->data
, "toggled", G_CALLBACK( on_must_match_toggled
), data
);
277 column
= gtk_tree_view_get_column( data
->listview
, MUST_NOT_MATCH_COLUMN
);
278 g_signal_connect( column
, "clicked", G_CALLBACK( on_must_not_match_clicked
), data
);
280 renderers
= gtk_cell_layout_get_cells( GTK_CELL_LAYOUT( column
));
281 g_signal_connect( renderers
->data
, "toggled", G_CALLBACK( on_must_not_match_toggled
), data
);
284 g_signal_connect( data
->addbutton
, "clicked", G_CALLBACK( data
->pon_add
), data
->window
);
286 g_signal_connect( data
->addbutton
, "clicked", G_CALLBACK( on_add_filter_clicked
), data
);
289 if( data
->pon_remove
){
290 g_signal_connect( data
->removebutton
, "clicked", G_CALLBACK( data
->pon_remove
), data
->window
);
292 g_signal_connect( data
->removebutton
, "clicked", G_CALLBACK( on_remove_filter_clicked
), data
);
296 gtk_tree_view_get_selection( data
->listview
),
297 "changed", G_CALLBACK( on_selection_changed
), data
);
301 "key-press-event", G_CALLBACK( on_key_pressed_event
), data
);
303 model
= gtk_tree_view_get_model( data
->listview
);
304 gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( model
), ITEM_COLUMN
, GTK_SORT_ASCENDING
);
305 data
->sort_column
= ITEM_COLUMN
;
306 data
->sort_order
= GTK_SORT_ASCENDING
;
308 column
= gtk_tree_view_get_column( data
->listview
, ITEM_COLUMN
);
309 sort_on_column( column
, data
, ITEM_COLUMN
);
311 treeview
= fma_main_window_get_items_view( data
->window
);
312 g_signal_connect( treeview
, TREE_SIGNAL_SELECTION_CHANGED
, G_CALLBACK( on_tree_selection_changed
), data
);
316 * Called each time the selection changes in the Actions tree view.
318 * Basically we are using here a rather common scheme:
319 * - object has a GSList of strings, each of one being a filter description,
320 * which may be negated
321 * - the list is displayed in a listview with radio toggle buttons
322 * so that a user cannot have both positive and negative assertions
323 * for the same basename filter
324 * - update the object with a summary of the listbox contents
327 on_tree_selection_changed( FMATreeView
*treeview
, GList
*selected_items
, MatchListData
*data
)
329 static const gchar
*thisfn
= "fma_match_list_on_tree_selection_changed";
330 FMAIContext
*context
;
334 GtkTreeSelection
*selection
;
335 GtkTreeViewColumn
*column
;
338 g_return_if_fail( treeview
&& FMA_IS_TREE_VIEW( treeview
));
339 g_return_if_fail( data
!= NULL
);
341 g_object_get( G_OBJECT( data
->window
),
342 MAIN_PROP_CONTEXT
, &context
, MAIN_PROP_EDITABLE
, &data
->editable_item
,
345 enable_tab
= ( context
!= NULL
);
346 fma_main_tab_enable_page( data
->window
, data
->tab_id
, enable_tab
);
348 data
->on_selection_change
= TRUE
;
350 filters
= context
? ( *data
->pget
)( context
) : NULL
;
351 g_debug( "%s: filters=%p (count=%d)", thisfn
, ( void * ) filters
, filters
? g_slist_length( filters
) : -1 );
353 model
= gtk_tree_view_get_model( data
->listview
);
354 selection
= gtk_tree_view_get_selection( data
->listview
);
355 gtk_tree_selection_unselect_all( selection
);
356 gtk_list_store_clear( GTK_LIST_STORE( model
));
359 fma_core_utils_slist_dump( thisfn
, filters
);
360 g_slist_foreach( filters
, ( GFunc
) iter_for_setup
, model
);
363 column
= gtk_tree_view_get_column( data
->listview
, ITEM_COLUMN
);
364 base_gtk_utils_set_editable( G_OBJECT( column
), data
->editable_item
&& data
->editable_filter
);
366 base_gtk_utils_set_editable( G_OBJECT( data
->addbutton
), data
->editable_item
);
367 base_gtk_utils_set_editable( G_OBJECT( data
->removebutton
), data
->editable_item
);
368 gtk_widget_set_sensitive( data
->removebutton
, FALSE
);
370 data
->on_selection_change
= FALSE
;
372 path
= gtk_tree_path_new_first();
374 selection
= gtk_tree_view_get_selection( data
->listview
);
375 gtk_tree_selection_select_path( selection
, path
);
376 gtk_tree_path_free( path
);
381 * fma_match_list_insert_row:
382 * @window: the #FMAMainWindow window which contains the view.
383 * @tab_name: a string constant which identifies this page.
384 * @filter: the item to add.
385 * @match: whether the 'must match' column is checked.
386 * @not_match: whether the 'must not match' column is checked.
388 * Add a new row to the list view.
391 fma_match_list_insert_row( FMAMainWindow
*window
, const gchar
*tab_name
, const gchar
*filter
, gboolean match
, gboolean not_match
)
395 data
= ( MatchListData
* ) g_object_get_data( G_OBJECT( window
), tab_name
);
396 g_return_if_fail( data
!= NULL
);
398 insert_new_row_data( data
, filter
, match
, not_match
);
402 * fma_match_list_get_rows:
403 * @window: the #FMAMainWindow window which contains the view.
404 * @tab_name: a string constant which identifies this page.
406 * Returns the list of rows as a newly allocated string list which should
407 * be fma_core_utils_slist_free() by the caller.
410 fma_match_list_get_rows( FMAMainWindow
*window
, const gchar
*tab_name
)
416 data
= ( MatchListData
* ) g_object_get_data( G_OBJECT( window
), tab_name
);
417 g_return_val_if_fail( data
!= NULL
, NULL
);
419 model
= gtk_tree_view_get_model( data
->listview
);
421 gtk_tree_model_foreach( model
, ( GtkTreeModelForeachFunc
) get_rows_iter
, &filters
);
426 /* callback function called when the user clicks on "Add" button
427 * and no application callback has been provided by the caller
428 * at initialization time
431 on_add_filter_clicked( GtkButton
*button
, MatchListData
*data
)
433 insert_new_row( data
);
437 on_filter_clicked( GtkTreeViewColumn
*treeviewcolumn
, MatchListData
*data
)
439 sort_on_column( treeviewcolumn
, data
, ITEM_COLUMN
);
443 on_filter_edited( GtkCellRendererText
*renderer
, const gchar
*path_str
, const gchar
*text
, MatchListData
*data
)
445 static const gchar
*thisfn
= "fma_match_list_on_filter_edited";
450 FMAIContext
*context
;
451 gboolean must_match
, must_not_match
;
452 gchar
*to_add
, *to_remove
;
456 g_return_if_fail( data
->editable_filter
);
458 g_object_get( G_OBJECT( data
->window
), MAIN_PROP_CONTEXT
, &context
, NULL
);
459 g_return_if_fail( FMA_IS_ICONTEXT( context
));
461 model
= gtk_tree_view_get_model( data
->listview
);
462 path
= gtk_tree_path_new_from_string( path_str
);
463 gtk_tree_model_get_iter( model
, &iter
, path
);
464 gtk_tree_path_free( path
);
466 gtk_tree_model_get( model
, &iter
, ITEM_COLUMN
, &old_text
, -1 );
468 if( strcmp( text
, old_text
) == 0 ){
472 dump_current_rows( data
);
473 g_debug( "%s: new filter=%s, count=%d", thisfn
, text
, count_filters( text
, data
));
475 if( count_filters( text
, data
) >= 1 ){
476 dialog
= gtk_message_dialog_new(
477 GTK_WINDOW( data
->window
),
478 GTK_DIALOG_MODAL
, GTK_MESSAGE_WARNING
, GTK_BUTTONS_OK
,
479 _( "'%s' filter already exists in the list.\nPlease provide another one." ), text
);
480 gtk_dialog_run( GTK_DIALOG( dialog
));
481 gtk_widget_destroy( dialog
);
486 gtk_tree_model_get( model
, &iter
,
487 MUST_MATCH_COLUMN
, &must_match
,
488 MUST_NOT_MATCH_COLUMN
, &must_not_match
,
491 gtk_list_store_set( GTK_LIST_STORE( model
), &iter
, ITEM_COLUMN
, text
, -1 );
493 filters
= ( *data
->pget
)( context
);
496 to_remove
= g_strdup( old_text
);
497 filters
= fma_core_utils_slist_remove_ascii( filters
, to_remove
);
499 to_remove
= g_strdup_printf( "!%s", old_text
);
500 filters
= fma_core_utils_slist_remove_ascii( filters
, to_remove
);
505 filters
= g_slist_prepend( filters
, g_strdup( text
));
507 } else if( must_not_match
){
508 to_add
= g_strdup_printf( "!%s", text
);
509 filters
= g_slist_prepend( filters
, to_add
);
512 ( *data
->pset
)( context
, filters
);
513 fma_core_utils_slist_free( filters
);
516 g_signal_emit_by_name( G_OBJECT( data
->window
), MAIN_SIGNAL_ITEM_UPDATED
, context
, 0 );
520 on_key_pressed_event( GtkWidget
*widget
, GdkEventKey
*event
, MatchListData
*data
)
526 if( event
->keyval
== FMA_KEY_F2
){
527 if( data
->editable_filter
){
533 if( event
->keyval
== FMA_KEY_Insert
|| event
->keyval
== FMA_KEY_KP_Insert
){
534 if( data
->editable_item
){
535 insert_new_row( data
);
540 if( event
->keyval
== FMA_KEY_Delete
|| event
->keyval
== FMA_KEY_KP_Delete
){
541 if( data
->editable_item
){
542 delete_current_row( data
);
551 on_must_match_clicked( GtkTreeViewColumn
*treeviewcolumn
, MatchListData
*data
)
553 sort_on_column( treeviewcolumn
, data
, MUST_MATCH_COLUMN
);
557 * clicking on an already active toggle button has no effect
558 * clicking on an inactive toggle button has a double effect:
559 * - the other toggle button becomes inactive
560 * - this toggle button becomes active
561 * the corresponding strings must be respectively removed/added to the
565 on_must_match_toggled( GtkCellRendererToggle
*cell_renderer
, gchar
*path_str
, MatchListData
*data
)
567 /*static const gchar *thisfn = "fma_match_list_on_must_match_toggled";*/
569 FMAIContext
*context
;
574 /*gboolean is_active = gtk_cell_renderer_toggle_get_active( cell_renderer );
575 g_debug( "%s: is_active=%s", thisfn, is_active ? "True":"False" );*/
577 active
= gtk_cell_renderer_toggle_get_active( cell_renderer
);
579 if( data
->editable_item
){
581 g_object_get( G_OBJECT( data
->window
), MAIN_PROP_CONTEXT
, &context
, NULL
);
582 g_return_if_fail( FMA_IS_ICONTEXT( context
));
584 set_match_status( path_str
, TRUE
, FALSE
, data
);
586 filter
= get_filter_from_path( path_str
, data
);
587 filters
= ( *data
->pget
)( context
);
590 to_remove
= g_strdup_printf( "!%s", filter
);
591 filters
= fma_core_utils_slist_remove_ascii( filters
, to_remove
);
595 filters
= g_slist_prepend( filters
, g_strdup( filter
));
596 ( *data
->pset
)( context
, filters
);
598 fma_core_utils_slist_free( filters
);
601 g_signal_emit_by_name( G_OBJECT( data
->window
), MAIN_SIGNAL_ITEM_UPDATED
, context
, 0 );
604 g_signal_handlers_block_by_func(( gpointer
) cell_renderer
, on_must_match_toggled
, data
);
605 gtk_cell_renderer_toggle_set_active( cell_renderer
, !active
);
606 g_signal_handlers_unblock_by_func(( gpointer
) cell_renderer
, on_must_match_toggled
, data
);
611 on_must_not_match_clicked( GtkTreeViewColumn
*treeviewcolumn
, MatchListData
*data
)
613 sort_on_column( treeviewcolumn
, data
, MUST_NOT_MATCH_COLUMN
);
617 on_must_not_match_toggled( GtkCellRendererToggle
*cell_renderer
, gchar
*path_str
, MatchListData
*data
)
619 /*static const gchar *thisfn = "fma_match_list_on_must_not_match_toggled";*/
621 FMAIContext
*context
;
626 /*gboolean is_active = gtk_cell_renderer_toggle_get_active( cell_renderer );
627 g_debug( "%s: is_active=%s", thisfn, is_active ? "True":"False" );*/
629 active
= gtk_cell_renderer_toggle_get_active( cell_renderer
);
631 if( data
->editable_item
){
633 g_object_get( G_OBJECT( data
->window
), MAIN_PROP_CONTEXT
, &context
, NULL
);
634 g_return_if_fail( FMA_IS_ICONTEXT( context
));
636 set_match_status( path_str
, FALSE
, TRUE
, data
);
638 filter
= get_filter_from_path( path_str
, data
);
639 filters
= ( *data
->pget
)( context
);
642 filters
= fma_core_utils_slist_remove_ascii( filters
, filter
);
645 to_add
= g_strdup_printf( "!%s", filter
);
646 filters
= g_slist_prepend( filters
, to_add
);
647 ( *data
->pset
)( context
, filters
);
649 fma_core_utils_slist_free( filters
);
652 g_signal_emit_by_name( G_OBJECT( data
->window
), MAIN_SIGNAL_ITEM_UPDATED
, context
, 0 );
655 g_signal_handlers_block_by_func(( gpointer
) cell_renderer
, on_must_not_match_toggled
, data
);
656 gtk_cell_renderer_toggle_set_active( cell_renderer
, !active
);
657 g_signal_handlers_unblock_by_func(( gpointer
) cell_renderer
, on_must_not_match_toggled
, data
);
662 on_remove_filter_clicked( GtkButton
*button
, MatchListData
*data
)
664 delete_current_row( data
);
668 on_selection_changed( GtkTreeSelection
*selection
, MatchListData
*data
)
670 gtk_widget_set_sensitive( data
->removebutton
,
671 data
->editable_item
&& gtk_tree_selection_count_selected_rows( selection
) > 0 );
675 add_filter( MatchListData
*data
, const gchar
*filter
, const gchar
*prefix
)
677 FMAIContext
*context
;
681 g_object_get( G_OBJECT( data
->window
), MAIN_PROP_CONTEXT
, &context
, NULL
);
684 filters
= ( *data
->pget
)( context
);
685 to_add
= g_strdup_printf( "%s%s", prefix
, filter
);
686 filters
= g_slist_prepend( filters
, to_add
);
687 ( *data
->pset
)( context
, filters
);
688 fma_core_utils_slist_free( filters
);
690 g_signal_emit_by_name( G_OBJECT( data
->window
), MAIN_SIGNAL_ITEM_UPDATED
, context
, 0 );
695 count_filters( const gchar
*filter
, MatchListData
*data
)
701 model
= gtk_tree_view_get_model( data
->listview
);
703 gtk_tree_model_foreach( model
, ( GtkTreeModelForeachFunc
) get_rows_iter
, &filters
);
704 count
= fma_core_utils_slist_count( filters
, filter
);
705 fma_core_utils_slist_free( filters
);
711 delete_current_row( MatchListData
*data
)
713 GtkTreeSelection
*selection
;
719 FMAIContext
*context
;
723 selection
= gtk_tree_view_get_selection( data
->listview
);
724 model
= gtk_tree_view_get_model( data
->listview
);
725 rows
= gtk_tree_selection_get_selected_rows( selection
, NULL
);
727 if( g_list_length( rows
) == 1 ){
728 path
= ( GtkTreePath
* ) rows
->data
;
729 gtk_tree_model_get_iter( model
, &iter
, path
);
730 gtk_tree_model_get( model
, &iter
, ITEM_COLUMN
, &filter
, -1 );
732 delete_row_at_path( data
->listview
, model
, path
);
734 g_object_get( G_OBJECT( data
->window
), MAIN_PROP_CONTEXT
, &context
, NULL
);
737 filters
= ( *data
->pget
)( context
);
740 to_remove
= g_strdup_printf( "!%s", filter
);
741 filters
= fma_core_utils_slist_remove_ascii( filters
, to_remove
);
743 filters
= fma_core_utils_slist_remove_ascii( filters
, filter
);
744 ( *data
->pset
)( context
, filters
);
745 fma_core_utils_slist_free( filters
);
747 g_signal_emit_by_name( G_OBJECT( data
->window
), MAIN_SIGNAL_ITEM_UPDATED
, context
, 0 );
754 g_list_foreach( rows
, ( GFunc
) gtk_tree_path_free
, NULL
);
759 delete_row_at_path( GtkTreeView
*treeview
, GtkTreeModel
*model
, GtkTreePath
*path
)
763 if( gtk_tree_model_get_iter( model
, &iter
, path
)){
764 gtk_list_store_remove( GTK_LIST_STORE( model
), &iter
);
766 if( gtk_tree_model_get_iter( model
, &iter
, path
) || gtk_tree_path_prev( path
)){
767 gtk_tree_view_set_cursor( treeview
, path
, NULL
, FALSE
);
773 dump_current_rows( MatchListData
*data
)
775 #ifdef FMA_MAINTAINER_MODE
779 model
= gtk_tree_view_get_model( data
->listview
);
781 gtk_tree_model_foreach( model
, ( GtkTreeModelForeachFunc
) get_rows_iter
, &filters
);
782 fma_core_utils_slist_dump( "fma_match_list_dump_current_rows", filters
);
783 fma_core_utils_slist_free( filters
);
788 edit_inline( MatchListData
*data
)
790 GtkTreeSelection
*selection
;
793 GtkTreeViewColumn
*column
;
795 selection
= gtk_tree_view_get_selection( data
->listview
);
796 rows
= gtk_tree_selection_get_selected_rows( selection
, NULL
);
798 if( g_list_length( rows
) == 1 ){
799 gtk_tree_view_get_cursor( data
->listview
, &path
, &column
);
800 gtk_tree_view_set_cursor( data
->listview
, path
, column
, TRUE
);
801 gtk_tree_path_free( path
);
804 g_list_foreach( rows
, ( GFunc
) gtk_tree_path_free
, NULL
);
809 get_filter_from_path( const gchar
*path_str
, MatchListData
*data
)
818 model
= gtk_tree_view_get_model( data
->listview
);
819 path
= gtk_tree_path_new_from_string( path_str
);
820 gtk_tree_model_get_iter( model
, &iter
, path
);
821 gtk_tree_path_free( path
);
823 gtk_tree_model_get( model
, &iter
, ITEM_COLUMN
, &filter
, -1 );
829 get_must_match_header( guint id
)
833 for( i
= 0 ; st_match_headers
[i
].header_id
; ++i
){
834 if( st_match_headers
[i
].header_id
== id
){
835 return( gettext( st_match_headers
[i
].header_label
));
843 get_rows_iter( GtkTreeModel
*model
, GtkTreePath
*path
, GtkTreeIter
* iter
, GSList
**filters
)
847 gtk_tree_model_get( model
, iter
, ITEM_COLUMN
, &keyword
, -1 );
848 *filters
= g_slist_prepend( *filters
, keyword
);
850 return( FALSE
); /* don't stop looping */
854 insert_new_row( MatchListData
*data
)
856 /* i18n notes : new filter for a new row in a match/no match list */
857 static const gchar
*filter_label
= N_( "new-filter" );
860 label
= search_for_unique_label( gettext( filter_label
), data
);
861 insert_new_row_data( data
, label
, TRUE
, FALSE
);
866 insert_new_row_data( MatchListData
*data
, const gchar
*filter
, gboolean match
, gboolean not_match
)
871 GtkTreeViewColumn
*column
;
873 g_return_if_fail( !( match
&& not_match
));
875 model
= gtk_tree_view_get_model( data
->listview
);
877 gtk_list_store_insert_with_values( GTK_LIST_STORE( model
), &iter
, 0,
879 MUST_MATCH_COLUMN
, match
,
880 MUST_NOT_MATCH_COLUMN
, not_match
,
883 path
= gtk_tree_model_get_path( model
, &iter
);
884 column
= gtk_tree_view_get_column( data
->listview
, ITEM_COLUMN
);
885 gtk_tree_view_set_cursor( data
->listview
, path
, column
, TRUE
);
886 gtk_tree_path_free( path
);
889 add_filter( data
, filter
, "" );
893 add_filter( data
, filter
, "!" );
898 iter_for_setup( gchar
*filter_orig
, GtkTreeModel
*model
)
905 filter
= g_strstrip( g_strdup( filter_orig
));
909 if( filter
[0] == '!' ){
910 tmp
= g_strstrip( g_strdup( filter
+1 ));
919 gtk_list_store_append( GTK_LIST_STORE( model
), &iter
);
921 GTK_LIST_STORE( model
),
924 MUST_MATCH_COLUMN
, positive
,
925 MUST_NOT_MATCH_COLUMN
, negative
,
932 search_for_unique_label( const gchar
*propal
, MatchListData
*data
)
937 label
= g_strdup( propal
);
940 while( count_filters( label
, data
) >= 1 ){
942 label
= g_strdup_printf( "%s-%d", propal
, ++count
);
949 set_match_status( const gchar
*path_str
, gboolean must_match
, gboolean must_not_match
, MatchListData
*data
)
955 model
= gtk_tree_view_get_model( data
->listview
);
956 path
= gtk_tree_path_new_from_string( path_str
);
957 gtk_tree_model_get_iter( model
, &iter
, path
);
958 gtk_tree_path_free( path
);
960 gtk_list_store_set( GTK_LIST_STORE( model
), &iter
,
961 MUST_MATCH_COLUMN
, must_match
,
962 MUST_NOT_MATCH_COLUMN
, must_not_match
,
967 sort_on_column( GtkTreeViewColumn
*treeviewcolumn
, MatchListData
*data
, guint new_col_id
)
970 guint prev_order
, new_order
;
972 GtkTreeViewColumn
*column
;
974 prev_col_id
= data
->sort_column
;
975 prev_order
= data
->sort_order
;
977 column
= gtk_tree_view_get_column( data
->listview
, prev_col_id
);
978 gtk_tree_view_column_set_sort_indicator( column
, FALSE
);
980 if( new_col_id
== prev_col_id
){
981 new_order
= ( prev_order
== GTK_SORT_ASCENDING
? GTK_SORT_DESCENDING
: GTK_SORT_ASCENDING
);
983 new_order
= GTK_SORT_ASCENDING
;
986 data
->sort_column
= new_col_id
;
987 data
->sort_order
= new_order
;
989 gtk_tree_view_column_set_sort_indicator( treeviewcolumn
, TRUE
);
990 gtk_tree_view_column_set_sort_order( treeviewcolumn
, new_order
);
992 model
= gtk_tree_view_get_model( data
->listview
);
993 gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( model
), new_col_id
, new_order
);
997 on_instance_finalized( MatchListData
*data
, BaseWindow
*window
)
999 static const gchar
*thisfn
= "fma_match_list_on_instance_finalized";
1001 g_return_if_fail( data
!= NULL
);
1003 g_debug( "%s: window=%p, user_data=%p, tab_name=%s",
1006 ( void * ) data
, data
->tab_name
);
1008 g_object_set_data( G_OBJECT( window
), data
->tab_name
, NULL
);
1010 /* This function is called when the FMAMainWindow is about to be finalized.
1011 * At this time, the FMATreeModel has already been finalized.
1012 * It is so too late to try to clear it...
1015 GtkTreeModel
*model
= gtk_tree_view_get_model( data
->listview
);
1016 GtkTreeSelection
*selection
= gtk_tree_view_get_selection( data
->listview
);
1017 gtk_tree_selection_unselect_all( selection
);
1018 gtk_list_store_clear( GTK_LIST_STORE( model
));
1021 g_free( data
->tab_name
);
1022 g_free( data
->item_header
);