README: add deprecation notice
[nautilus-actions.git] / src / ui / fma-match-list.c
blobf8de0b8a45afced23c7b5bce3e66866c31eab691
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 <glib/gi18n.h>
35 #include <libintl.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"
46 /* column ordering
48 enum {
49 ITEM_COLUMN = 0,
50 MUST_MATCH_COLUMN,
51 MUST_NOT_MATCH_COLUMN,
52 N_COLUMN
55 typedef struct {
56 guint header_id;
57 gchar *header_label;
59 ColumnHeaderStruct;
61 /* internal data set against the instance,
62 * addressed with the tab name
64 typedef struct {
65 FMAMainWindow *window;
66 gchar *tab_name;
67 guint tab_id;
68 GtkTreeView *listview;
69 GtkWidget *addbutton;
70 GtkWidget *removebutton;
71 pget_filters pget;
72 pset_filters pset;
73 pon_add_cb pon_add;
74 pon_remove_cb pon_remove;
75 guint match_header;
76 gchar *item_header;
77 gboolean editable_filter;
78 /* dynamic data */
79 gboolean on_selection_change;
80 gboolean editable_item;
81 guint sort_column;
82 guint sort_order;
84 MatchListData;
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" ) },
91 { 0 }
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.
147 void
148 fma_match_list_init_with_args( FMAMainWindow *window, const gchar *tab_name,
149 guint tab_id,
150 GtkWidget *listview,
151 GtkWidget *addbutton,
152 GtkWidget *removebutton,
153 pget_filters pget,
154 pset_filters pset,
155 pon_add_cb pon_add,
156 pon_remove_cb pon_remove,
157 guint match_header,
158 const gchar *item_header,
159 gboolean editable_filter )
161 static const gchar *thisfn = "fma_match_list_init_with_args";
162 MatchListData *data;
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 );
170 /* parameters
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;
178 data->pget = pget;
179 data->pset = pset;
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;
186 /* dynamic data
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 );
200 static void
201 create_tree_model( MatchListData *data )
203 GtkListStore *model;
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(
214 data->item_header,
215 text_cell,
216 "text", ITEM_COLUMN,
217 NULL );
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 ),
224 radio_cell,
225 "active", MUST_MATCH_COLUMN,
226 NULL );
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" ),
234 radio_cell,
235 "active", MUST_NOT_MATCH_COLUMN,
236 NULL );
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.
253 * Connect signals.
255 static void
256 initialize_window( MatchListData *data )
258 GtkTreeViewColumn *column;
259 GList *renderers;
260 GtkTreeModel *model;
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 );
283 if( data->pon_add ){
284 g_signal_connect( data->addbutton, "clicked", G_CALLBACK( data->pon_add ), data->window );
285 } else {
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 );
291 } else {
292 g_signal_connect( data->removebutton, "clicked", G_CALLBACK( on_remove_filter_clicked ), data );
295 g_signal_connect(
296 gtk_tree_view_get_selection( data->listview ),
297 "changed", G_CALLBACK( on_selection_changed ), data );
299 g_signal_connect(
300 data->listview,
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
326 static void
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;
331 gboolean enable_tab;
332 GSList *filters;
333 GtkTreeModel *model;
334 GtkTreeSelection *selection;
335 GtkTreeViewColumn *column;
336 GtkTreePath *path;
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,
343 NULL );
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 ));
358 if( filters ){
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();
373 if( path ){
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.
390 void
391 fma_match_list_insert_row( FMAMainWindow *window, const gchar *tab_name, const gchar *filter, gboolean match, gboolean not_match )
393 MatchListData *data;
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.
409 GSList *
410 fma_match_list_get_rows( FMAMainWindow *window, const gchar *tab_name )
412 GSList *filters;
413 MatchListData *data;
414 GtkTreeModel *model;
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 );
420 filters = NULL;
421 gtk_tree_model_foreach( model, ( GtkTreeModelForeachFunc ) get_rows_iter, &filters );
423 return( 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
430 static void
431 on_add_filter_clicked( GtkButton *button, MatchListData *data )
433 insert_new_row( data );
436 static void
437 on_filter_clicked( GtkTreeViewColumn *treeviewcolumn, MatchListData *data )
439 sort_on_column( treeviewcolumn, data, ITEM_COLUMN );
442 static void
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";
446 GtkTreeModel *model;
447 GtkTreeIter iter;
448 GtkTreePath *path;
449 gchar *old_text;
450 FMAIContext *context;
451 gboolean must_match, must_not_match;
452 gchar *to_add, *to_remove;
453 GSList *filters;
454 GtkWidget *dialog;
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 ){
469 return;
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 );
483 return;
486 gtk_tree_model_get( model, &iter,
487 MUST_MATCH_COLUMN, &must_match,
488 MUST_NOT_MATCH_COLUMN, &must_not_match,
489 -1 );
491 gtk_list_store_set( GTK_LIST_STORE( model ), &iter, ITEM_COLUMN, text, -1 );
493 filters = ( *data->pget )( context );
495 if( filters ){
496 to_remove = g_strdup( old_text );
497 filters = fma_core_utils_slist_remove_ascii( filters, to_remove );
498 g_free( to_remove );
499 to_remove = g_strdup_printf( "!%s", old_text );
500 filters = fma_core_utils_slist_remove_ascii( filters, to_remove );
501 g_free( to_remove );
504 if( must_match ){
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 );
514 g_free( old_text );
516 g_signal_emit_by_name( G_OBJECT( data->window ), MAIN_SIGNAL_ITEM_UPDATED, context, 0 );
519 static gboolean
520 on_key_pressed_event( GtkWidget *widget, GdkEventKey *event, MatchListData *data )
522 gboolean stop;
524 stop = FALSE;
526 if( event->keyval == FMA_KEY_F2 ){
527 if( data->editable_filter ){
528 edit_inline( data );
529 stop = TRUE;
533 if( event->keyval == FMA_KEY_Insert || event->keyval == FMA_KEY_KP_Insert ){
534 if( data->editable_item ){
535 insert_new_row( data );
536 stop = TRUE;
540 if( event->keyval == FMA_KEY_Delete || event->keyval == FMA_KEY_KP_Delete ){
541 if( data->editable_item ){
542 delete_current_row( data );
543 stop = TRUE;
547 return( stop );
550 static void
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
562 * filters list
564 static void
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";*/
568 gchar *filter;
569 FMAIContext *context;
570 GSList *filters;
571 gchar *to_remove;
572 gboolean active;
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 ){
580 if( !active ){
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 );
589 if( filters ){
590 to_remove = g_strdup_printf( "!%s", filter );
591 filters = fma_core_utils_slist_remove_ascii( filters, to_remove );
592 g_free( to_remove );
595 filters = g_slist_prepend( filters, g_strdup( filter ));
596 ( *data->pset )( context, filters );
598 fma_core_utils_slist_free( filters );
599 g_free( filter );
601 g_signal_emit_by_name( G_OBJECT( data->window ), MAIN_SIGNAL_ITEM_UPDATED, context, 0 );
603 } else {
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 );
610 static void
611 on_must_not_match_clicked( GtkTreeViewColumn *treeviewcolumn, MatchListData *data )
613 sort_on_column( treeviewcolumn, data, MUST_NOT_MATCH_COLUMN );
616 static void
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";*/
620 gchar *filter;
621 FMAIContext *context;
622 GSList *filters;
623 gchar *to_add;
624 gboolean active;
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 ){
632 if( !active ){
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 );
641 if( filters ){
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 );
650 g_free( filter );
652 g_signal_emit_by_name( G_OBJECT( data->window ), MAIN_SIGNAL_ITEM_UPDATED, context, 0 );
654 } else {
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 );
661 static void
662 on_remove_filter_clicked( GtkButton *button, MatchListData *data )
664 delete_current_row( data );
667 static void
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 );
674 static void
675 add_filter( MatchListData *data, const gchar *filter, const gchar *prefix )
677 FMAIContext *context;
678 GSList *filters;
679 gchar *to_add;
681 g_object_get( G_OBJECT( data->window ), MAIN_PROP_CONTEXT, &context, NULL );
683 if( context ){
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 );
694 static guint
695 count_filters( const gchar *filter, MatchListData *data )
697 guint count;
698 GtkTreeModel *model;
699 GSList *filters;
701 model = gtk_tree_view_get_model( data->listview );
702 filters = NULL;
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 );
707 return( count );
710 static void
711 delete_current_row( MatchListData *data )
713 GtkTreeSelection *selection;
714 GtkTreeModel *model;
715 GList *rows;
716 GtkTreePath *path;
717 GtkTreeIter iter;
718 gchar *filter;
719 FMAIContext *context;
720 GSList *filters;
721 gchar *to_remove;
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 );
736 if( context ){
737 filters = ( *data->pget )( context );
739 if( filters ){
740 to_remove = g_strdup_printf( "!%s", filter );
741 filters = fma_core_utils_slist_remove_ascii( filters, to_remove );
742 g_free( 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 );
751 g_free( filter );
754 g_list_foreach( rows, ( GFunc ) gtk_tree_path_free, NULL );
755 g_list_free( rows );
758 static void
759 delete_row_at_path( GtkTreeView *treeview, GtkTreeModel *model, GtkTreePath *path )
761 GtkTreeIter iter;
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 );
772 static void
773 dump_current_rows( MatchListData *data )
775 #ifdef FMA_MAINTAINER_MODE
776 GtkTreeModel *model;
777 GSList *filters;
779 model = gtk_tree_view_get_model( data->listview );
780 filters = NULL;
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 );
784 #endif
787 static void
788 edit_inline( MatchListData *data )
790 GtkTreeSelection *selection;
791 GList *rows;
792 GtkTreePath *path;
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 );
805 g_list_free( rows );
808 static gchar *
809 get_filter_from_path( const gchar *path_str, MatchListData *data )
811 gchar *filter;
812 GtkTreeModel *model;
813 GtkTreePath *path;
814 GtkTreeIter iter;
816 filter = NULL;
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 );
825 return( filter );
828 static const gchar *
829 get_must_match_header( guint id )
831 guint i;
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 ));
839 return( "" );
842 static gboolean
843 get_rows_iter( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter* iter, GSList **filters )
845 gchar *keyword;
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 */
853 static void
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" );
858 gchar *label;
860 label = search_for_unique_label( gettext( filter_label ), data );
861 insert_new_row_data( data, label, TRUE, FALSE );
862 g_free( label );
865 static void
866 insert_new_row_data( MatchListData *data, const gchar *filter, gboolean match, gboolean not_match )
868 GtkTreeModel *model;
869 GtkTreeIter iter;
870 GtkTreePath *path;
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,
878 ITEM_COLUMN, filter,
879 MUST_MATCH_COLUMN, match,
880 MUST_NOT_MATCH_COLUMN, not_match,
881 -1 );
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 );
888 if( match ){
889 add_filter( data, filter, "" );
892 if( not_match ){
893 add_filter( data, filter, "!" );
897 static void
898 iter_for_setup( gchar *filter_orig, GtkTreeModel *model )
900 GtkTreeIter iter;
901 gchar *tmp, *filter;
902 gboolean positive;
903 gboolean negative;
905 filter = g_strstrip( g_strdup( filter_orig ));
906 positive = FALSE;
907 negative = FALSE;
909 if( filter[0] == '!' ){
910 tmp = g_strstrip( g_strdup( filter+1 ));
911 g_free( filter );
912 filter = tmp;
913 negative = TRUE;
915 } else {
916 positive = TRUE;
919 gtk_list_store_append( GTK_LIST_STORE( model ), &iter );
920 gtk_list_store_set(
921 GTK_LIST_STORE( model ),
922 &iter,
923 ITEM_COLUMN, filter,
924 MUST_MATCH_COLUMN, positive,
925 MUST_NOT_MATCH_COLUMN, negative,
926 -1 );
928 g_free( filter );
931 static gchar *
932 search_for_unique_label( const gchar *propal, MatchListData *data )
934 gchar *label;
935 guint count;
937 label = g_strdup( propal );
938 count = 1;
940 while( count_filters( label, data ) >= 1 ){
941 g_free( label );
942 label = g_strdup_printf( "%s-%d", propal, ++count );
945 return( label );
948 static void
949 set_match_status( const gchar *path_str, gboolean must_match, gboolean must_not_match, MatchListData *data )
951 GtkTreeModel *model;
952 GtkTreePath *path;
953 GtkTreeIter iter;
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,
963 -1 );
966 static void
967 sort_on_column( GtkTreeViewColumn *treeviewcolumn, MatchListData *data, guint new_col_id )
969 guint prev_col_id;
970 guint prev_order, new_order;
971 GtkTreeModel *model;
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 );
982 } else {
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 );
996 static void
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",
1004 thisfn,
1005 ( void * ) window,
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...
1014 #if 0
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 ));
1019 #endif
1021 g_free( data->tab_name );
1022 g_free( data->item_header );
1024 g_free( data );