Update copyright message
[nautilus-actions.git] / src / nact / nact-match-list.c
blobc9bf1b80eba2339c6bb969c73a2eaa588bccd1d9
1 /*
2 * Nautilus Actions
3 * A Nautilus extension which offers configurable context menu actions.
5 * Copyright (C) 2005 The GNOME Foundation
6 * Copyright (C) 2006, 2007, 2008 Frederic Ruaudel and others (see AUTHORS)
7 * Copyright (C) 2009, 2010, 2011 Pierre Wieser and others (see AUTHORS)
9 * This Program 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 * This Program 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
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public
20 * License along with this Library; see the file COPYING. If not,
21 * write to the Free Software Foundation, Inc., 59 Temple Place,
22 * Suite 330, Boston, MA 02111-1307, USA.
24 * Authors:
25 * Frederic Ruaudel <grumz@grumz.net>
26 * Rodrigo Moya <rodrigo@gnome-db.org>
27 * Pierre Wieser <pwieser@trychlos.org>
28 * ... and many others (see AUTHORS)
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
35 #include <glib/gi18n.h>
37 #include <api/na-object-api.h>
38 #include <api/na-core-utils.h>
40 #include "base-keysyms.h"
41 #include "nact-gtk-utils.h"
42 #include "nact-main-tab.h"
43 #include "nact-match-list.h"
45 /* column ordering
47 enum {
48 ITEM_COLUMN = 0,
49 MUST_MATCH_COLUMN,
50 MUST_NOT_MATCH_COLUMN,
51 N_COLUMN
54 typedef struct {
55 guint header_id;
56 gchar *header_label;
58 ColumnHeaderStruct;
60 /* i18n: label of the header of the column which let the user select a positive filter
62 static ColumnHeaderStruct st_match_headers[] = {
63 { MATCH_LIST_MUST_MATCH_ONE_OF, N_( "Must match one of" ) },
64 { MATCH_LIST_MUST_MATCH_ALL_OF, N_( "Must match all of" ) },
65 { 0 }
68 static gboolean st_on_selection_change = FALSE;
70 static void on_add_filter_clicked( GtkButton *button, MatchListStr *data );
71 static void on_filter_clicked( GtkTreeViewColumn *treeviewcolumn, MatchListStr *data );
72 static void on_filter_edited( GtkCellRendererText *renderer, const gchar *path, const gchar *text, MatchListStr *data );
73 static gboolean on_key_pressed_event( GtkWidget *widget, GdkEventKey *event, MatchListStr *data );
74 static void on_must_match_clicked( GtkTreeViewColumn *treeviewcolumn, MatchListStr *data );
75 static void on_must_match_toggled( GtkCellRendererToggle *cell_renderer, gchar *path, MatchListStr *data );
76 static void on_must_not_match_clicked( GtkTreeViewColumn *treeviewcolumn, MatchListStr *data );
77 static void on_must_not_match_toggled( GtkCellRendererToggle *cell_renderer, gchar *path, MatchListStr *data );
78 static void on_remove_filter_clicked( GtkButton *button, MatchListStr *data );
79 static void on_selection_changed( GtkTreeSelection *selection, MatchListStr *data );
81 static void add_filter( MatchListStr *data, const gchar *filter, const gchar *prefix );
82 static guint count_filters( const gchar *filter, MatchListStr *data );
83 static void delete_current_row( MatchListStr *data );
84 static void delete_row_at_path( GtkTreeView *treeview, GtkTreeModel *model, GtkTreePath *path );
85 static void dump_current_rows( MatchListStr *data );
86 static void edit_inline( MatchListStr *data );
87 static gchar *get_filter_from_path( const gchar *path_str, MatchListStr *data );
88 static const gchar *get_must_match_header( guint id );
89 static gboolean get_rows_iter( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter* iter, GSList **filters );
90 static void insert_new_row( MatchListStr *data );
91 static void insert_new_row_data( MatchListStr *data, const gchar *filter, gboolean match, gboolean no_match );
92 static void iter_for_setup( gchar *filter, GtkTreeModel *model );
93 static gchar *search_for_unique_label( const gchar *propal, MatchListStr *data );
94 static void set_match_status( const gchar *path_str, gboolean must_match, gboolean must_not_match, MatchListStr *data );
95 static void sort_on_column( GtkTreeViewColumn *treeviewcolumn, MatchListStr *data, guint colid );
97 /**
98 * nact_match_list_create_model:
99 * @window: the #BaseWindow window which contains the view.
100 * @tab_name: a string constant which identifies this page.
101 * @tab_id: our id for this page.
102 * @listview: the #GtkTreeView widget.
103 * @addbutton: the #GtkButton widget.
104 * @removebutton: the #GtkButton widget.
105 * @pget: a pointer to the function to get the list of filters.
106 * @pset: a pointer to the function to set the list of filters.
107 * @pon_add: an optional pointer to a function which handles the Add button.
108 * @item_header: the title of the item header.
110 * Creates the tree model.
112 void
113 nact_match_list_create_model( BaseWindow *window,
114 const gchar *tab_name, guint tab_id,
115 GtkWidget *listview, GtkWidget *addbutton, GtkWidget *removebutton,
116 pget_filters pget, pset_filters pset, pon_add_cb pon_add,
117 guint match_header,
118 const gchar *item_header,
119 gboolean editable_filter )
121 MatchListStr *data;
122 GtkListStore *model;
123 GtkCellRenderer *text_cell, *radio_cell;
124 GtkTreeViewColumn *column;
125 GtkTreeSelection *selection;
127 data = g_new0( MatchListStr, 1 );
128 data->window = window;
129 data->tab_id = tab_id;
130 data->listview = GTK_TREE_VIEW( listview );
131 data->addbutton = addbutton;
132 data->removebutton = removebutton;
133 data->pget = pget;
134 data->pset = pset;
135 data->pon_add = pon_add;
136 data->match_header = match_header;
137 data->item_header = g_strdup( item_header );
138 data->editable_filter = editable_filter;
139 data->editable_item = FALSE;
140 data->sort_column = 0;
141 data->sort_order = 0;
142 g_object_set_data( G_OBJECT( window ), tab_name, data );
144 model = gtk_list_store_new( N_COLUMN, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN );
145 gtk_tree_view_set_model( data->listview, GTK_TREE_MODEL( model ));
146 g_object_unref( model );
148 text_cell = gtk_cell_renderer_text_new();
149 column = gtk_tree_view_column_new_with_attributes(
150 data->item_header,
151 text_cell,
152 "text", ITEM_COLUMN,
153 NULL );
154 gtk_tree_view_append_column( data->listview, column );
156 radio_cell = gtk_cell_renderer_toggle_new();
157 gtk_cell_renderer_toggle_set_radio( GTK_CELL_RENDERER_TOGGLE( radio_cell ), TRUE );
158 column = gtk_tree_view_column_new_with_attributes(
159 get_must_match_header( match_header ),
160 radio_cell,
161 "active", MUST_MATCH_COLUMN,
162 NULL );
163 gtk_tree_view_append_column( data->listview, column );
165 radio_cell = gtk_cell_renderer_toggle_new();
166 gtk_cell_renderer_toggle_set_radio( GTK_CELL_RENDERER_TOGGLE( radio_cell ), TRUE );
167 column = gtk_tree_view_column_new_with_attributes(
168 /* i18n: label of the header of a column which let the user select a negative filter */
169 _( "Must not match any of" ),
170 radio_cell,
171 "active", MUST_NOT_MATCH_COLUMN,
172 NULL );
173 gtk_tree_view_append_column( data->listview, column );
175 /* an empty column to fill out the view
177 column = gtk_tree_view_column_new();
178 gtk_tree_view_append_column( data->listview, column );
180 gtk_tree_view_set_headers_visible( data->listview, TRUE );
181 gtk_tree_view_set_headers_clickable( data->listview, TRUE );
183 selection = gtk_tree_view_get_selection( data->listview );
184 gtk_tree_selection_set_mode( selection, GTK_SELECTION_BROWSE );
188 * nact_match_list_init_view:
189 * @window: the #BaseWindow window which contains the view.
190 * @tab_name: a string constant which identifies this page.
192 * Initializes the tab widget at each time the widget will be displayed.
193 * Connect signals.
195 void
196 nact_match_list_init_view( BaseWindow *window, const gchar *tab_name )
198 MatchListStr *data;
199 GtkTreeViewColumn *column;
200 GList *renderers;
201 GtkTreeModel *model;
203 data = ( MatchListStr * ) g_object_get_data( G_OBJECT( window ), tab_name );
204 g_return_if_fail( data != NULL );
206 column = gtk_tree_view_get_column( data->listview, ITEM_COLUMN );
207 base_window_signal_connect_with_data(
208 window,
209 G_OBJECT( column ),
210 "clicked",
211 G_CALLBACK( on_filter_clicked ),
212 data );
214 renderers = gtk_cell_layout_get_cells( GTK_CELL_LAYOUT( column ));
215 base_window_signal_connect_with_data(
216 window,
217 G_OBJECT( renderers->data ),
218 "edited",
219 G_CALLBACK( on_filter_edited ),
220 data );
222 column = gtk_tree_view_get_column( data->listview, MUST_MATCH_COLUMN );
223 base_window_signal_connect_with_data(
224 window,
225 G_OBJECT( column ),
226 "clicked",
227 G_CALLBACK( on_must_match_clicked ),
228 data );
230 renderers = gtk_cell_layout_get_cells( GTK_CELL_LAYOUT( column ));
231 base_window_signal_connect_with_data(
232 window,
233 G_OBJECT( renderers->data ),
234 "toggled",
235 G_CALLBACK( on_must_match_toggled ),
236 data );
238 column = gtk_tree_view_get_column( data->listview, MUST_NOT_MATCH_COLUMN );
239 base_window_signal_connect_with_data(
240 window,
241 G_OBJECT( column ),
242 "clicked",
243 G_CALLBACK( on_must_not_match_clicked ),
244 data );
246 renderers = gtk_cell_layout_get_cells( GTK_CELL_LAYOUT( column ));
247 base_window_signal_connect_with_data(
248 window,
249 G_OBJECT( renderers->data ),
250 "toggled",
251 G_CALLBACK( on_must_not_match_toggled ),
252 data );
254 if( data->pon_add ){
255 base_window_signal_connect(
256 window,
257 G_OBJECT( data->addbutton ),
258 "clicked",
259 G_CALLBACK( data->pon_add ));
260 } else {
261 base_window_signal_connect_with_data(
262 window,
263 G_OBJECT( data->addbutton ),
264 "clicked",
265 G_CALLBACK( on_add_filter_clicked ),
266 data );
269 base_window_signal_connect_with_data(
270 window,
271 G_OBJECT( data->removebutton ),
272 "clicked",
273 G_CALLBACK( on_remove_filter_clicked ),
274 data );
276 base_window_signal_connect_with_data(
277 window,
278 G_OBJECT( gtk_tree_view_get_selection( data->listview )),
279 "changed",
280 G_CALLBACK( on_selection_changed ),
281 data );
283 base_window_signal_connect_with_data(
284 window,
285 G_OBJECT( data->listview ),
286 "key-press-event",
287 G_CALLBACK( on_key_pressed_event ),
288 data );
290 model = gtk_tree_view_get_model( data->listview );
291 gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( model ), ITEM_COLUMN, GTK_SORT_ASCENDING );
292 data->sort_column = ITEM_COLUMN;
293 data->sort_order = GTK_SORT_ASCENDING;
295 column = gtk_tree_view_get_column( data->listview, ITEM_COLUMN );
296 sort_on_column( column, data, ITEM_COLUMN );
300 * nact_match_list_on_selection_changed:
301 * @window: the #BaseWindow window which contains the view.
302 * @tab_name: a string constant which identifies this page.
303 * @count_selected: count of selected items in the #NactIActionsList list view.
305 * Called at instance_dispose time.
307 * Basically we are using here a rather common scheme:
308 * - object has a GSList of strings, each of one being a filter description,
309 * which may be negated
310 * - the list is displayed in a listview with radio toggle buttons
311 * so that a user cannot have both positive and negative assertions
312 * for the same basename filter
313 * - update the object with a summary of the listbox contents
315 void
316 nact_match_list_on_selection_changed( BaseWindow *window, const gchar *tab_name, guint count_selected )
318 static const gchar *thisfn = "nact_match_list_on_selection_changed";
319 MatchListStr *data;
320 NAIContext *context;
321 gboolean enable_tab;
322 GSList *filters;
323 GtkTreeModel *model;
324 GtkTreeSelection *selection;
325 GtkTreeViewColumn *column;
326 GtkTreePath *path;
328 g_debug( "%s: window=%p, tab=%s, count_selected=%d", thisfn, ( void * ) window, tab_name, count_selected );
330 data = ( MatchListStr * ) g_object_get_data( G_OBJECT( window ), tab_name );
331 g_return_if_fail( data != NULL );
333 context = nact_main_tab_get_context( NACT_MAIN_WINDOW( window ), &data->editable_item );
335 enable_tab = ( context != NULL );
336 nact_main_tab_enable_page( NACT_MAIN_WINDOW( data->window ), data->tab_id, enable_tab );
338 st_on_selection_change = TRUE;
340 filters = context ? ( *data->pget )( context ) : NULL;
341 g_debug( "%s: filters=%p (count=%d)", thisfn, ( void * ) filters, filters ? g_slist_length( filters ) : -1 );
343 model = gtk_tree_view_get_model( data->listview );
344 selection = gtk_tree_view_get_selection( data->listview );
345 gtk_tree_selection_unselect_all( selection );
346 gtk_list_store_clear( GTK_LIST_STORE( model ));
348 if( filters ){
349 na_core_utils_slist_dump( thisfn, filters );
350 g_slist_foreach( filters, ( GFunc ) iter_for_setup, model );
353 column = gtk_tree_view_get_column( data->listview, ITEM_COLUMN );
354 nact_gtk_utils_set_editable( G_OBJECT( column ), data->editable_item && data->editable_filter );
356 nact_gtk_utils_set_editable( G_OBJECT( data->addbutton ), data->editable_item );
357 nact_gtk_utils_set_editable( G_OBJECT( data->removebutton ), data->editable_item );
358 gtk_widget_set_sensitive( data->removebutton, FALSE );
360 st_on_selection_change = FALSE;
362 path = gtk_tree_path_new_first();
363 if( path ){
364 selection = gtk_tree_view_get_selection( data->listview );
365 gtk_tree_selection_select_path( selection, path );
366 gtk_tree_path_free( path );
371 * nact_match_list_insert_row:
372 * @window: the #BaseWindow window which contains the view.
373 * @tab_name: a string constant which identifies this page.
374 * @filter: the item to add.
375 * @match: whether the 'must match' column is checked.
376 * @not_match: whether the 'must not match' column is checked.
378 * Add a new row to the list view.
380 void
381 nact_match_list_insert_row( BaseWindow *window, const gchar *tab_name, const gchar *filter, gboolean match, gboolean not_match )
383 MatchListStr *data;
385 data = ( MatchListStr * ) g_object_get_data( G_OBJECT( window ), tab_name );
386 g_return_if_fail( data != NULL );
388 insert_new_row_data( data, filter, match, not_match );
392 * nact_match_list_get_rows:
393 * @window: the #BaseWindow window which contains the view.
394 * @tab_name: a string constant which identifies this page.
396 * Returns the list of rows as a newly allocated string list which should
397 * be na_core_utils_slist_free() by the caller.
399 GSList *
400 nact_match_list_get_rows( BaseWindow *window, const gchar *tab_name )
402 GSList *filters;
403 MatchListStr *data;
404 GtkTreeModel *model;
406 data = ( MatchListStr * ) g_object_get_data( G_OBJECT( window ), tab_name );
407 g_return_val_if_fail( data != NULL, NULL );
409 model = gtk_tree_view_get_model( data->listview );
410 filters = NULL;
411 gtk_tree_model_foreach( model, ( GtkTreeModelForeachFunc ) get_rows_iter, &filters );
413 return( filters );
417 * nact_match_list_dispose:
418 * @window: the #BaseWindow window which contains the view.
419 * @tab_name: a string constant which identifies this page.
421 * Called at instance_dispose time.
423 void
424 nact_match_list_dispose( BaseWindow *window, const gchar *tab_name )
426 MatchListStr *data;
427 GtkTreeModel *model;
428 GtkTreeSelection *selection;
430 data = ( MatchListStr * ) g_object_get_data( G_OBJECT( window ), tab_name );
431 g_return_if_fail( data != NULL );
433 model = gtk_tree_view_get_model( data->listview );
434 selection = gtk_tree_view_get_selection( data->listview );
435 gtk_tree_selection_unselect_all( selection );
436 gtk_list_store_clear( GTK_LIST_STORE( model ));
438 g_free( data->item_header );
440 g_free( data );
441 g_object_set_data( G_OBJECT( window ), tab_name, NULL );
444 static void
445 on_add_filter_clicked( GtkButton *button, MatchListStr *data )
447 insert_new_row( data );
450 static void
451 on_filter_clicked( GtkTreeViewColumn *treeviewcolumn, MatchListStr *data )
453 sort_on_column( treeviewcolumn, data, ITEM_COLUMN );
456 static void
457 on_filter_edited( GtkCellRendererText *renderer, const gchar *path_str, const gchar *text, MatchListStr *data )
459 static const gchar *thisfn = "nact_match_list_on_filter_edited";
460 GtkTreeModel *model;
461 GtkTreeIter iter;
462 GtkTreePath *path;
463 gchar *old_text;
464 NAIContext *context;
465 gboolean must_match, must_not_match;
466 gchar *to_add, *to_remove;
467 GSList *filters;
468 GtkWidget *dialog;
470 g_return_if_fail( data->editable_filter );
472 context = nact_main_tab_get_context( NACT_MAIN_WINDOW( data->window ), NULL );
473 g_return_if_fail( NA_IS_ICONTEXT( context ));
475 model = gtk_tree_view_get_model( data->listview );
476 path = gtk_tree_path_new_from_string( path_str );
477 gtk_tree_model_get_iter( model, &iter, path );
478 gtk_tree_path_free( path );
480 gtk_tree_model_get( model, &iter, ITEM_COLUMN, &old_text, -1 );
482 if( strcmp( text, old_text ) == 0 ){
483 return;
486 dump_current_rows( data );
487 g_debug( "%s: new filter=%s, count=%d", thisfn, text, count_filters( text, data ));
489 if( count_filters( text, data ) >= 1 ){
490 dialog = gtk_message_dialog_new(
491 base_window_get_toplevel( BASE_WINDOW( data->window )),
492 GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
493 _( "'%s' filter already exists in the list.\nPlease provide another one." ), text );
494 gtk_dialog_run( GTK_DIALOG( dialog ));
495 gtk_widget_destroy( dialog );
497 return;
500 gtk_tree_model_get( model, &iter,
501 MUST_MATCH_COLUMN, &must_match,
502 MUST_NOT_MATCH_COLUMN, &must_not_match,
503 -1 );
505 gtk_list_store_set( GTK_LIST_STORE( model ), &iter, ITEM_COLUMN, text, -1 );
507 filters = ( *data->pget )( context );
509 if( filters ){
510 to_remove = g_strdup( old_text );
511 filters = na_core_utils_slist_remove_ascii( filters, to_remove );
512 g_free( to_remove );
513 to_remove = g_strdup_printf( "!%s", old_text );
514 filters = na_core_utils_slist_remove_ascii( filters, to_remove );
515 g_free( to_remove );
518 if( must_match ){
519 filters = g_slist_prepend( filters, g_strdup( text ));
521 } else if( must_not_match ){
522 to_add = g_strdup_printf( "!%s", text );
523 filters = g_slist_prepend( filters, to_add );
526 ( *data->pset )( context, filters );
527 na_core_utils_slist_free( filters );
528 g_free( old_text );
530 g_signal_emit_by_name( G_OBJECT( data->window ), TAB_UPDATABLE_SIGNAL_ITEM_UPDATED, context, FALSE );
533 static gboolean
534 on_key_pressed_event( GtkWidget *widget, GdkEventKey *event, MatchListStr *data )
536 gboolean stop;
538 stop = FALSE;
540 if( event->keyval == NACT_KEY_F2 ){
541 if( data->editable_filter ){
542 edit_inline( data );
543 stop = TRUE;
547 if( event->keyval == NACT_KEY_Insert || event->keyval == NACT_KEY_KP_Insert ){
548 if( data->editable_item ){
549 insert_new_row( data );
550 stop = TRUE;
554 if( event->keyval == NACT_KEY_Delete || event->keyval == NACT_KEY_KP_Delete ){
555 if( data->editable_item ){
556 delete_current_row( data );
557 stop = TRUE;
561 return( stop );
564 static void
565 on_must_match_clicked( GtkTreeViewColumn *treeviewcolumn, MatchListStr *data )
567 sort_on_column( treeviewcolumn, data, MUST_MATCH_COLUMN );
571 * clicking on an already active toggle button has no effect
572 * clicking on an inactive toggle button has a double effect:
573 * - the other toggle button becomes inactive
574 * - this toggle button becomes active
575 * the corresponding strings must be respectively removed/added to the
576 * filters list
578 static void
579 on_must_match_toggled( GtkCellRendererToggle *cell_renderer, gchar *path_str, MatchListStr *data )
581 /*static const gchar *thisfn = "nact_match_list_on_must_match_toggled";*/
582 gchar *filter;
583 NAIContext *context;
584 GSList *filters;
585 gchar *to_remove;
586 gboolean active;
588 /*gboolean is_active = gtk_cell_renderer_toggle_get_active( cell_renderer );
589 g_debug( "%s: is_active=%s", thisfn, is_active ? "True":"False" );*/
591 active = gtk_cell_renderer_toggle_get_active( cell_renderer );
593 if( data->editable_item ){
594 if( !active ){
595 context = nact_main_tab_get_context( NACT_MAIN_WINDOW( data->window ), NULL );
596 g_return_if_fail( NA_IS_ICONTEXT( context ));
598 set_match_status( path_str, TRUE, FALSE, data );
600 filter = get_filter_from_path( path_str, data );
601 filters = ( *data->pget )( context );
603 if( filters ){
604 to_remove = g_strdup_printf( "!%s", filter );
605 filters = na_core_utils_slist_remove_ascii( filters, to_remove );
606 g_free( to_remove );
609 filters = g_slist_prepend( filters, g_strdup( filter ));
610 ( *data->pset )( context, filters );
612 na_core_utils_slist_free( filters );
613 g_free( filter );
615 g_signal_emit_by_name( G_OBJECT( data->window ), TAB_UPDATABLE_SIGNAL_ITEM_UPDATED, context, FALSE );
617 } else {
618 g_signal_handlers_block_by_func(( gpointer ) cell_renderer, on_must_match_toggled, data );
619 gtk_cell_renderer_toggle_set_active( cell_renderer, !active );
620 g_signal_handlers_unblock_by_func(( gpointer ) cell_renderer, on_must_match_toggled, data );
624 static void
625 on_must_not_match_clicked( GtkTreeViewColumn *treeviewcolumn, MatchListStr *data )
627 sort_on_column( treeviewcolumn, data, MUST_NOT_MATCH_COLUMN );
630 static void
631 on_must_not_match_toggled( GtkCellRendererToggle *cell_renderer, gchar *path_str, MatchListStr *data )
633 /*static const gchar *thisfn = "nact_match_list_on_must_not_match_toggled";*/
634 gchar *filter;
635 NAIContext *context;
636 GSList *filters;
637 gchar *to_add;
638 gboolean active;
640 /*gboolean is_active = gtk_cell_renderer_toggle_get_active( cell_renderer );
641 g_debug( "%s: is_active=%s", thisfn, is_active ? "True":"False" );*/
643 active = gtk_cell_renderer_toggle_get_active( cell_renderer );
645 if( data->editable_item ){
646 if( !active ){
647 context = nact_main_tab_get_context( NACT_MAIN_WINDOW( data->window ), NULL );
648 g_return_if_fail( NA_IS_ICONTEXT( context ));
650 set_match_status( path_str, FALSE, TRUE, data );
652 filter = get_filter_from_path( path_str, data );
653 filters = ( *data->pget )( context );
655 if( filters ){
656 filters = na_core_utils_slist_remove_ascii( filters, filter );
659 to_add = g_strdup_printf( "!%s", filter );
660 filters = g_slist_prepend( filters, to_add );
661 ( *data->pset )( context, filters );
663 na_core_utils_slist_free( filters );
664 g_free( filter );
666 g_signal_emit_by_name( G_OBJECT( data->window ), TAB_UPDATABLE_SIGNAL_ITEM_UPDATED, context, FALSE );
668 } else {
669 g_signal_handlers_block_by_func(( gpointer ) cell_renderer, on_must_not_match_toggled, data );
670 gtk_cell_renderer_toggle_set_active( cell_renderer, !active );
671 g_signal_handlers_unblock_by_func(( gpointer ) cell_renderer, on_must_not_match_toggled, data );
675 static void
676 on_remove_filter_clicked( GtkButton *button, MatchListStr *data )
678 delete_current_row( data );
681 static void
682 on_selection_changed( GtkTreeSelection *selection, MatchListStr *data )
684 gtk_widget_set_sensitive( data->removebutton,
685 data->editable_item && gtk_tree_selection_count_selected_rows( selection ) > 0 );
688 static void
689 add_filter( MatchListStr *data, const gchar *filter, const gchar *prefix )
691 NAIContext *context;
692 GSList *filters;
693 gchar *to_add;
695 context = nact_main_tab_get_context( NACT_MAIN_WINDOW( data->window ), NULL );
697 if( context ){
698 filters = ( *data->pget )( context );
699 to_add = g_strdup_printf( "%s%s", prefix, filter );
700 filters = g_slist_prepend( filters, to_add );
701 ( *data->pset )( context, filters );
702 na_core_utils_slist_free( filters );
704 g_signal_emit_by_name( G_OBJECT( data->window ), TAB_UPDATABLE_SIGNAL_ITEM_UPDATED, context, FALSE );
708 static guint
709 count_filters( const gchar *filter, MatchListStr *data )
711 guint count;
712 GtkTreeModel *model;
713 GSList *filters;
715 model = gtk_tree_view_get_model( data->listview );
716 filters = NULL;
717 gtk_tree_model_foreach( model, ( GtkTreeModelForeachFunc ) get_rows_iter, &filters );
718 count = na_core_utils_slist_count( filters, filter );
719 na_core_utils_slist_free( filters );
721 return( count );
724 static void
725 delete_current_row( MatchListStr *data )
727 GtkTreeSelection *selection;
728 GtkTreeModel *model;
729 GList *rows;
730 GtkTreePath *path;
731 GtkTreeIter iter;
732 gchar *filter;
733 NAIContext *context;
734 GSList *filters;
735 gchar *to_remove;
737 selection = gtk_tree_view_get_selection( data->listview );
738 model = gtk_tree_view_get_model( data->listview );
739 rows = gtk_tree_selection_get_selected_rows( selection, NULL );
741 if( g_list_length( rows ) == 1 ){
742 path = ( GtkTreePath * ) rows->data;
743 gtk_tree_model_get_iter( model, &iter, path );
744 gtk_tree_model_get( model, &iter, ITEM_COLUMN, &filter, -1 );
746 delete_row_at_path( data->listview, model, path );
748 context = nact_main_tab_get_context( NACT_MAIN_WINDOW( data->window ), NULL );
750 if( context ){
751 filters = ( *data->pget )( context );
753 if( filters ){
754 to_remove = g_strdup_printf( "!%s", filter );
755 filters = na_core_utils_slist_remove_ascii( filters, to_remove );
756 g_free( to_remove );
757 filters = na_core_utils_slist_remove_ascii( filters, filter );
758 ( *data->pset )( context, filters );
759 na_core_utils_slist_free( filters );
761 g_signal_emit_by_name( G_OBJECT( data->window ), TAB_UPDATABLE_SIGNAL_ITEM_UPDATED, context, FALSE );
765 g_free( filter );
768 g_list_foreach( rows, ( GFunc ) gtk_tree_path_free, NULL );
769 g_list_free( rows );
772 static void
773 delete_row_at_path( GtkTreeView *treeview, GtkTreeModel *model, GtkTreePath *path )
775 GtkTreeIter iter;
777 if( gtk_tree_model_get_iter( model, &iter, path )){
778 gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
780 if( gtk_tree_model_get_iter( model, &iter, path ) || gtk_tree_path_prev( path )){
781 gtk_tree_view_set_cursor( treeview, path, NULL, FALSE );
786 static void
787 dump_current_rows( MatchListStr *data )
789 #ifdef NA_MAINTAINER_MODE
790 GtkTreeModel *model;
791 GSList *filters;
793 model = gtk_tree_view_get_model( data->listview );
794 filters = NULL;
795 gtk_tree_model_foreach( model, ( GtkTreeModelForeachFunc ) get_rows_iter, &filters );
796 na_core_utils_slist_dump( "nact_match_list_dump_current_rows", filters );
797 na_core_utils_slist_free( filters );
798 #endif
801 static void
802 edit_inline( MatchListStr *data )
804 GtkTreeSelection *selection;
805 GList *rows;
806 GtkTreePath *path;
807 GtkTreeViewColumn *column;
809 selection = gtk_tree_view_get_selection( data->listview );
810 rows = gtk_tree_selection_get_selected_rows( selection, NULL );
812 if( g_list_length( rows ) == 1 ){
813 gtk_tree_view_get_cursor( data->listview, &path, &column );
814 gtk_tree_view_set_cursor( data->listview, path, column, TRUE );
815 gtk_tree_path_free( path );
818 g_list_foreach( rows, ( GFunc ) gtk_tree_path_free, NULL );
819 g_list_free( rows );
822 static gchar *
823 get_filter_from_path( const gchar *path_str, MatchListStr *data )
825 gchar *filter;
826 GtkTreeModel *model;
827 GtkTreePath *path;
828 GtkTreeIter iter;
830 filter = NULL;
832 model = gtk_tree_view_get_model( data->listview );
833 path = gtk_tree_path_new_from_string( path_str );
834 gtk_tree_model_get_iter( model, &iter, path );
835 gtk_tree_path_free( path );
837 gtk_tree_model_get( model, &iter, ITEM_COLUMN, &filter, -1 );
839 return( filter );
842 static const gchar *
843 get_must_match_header( guint id )
845 guint i;
847 for( i = 0 ; st_match_headers[i].header_id ; ++i ){
848 if( st_match_headers[i].header_id == id ){
849 return( st_match_headers[i].header_label );
853 return( "" );
856 static gboolean
857 get_rows_iter( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter* iter, GSList **filters )
859 gchar *keyword;
861 gtk_tree_model_get( model, iter, ITEM_COLUMN, &keyword, -1 );
862 *filters = g_slist_prepend( *filters, keyword );
864 return( FALSE ); /* don't stop looping */
867 static void
868 insert_new_row( MatchListStr *data )
870 /* i18n notes : new filter for a new row in a match/no match list */
871 static const gchar *filter_label = N_( "new-filter" );
872 gchar *label;
874 label = search_for_unique_label( filter_label, data );
875 insert_new_row_data( data, label, FALSE, FALSE );
876 g_free( label );
879 static void
880 insert_new_row_data( MatchListStr *data, const gchar *filter, gboolean match, gboolean not_match )
882 GtkTreeModel *model;
883 GtkTreeIter iter;
884 GtkTreePath *path;
885 GtkTreeViewColumn *column;
887 g_return_if_fail( !( match && not_match ));
889 model = gtk_tree_view_get_model( data->listview );
891 gtk_list_store_insert_with_values( GTK_LIST_STORE( model ), &iter, 0,
892 ITEM_COLUMN, filter,
893 MUST_MATCH_COLUMN, match,
894 MUST_NOT_MATCH_COLUMN, not_match,
895 -1 );
897 path = gtk_tree_model_get_path( model, &iter );
898 column = gtk_tree_view_get_column( data->listview, ITEM_COLUMN );
899 gtk_tree_view_set_cursor( data->listview, path, column, TRUE );
900 gtk_tree_path_free( path );
902 if( match ){
903 add_filter( data, filter, "" );
906 if( not_match ){
907 add_filter( data, filter, "!" );
911 static void
912 iter_for_setup( gchar *filter_orig, GtkTreeModel *model )
914 GtkTreeIter iter;
915 gchar *tmp, *filter;
916 gboolean positive;
917 gboolean negative;
919 filter = g_strstrip( g_strdup( filter_orig ));
920 positive = FALSE;
921 negative = FALSE;
923 if( filter[0] == '!' ){
924 tmp = g_strstrip( g_strdup( filter+1 ));
925 g_free( filter );
926 filter = tmp;
927 negative = TRUE;
929 } else {
930 positive = TRUE;
933 gtk_list_store_append( GTK_LIST_STORE( model ), &iter );
934 gtk_list_store_set(
935 GTK_LIST_STORE( model ),
936 &iter,
937 ITEM_COLUMN, filter,
938 MUST_MATCH_COLUMN, positive,
939 MUST_NOT_MATCH_COLUMN, negative,
940 -1 );
942 g_free( filter );
945 static gchar *
946 search_for_unique_label( const gchar *propal, MatchListStr *data )
948 gchar *label;
949 guint count;
951 label = g_strdup( propal );
952 count = 1;
954 while( count_filters( label, data ) >= 1 ){
955 g_free( label );
956 label = g_strdup_printf( "%s-%d", propal, ++count );
959 return( label );
962 static void
963 set_match_status( const gchar *path_str, gboolean must_match, gboolean must_not_match, MatchListStr *data )
965 GtkTreeModel *model;
966 GtkTreePath *path;
967 GtkTreeIter iter;
969 model = gtk_tree_view_get_model( data->listview );
970 path = gtk_tree_path_new_from_string( path_str );
971 gtk_tree_model_get_iter( model, &iter, path );
972 gtk_tree_path_free( path );
974 gtk_list_store_set( GTK_LIST_STORE( model ), &iter,
975 MUST_MATCH_COLUMN, must_match,
976 MUST_NOT_MATCH_COLUMN, must_not_match,
977 -1 );
980 static void
981 sort_on_column( GtkTreeViewColumn *treeviewcolumn, MatchListStr *data, guint new_col_id )
983 guint prev_col_id;
984 guint prev_order, new_order;
985 GtkTreeModel *model;
986 GtkTreeViewColumn *column;
988 prev_col_id = data->sort_column;
989 prev_order = data->sort_order;
991 column = gtk_tree_view_get_column( data->listview, prev_col_id );
992 gtk_tree_view_column_set_sort_indicator( column, FALSE );
994 if( new_col_id == prev_col_id ){
995 new_order = ( prev_order == GTK_SORT_ASCENDING ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING );
996 } else {
997 new_order = GTK_SORT_ASCENDING;
1000 data->sort_column = new_col_id;
1001 data->sort_order = new_order;
1003 gtk_tree_view_column_set_sort_indicator( treeviewcolumn, TRUE );
1004 gtk_tree_view_column_set_sort_order( treeviewcolumn, new_order );
1006 model = gtk_tree_view_get_model( data->listview );
1007 gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( model ), new_col_id, new_order );