Document Replay Gain
[cmus.git] / filters.c
blob0079db6e1473b2dde6d45aabb57ea367b1014f4f
1 /*
2 * Copyright 2005 Timo Hirvonen
3 */
5 #include "filters.h"
6 #include "expr.h"
7 #include "window.h"
8 #include "search.h"
9 #include "uchar.h"
10 #include "lib.h"
11 #include "misc.h"
12 #include "file.h"
13 #include "ui_curses.h"
14 #include "xmalloc.h"
16 #include <ctype.h>
18 struct window *filters_win;
19 struct searchable *filters_searchable;
20 LIST_HEAD(filters_head);
22 static const char *recursive_filter;
24 static inline void filter_entry_to_iter(struct filter_entry *e, struct iter *iter)
26 iter->data0 = &filters_head;
27 iter->data1 = e;
28 iter->data2 = NULL;
31 static GENERIC_ITER_PREV(filters_get_prev, struct filter_entry, node)
32 static GENERIC_ITER_NEXT(filters_get_next, struct filter_entry, node)
34 static int filters_search_get_current(void *data, struct iter *iter)
36 return window_get_sel(filters_win, iter);
39 static int filters_search_matches(void *data, struct iter *iter, const char *text)
41 char **words = get_words(text);
42 int matched = 0;
44 if (words[0] != NULL) {
45 struct filter_entry *e;
46 int i;
48 e = iter_to_filter_entry(iter);
49 for (i = 0; ; i++) {
50 if (words[i] == NULL) {
51 window_set_sel(filters_win, iter);
52 matched = 1;
53 break;
55 if (u_strcasestr(e->name, words[i]) == NULL)
56 break;
59 free_str_array(words);
60 return matched;
63 static const struct searchable_ops filters_search_ops = {
64 .get_prev = filters_get_prev,
65 .get_next = filters_get_next,
66 .get_current = filters_search_get_current,
67 .matches = filters_search_matches
70 static void free_filter(struct filter_entry *e)
72 free(e->name);
73 free(e->filter);
74 free(e);
77 static struct filter_entry *find_filter(const char *name)
79 struct filter_entry *e;
81 list_for_each_entry(e, &filters_head, node) {
82 if (strcmp(e->name, name) == 0)
83 return e;
85 return NULL;
88 static const char *get_filter(const char *name)
90 struct filter_entry *e = find_filter(name);
92 if (e) {
93 if (e->visited) {
94 recursive_filter = e->name;
95 return NULL;
97 e->visited = 1;
98 return e->filter;
100 return NULL;
103 void filters_activate(void)
105 struct filter_entry *f;
106 struct expr *e, *expr = NULL;
108 /* mark visited and AND together all selected filters
109 * mark any other filters unvisited */
110 list_for_each_entry(f, &filters_head, node) {
111 f->visited = 0;
112 if (f->sel_stat == FS_IGNORE)
113 continue;
115 f->visited = 1;
116 e = expr_parse(f->filter);
117 if (e == NULL) {
118 error_msg("error parsing filter %s: %s", f->name, expr_error());
119 if (expr)
120 expr_free(expr);
121 return;
124 if (f->sel_stat == FS_NO) {
125 /* add ! */
126 struct expr *not = xnew(struct expr, 1);
128 not->type = EXPR_NOT;
129 not->key = NULL;
130 not->left = e;
131 not->right = NULL;
132 e = not;
134 if (expr == NULL) {
135 expr = e;
136 } else {
137 struct expr *and = xnew(struct expr, 1);
139 and->type = EXPR_AND;
140 and->key = NULL;
141 and->left = expr;
142 and->right = e;
143 expr->parent = and;
144 e->parent = and;
145 expr = and;
149 recursive_filter = NULL;
150 if (expr && expr_check_leaves(&expr, get_filter)) {
151 if (recursive_filter) {
152 error_msg("recursion detected in filter %s", recursive_filter);
153 } else {
154 error_msg("error parsing filter: %s", expr_error());
156 expr_free(expr);
157 return;
160 /* update active flag */
161 list_for_each_entry(f, &filters_head, node) {
162 f->act_stat = f->sel_stat;
164 lib_set_filter(expr);
165 filters_win->changed = 1;
168 static int for_each_name(const char *str, int (*cb)(const char *name, int sel_stat))
170 char buf[64];
171 int s, e, len;
173 e = 0;
174 do {
175 int sel_stat = FS_YES;
177 s = e;
178 while (str[s] == ' ')
179 s++;
180 if (str[s] == '!') {
181 sel_stat = FS_NO;
182 s++;
184 e = s;
185 while (str[e] && str[e] != ' ')
186 e++;
188 len = e - s;
189 if (len == 0)
190 return 0;
191 if (len >= sizeof(buf)) {
192 error_msg("filter name too long");
193 return -1;
196 memcpy(buf, str + s, len);
197 buf[len] = 0;
199 if (cb(buf, sel_stat))
200 return -1;
201 } while (1);
204 static int ensure_filter_name(const char *name, int sel_stat)
206 if (find_filter(name) == NULL) {
207 error_msg("no such filter %s", name);
208 return -1;
210 return 0;
213 static int select_filter(const char *name, int sel_stat)
215 struct filter_entry *e = find_filter(name);
217 e->sel_stat = sel_stat;
218 return 0;
221 void filters_activate_names(const char *str)
223 struct filter_entry *f;
225 /* first validate all filter names */
226 if (str && for_each_name(str, ensure_filter_name))
227 return;
229 /* mark all filters unselected */
230 list_for_each_entry(f, &filters_head, node)
231 f->sel_stat = FS_IGNORE;
233 /* select the filters */
234 if (str)
235 for_each_name(str, select_filter);
237 /* activate selected */
238 filters_activate();
241 void filters_toggle_filter(void)
243 struct iter iter;
245 if (window_get_sel(filters_win, &iter)) {
246 struct filter_entry *e;
248 e = iter_to_filter_entry(&iter);
249 e->sel_stat = (e->sel_stat + 1) % 3;
250 filters_win->changed = 1;
254 void filters_delete_filter(void)
256 struct iter iter;
258 if (window_get_sel(filters_win, &iter)) {
259 struct filter_entry *e;
261 e = iter_to_filter_entry(&iter);
262 if (yes_no_query("Delete filter '%s'? [y/N]", e->name)) {
263 window_row_vanishes(filters_win, &iter);
264 list_del(&e->node);
265 free_filter(e);
270 static int validate_filter_name(const char *name)
272 int i;
274 for (i = 0; name[i]; i++) {
275 if (isalnum(name[i]))
276 continue;
277 if (name[i] == '_' || name[i] == '-')
278 continue;
279 return 0;
281 return i != 0;
284 static void do_filters_set_filter(const char *keyval)
286 const char *eq = strchr(keyval, '=');
287 char *key, *val;
288 struct expr *expr;
289 struct filter_entry *new;
290 struct list_head *item;
292 if (eq == NULL) {
293 if (ui_initialized)
294 error_msg("invalid argument ('key=value' expected)");
295 return;
297 key = xstrndup(keyval, eq - keyval);
298 val = xstrdup(eq + 1);
299 if (!validate_filter_name(key)) {
300 if (ui_initialized)
301 error_msg("invalid filter name (can only contain 'a-zA-Z0-9_-' characters)");
302 free(key);
303 free(val);
304 return;
306 expr = expr_parse(val);
307 if (expr == NULL) {
308 if (ui_initialized)
309 error_msg("error parsing filter %s: %s", val, expr_error());
310 free(key);
311 free(val);
312 return;
314 expr_free(expr);
316 new = xnew(struct filter_entry, 1);
317 new->name = key;
318 new->filter = val;
319 new->act_stat = FS_IGNORE;
320 new->sel_stat = FS_IGNORE;
322 /* add or replace filter */
323 list_for_each(item, &filters_head) {
324 struct filter_entry *e = container_of(item, struct filter_entry, node);
325 int res = strcmp(key, e->name);
327 if (res < 0)
328 break;
329 if (res == 0) {
330 /* replace */
331 struct iter iter;
333 if (ui_initialized) {
334 filter_entry_to_iter(e, &iter);
335 window_row_vanishes(filters_win, &iter);
337 item = item->next;
338 list_del(&e->node);
339 free_filter(e);
340 break;
343 /* add before item */
344 list_add_tail(&new->node, item);
345 if (ui_initialized)
346 window_changed(filters_win);
349 void filters_init(void)
351 struct iter iter;
353 filters_win = window_new(filters_get_prev, filters_get_next);
354 window_set_contents(filters_win, &filters_head);
355 window_changed(filters_win);
357 iter.data0 = &filters_head;
358 iter.data1 = NULL;
359 iter.data2 = NULL;
360 filters_searchable = searchable_new(NULL, &iter, &filters_search_ops);
363 void filters_exit(void)
365 searchable_free(filters_searchable);
366 window_free(filters_win);
369 void filters_set_filter(const char *keyval)
371 do_filters_set_filter(keyval);
374 struct expr *parse_filter(const char *val)
376 struct expr *e = NULL;
377 struct filter_entry *f;
379 if (val) {
380 e = expr_parse(val);
381 if (e == NULL) {
382 error_msg("error parsing filter %s: %s", val, expr_error());
383 return NULL;
387 /* mark all unvisited so that we can check recursion */
388 list_for_each_entry(f, &filters_head, node)
389 f->visited = 0;
391 recursive_filter = NULL;
392 if (e && expr_check_leaves(&e, get_filter)) {
393 if (recursive_filter) {
394 error_msg("recursion detected in filter %s", recursive_filter);
395 } else {
396 error_msg("error parsing filter: %s", expr_error());
398 expr_free(e);
399 return NULL;
401 return e;
404 void filters_set_anonymous(const char *val)
406 struct filter_entry *f;
407 struct expr *e = NULL;
409 if (val) {
410 e = parse_filter(val);
411 if (e == NULL)
412 return;
415 /* deactive all filters */
416 list_for_each_entry(f, &filters_head, node)
417 f->act_stat = FS_IGNORE;
419 lib_set_filter(e);
421 filters_win->changed = 1;