1 #include <config.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <errno.h>
5 #include <string.h>
6 #include <glib.h>
7 #include <glib/gi18n.h>
8 #include <gtk/gtk.h>
9 #include <glade/glade.h>
10 #ifdef HAVE_GNU_REGEX_H
11 # include <gnu/regex.h>	/* for recent FreeBSD */
12 #elif HAVE_GNUREGEX_H
13 # include <gnuregex.h>	/* for older FreeBSD */
14 #else
15 # include <regex.h>	/* for everyone else */
16 #endif
use_structs()17 
18 #include "elist.h"
19 #include "file_util.h"
20 #include "str_util.h"
21 #include "str_convert.h"
22 #include "mru.h"
23 #include "prefs.h"
24 #include "audio_file.h"
25 #include "genre.h"
26 #include "file_list.h"
27 #include "char_conv_dlg.h"
28 #include "message_box.h"
29 #include "progress_dlg.h"
30 #include "cursor.h"
31 #include "help.h"
32 #include "tag_tab.h"
33 
34 
35 enum {
36 	APPLY_TO_ALL = 0,
37 	APPLY_TO_SELECTED = 1
38 };
39 
40 
41 /* for parsing file names */
42 typedef struct {
43 	/* regexp to match against the file name */
44 	regex_t re;
45 	/* indexes of substring matches */
46 	gint title;
47 	gint artist;
48 	gint album;
49 	gint year;
50 	gint comment;
51 	gint track;
52 	gint genre;
53 } parse_info;
54 
55 
56 /* widgets */
57 static GtkCombo *combo_tag_format = NULL;
58 static GtkEntry *ent_tag_format = NULL;
59 static GtkButton *b_tag_edit_format = NULL;
60 static GtkCheckButton *cb_use_filename = NULL;
61 static GtkButton *b_tag_go = NULL;
62 static GtkComboBox *combo_tag_apply = NULL;
63 static GtkCheckButton *cb_title = NULL;
64 static GtkCheckButton *cb_artist = NULL;
65 static GtkCheckButton *cb_album = NULL;
66 static GtkCheckButton *cb_year = NULL;
67 static GtkCheckButton *cb_comment = NULL;
68 static GtkCheckButton *cb_track = NULL;
69 static GtkCheckButton *cb_genre = NULL;
70 static GtkEntry *ent_title = NULL;
71 static GtkEntry *ent_artist = NULL;
72 static GtkEntry *ent_album = NULL;
73 static GtkEntry *ent_year = NULL;
74 static GtkEntry *ent_comment = NULL;
75 static GtkEntry *ent_track = NULL;
76 static GtkCheckButton *cb_track_auto = NULL;
77 static GtkCombo *combo_genre = NULL;
78 static GtkEntry *ent_genre = NULL;
79 static GtkWindow *w_main = NULL;
80 
81 /* private data */
82 static gboolean ignore_field_changed = FALSE;
83 static gboolean from_fn_title = FALSE;
84 static gboolean from_fn_artist = FALSE;
85 static gboolean from_fn_album = FALSE;
86 static gboolean from_fn_year = FALSE;
87 static gboolean from_fn_comment = FALSE;
88 static gboolean from_fn_track = FALSE;
89 static gboolean from_fn_genre = FALSE;
90 
91 /* preferences */
92 static gboolean *use_filename;
93 static MRUList *format_mru;
94 
95 
96 /*** private functions ******************************************************/
97 
98 static parse_info *build_parse_info(const gchar *format)
99 {
100 	parse_info *info = calloc(1, sizeof(parse_info));
101 	gchar *aux_str = calloc(1, 2 + 2*strlen(format));
102 	gchar *expr_str = calloc(1, 2 + 2*strlen(format));
103 	gint i, j, span, pos;
104 	gchar *p;
105 	gint res;
106 
107 
108 	/* printf("format: %s\n", format); */
109 
110 	/* constrain the expression to match the whole string and escape
111 	   characters that have special meaning in regexps */
112 	i = 0;
113 	j = 0;
114 	aux_str[j++] = '^';
115 	while (format[i]) {
116 		if (strchr(".*+?[]{}()|^$\\", format[i]))
117 			aux_str[j++] = '\\';
118 		aux_str[j++] = format[i++];
119 	}
120 	aux_str[j++] = '$';
121 	aux_str[j++] = 0;
122 
123 	/* find the markers, record their relative positions and replace
124 	   them in the final expression */
125 	i = 0;
126 	j = 0;
127 	pos = 1;
128 	while (TRUE) {
129 		p = index(&aux_str[i], '<');
130 		if (p != NULL) {
131 			span = (gint)(p - &aux_str[i]);
132 			if (span > 0) {
133 				strncpy(&expr_str[j], &aux_str[i], span);
134 				i += span;
135 				j += span;
136 			} else if (strncmp(&aux_str[i], "<title>", 7) == 0) {
137 				strncpy(&expr_str[j], "(.*)", 4);
138 				info->title = pos++;
139 				i += 7;
140 				j += 4;
141 			} else if (strncmp(&aux_str[i], "<artist>", 8) == 0) {
142 				strncpy(&expr_str[j], "(.*)", 4);
143 				info->artist = pos++;
144 				i += 8;
145 				j += 4;
146 			} else if (strncmp(&aux_str[i], "<album>", 7) == 0) {
147 				strncpy(&expr_str[j], "(.*)", 4);
148 				info->album = pos++;
149 				i += 7;
150 				j += 4;
151 			} else if (strncmp(&aux_str[i], "<year>", 6) == 0) {
152 				strncpy(&expr_str[j], "(.*)", 4);
153 				info->year = pos++;
154 				i += 6;
155 				j += 4;
156 			} else if (strncmp(&aux_str[i], "<comment>", 9) == 0) {
157 				strncpy(&expr_str[j], "(.*)", 4);
158 				info->comment = pos++;
159 				i += 9;
160 				j += 4;
161 			} else if (strncmp(&aux_str[i], "<track>", 7) == 0) {
162 				strncpy(&expr_str[j], "([^ ]*)", 7);
163 				info->track = pos++;
164 				i += 7;
165 				j += 7;
166 			} else if (strncmp(&aux_str[i], "<genre>", 7) == 0) {
167 				strncpy(&expr_str[j], "(.*)", 4);
168 				info->genre = pos++;
169 				i += 7;
170 				j += 4;
171 			} else if (strncmp(&aux_str[i], "<\\*>", 4) == 0) {
172 				strncpy(&expr_str[j], ".*", 2);
173 				i += 4;
174 				j += 2;
175 			} else {
176 				expr_str[j++] = aux_str[i++];
177 			}
178 		} else {
179 			strcpy(&expr_str[j], &aux_str[i]);
180 			break;
181 		}
182 	}
183 
184 
185 	/* printf("regexp: %s\n", expr_str); */
186 
187 	/* compile the regexp from the expression string */
188 	res = regcomp(&info->re, expr_str, REG_EXTENDED | REG_ICASE);
189 	if (res != 0) {
190 		g_warning("error in regcomp (\"%s\"): %d", expr_str, res);
191 		free(aux_str);
192 		free(expr_str);
193 		free(info);
194 		return NULL;
195 	}
196 
197 	free(aux_str);
198 	free(expr_str);
199 
200 	return info;
201 }
202 
203 static void free_parse_info(parse_info *info)
204 {
205 	regfree(&info->re);
206 	free(info);
207 }
208 
209 
210 static void set_field(audio_file *af, int field, gchar *src, int len)
211 {
212 	chconv_tag_options options;
213 	char *temp;
214 	char *buf = malloc(len+1);
215 	str_safe_strncpy(buf, src, len+1);
216 
217 	/* apply character conversions */
218 	options = chconv_get_tag_options();
219 	if (options.space_conv != NULL && options.space_conv[0] != 0) {
220 		str_rtrim(buf);
221 
222 		temp = buf;
223 		buf = str_replace_char(buf, options.space_conv[0], ' ');
224 		free(temp);
225 	}
226 	if (options.case_conv != CASE_CONV_NONE) {
227 		temp = buf;
228 		buf = str_convert_case(buf, options.case_conv);
229 		free(temp);
230 	}
231 
232 	audio_file_set_field(af, field, buf);
233 	free(buf);
234 }
235 
236 static gboolean tag_from_filename(parse_info *pi, gchar *filename, audio_file *af)
237 {
238 	regmatch_t matches[8];
239 	char *filename_utf8;
240 	char *p;
241 	int res;
242 
243 	/* convert to UTF-8 */
244 	filename_utf8 = str_filename_to_utf8(filename, NULL);
245 	if (filename_utf8 == NULL)
246 		return FALSE;
247 
248 	/* truncate the file extension */
249 	p = g_utf8_strrchr(filename_utf8, -1, '.');
250 	if (p && strlen(p) <= 5)
251 		*p = 0;
252 
253 	/* printf("string: %s\n", filename_utf8); */
254 
255 
256 	/* match the regexp against the file name (minus extension) and
257 	   extract the tag fields */
258 	res = regexec(&pi->re, filename_utf8, 8, matches, 0);
259 	if (res != 0) {
260 		free(filename_utf8);
261 		return FALSE;
262 	}
263 
264 	if (pi->title > 0) {
265 		set_field(af, AF_TITLE, &filename_utf8[matches[pi->title].rm_so],
266 			  matches[pi->title].rm_eo - matches[pi->title].rm_so);
267 	}
268 	if (pi->artist > 0) {
269 		set_field(af, AF_ARTIST, &filename_utf8[matches[pi->artist].rm_so],
270 			  matches[pi->artist].rm_eo - matches[pi->artist].rm_so);
271 	}
272 	if (pi->album > 0) {
273 		set_field(af, AF_ALBUM, &filename_utf8[matches[pi->album].rm_so],
274 			  matches[pi->album].rm_eo - matches[pi->album].rm_so);
275 	}
276 	if (pi->year > 0) {
277 		set_field(af, AF_YEAR, &filename_utf8[matches[pi->year].rm_so],
278 			  matches[pi->year].rm_eo - matches[pi->year].rm_so);
279 	}
280 	if (pi->comment > 0) {
281 		set_field(af, AF_COMMENT, &filename_utf8[matches[pi->comment].rm_so],
282 			  matches[pi->comment].rm_eo - matches[pi->comment].rm_so);
283 	}
284 	if (pi->track > 0) {
285 		set_field(af, AF_TRACK, &filename_utf8[matches[pi->track].rm_so],
286 			  matches[pi->track].rm_eo - matches[pi->track].rm_so);
287 	}
288 	if (pi->genre > 0) {
289 		set_field(af, AF_GENRE, &filename_utf8[matches[pi->genre].rm_so],
290 			  matches[pi->genre].rm_eo - matches[pi->genre].rm_so);
291 	}
292 
293 	free(filename_utf8);
294 	return TRUE;
295 }
296 
297 
298 static void tag_files(GEList *file_list)
299 {
300 	GList *iter;
301 	audio_file *af;
302 	parse_info *pi;
303 	gint path_components;
304 	gchar *last_path;
305 	gchar *curr_path;
306 	gchar *file_name = NULL;
307 	gchar *file_name_utf8 = NULL;
308 	gchar *temp_utf8;
309 	gint track_auto_index;
310 	gint count_total, count_tagged;
311 	int save_errno, res;
312 
313 	gboolean write_title   = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_title));
314 	gboolean write_artist  = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_artist));
315 	gboolean write_album   = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_album));
316 	gboolean write_year    = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_year));
317 	gboolean write_comment = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_comment));
318 	gboolean write_track   = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_track));
319 	gboolean write_genre   = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_genre));
320 	gboolean track_auto    = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_track_auto));
321 
322 	pd_start(_("Tagging Files"));
323 	if (!(write_title || write_artist || write_album || write_year ||
324 	      write_comment || write_track || write_genre))
325 	{
326 		pd_printf(PD_ICON_INFO, _("No tag fields to set!"));
327 		pd_end();
328 		return;
329 	}
330 	pd_printf(PD_ICON_INFO, _("Starting in directory \"%s\""), fl_get_working_dir_utf8());
331 
332 	if (*use_filename) {
333 		const gchar *format = gtk_entry_get_text(ent_tag_format);
334 		pi = build_parse_info(format);
335 		path_components = fu_count_path_components(format);
336 	}
337 	else {
338 		pi = NULL;
339 		path_components = 0;
340 	}
341 
342 	count_total = 0;
343 	count_tagged = 0;
344 	track_auto_index = 1;
345 	last_path = "";
346 	for (iter = g_elist_first(file_list); iter; iter = g_list_next(iter)) {
347 		/* flush pending gtk operations so the UI doesn't freeze */
348 		pd_scroll_to_bottom();
349 		while (gtk_events_pending()) gtk_main_iteration();
350 		if (pd_stop_requested()) {
351 			pd_printf(PD_ICON_WARN, _("Operation stopped at user's request"));
352 			break;
353 		}
354 
355 		count_total++;
356 
357 		curr_path = (gchar *)iter->data;
358 
359 		if (!fu_compare_file_paths(last_path, curr_path)) {
360 			gchar *p;
361 			temp_utf8 = str_filename_to_utf8(curr_path, _("(UTF8 conversion error)"));
362 			p = g_utf8_strrchr(temp_utf8, -1, '/');
363 			pd_printf(PD_ICON_INFO, _("Entering directory \"%.*s\""), (gint)(p-temp_utf8), temp_utf8);
364 			free(temp_utf8);
365 
366 			track_auto_index = 1;	/* new dir, reset track auto-increment */
367 		}
368 		last_path = curr_path;
369 
370 		file_name = fu_last_n_path_components(curr_path, 1);
371 		file_name_utf8 = str_filename_to_utf8(file_name, _("(UTF8 conversion error)"));
372 
373 		res = audio_file_new(&af, curr_path, TRUE);
374 		if (res != AF_OK) {
375 			pd_printf(PD_ICON_FAIL, _("Error tagging \"%s\""), file_name_utf8);
376 
377 			if (res == AF_ERR_FILE)
378 				pd_printf(PD_ICON_NONE, _("Couldn't open file for writing"));
379 			else if (res == AF_ERR_FORMAT)
380 				pd_printf(PD_ICON_NONE, _("Audio format not recognized"));
381 			else
382 				pd_printf(PD_ICON_NONE, _("Unknown error (%d)"), res);
383 
384 			goto _continue;
385 		}
386 
387 		/* create the tag if not already present */
388 		audio_file_create_tag(af);
389 
390 		/* fill in the values from the form */
391 		if (write_title && !(from_fn_title && *use_filename))
392 			audio_file_set_field(af, AF_TITLE, gtk_entry_get_text(ent_title));
393 		if (write_artist && !(from_fn_artist && *use_filename))
394 			audio_file_set_field(af, AF_ARTIST, gtk_entry_get_text(ent_artist));
395 		if (write_album && !(from_fn_album && *use_filename))
396 			audio_file_set_field(af, AF_ALBUM, gtk_entry_get_text(ent_album));
397 		if (write_year && !(from_fn_year && *use_filename))
398 			audio_file_set_field(af, AF_YEAR, gtk_entry_get_text(ent_year));
399 		if (write_comment && !(from_fn_comment && *use_filename))
400 			audio_file_set_field(af, AF_COMMENT, gtk_entry_get_text(ent_comment));
401 		if (write_track && !(from_fn_track && *use_filename)) {
402 			if (track_auto) {
403 				char buf[10];
404 				snprintf(buf, 10, "%02u", track_auto_index++);
405 				audio_file_set_field(af, AF_TRACK, buf);
406 			} else
407 				audio_file_set_field(af, AF_TRACK, gtk_entry_get_text(ent_track));
408 		}
409 		if (write_genre && !(from_fn_genre && *use_filename))
410 			audio_file_set_field(af, AF_GENRE, gtk_entry_get_text(GTK_ENTRY(combo_genre->entry)));
411 
412 		/* fill in the values from the file name */
413 		if (pi != NULL) {
414 			gchar *full_path = fu_join_path(fl_get_working_dir(), curr_path);
415 			gchar *relevant_path = fu_last_n_path_components(full_path, path_components);
416 
417 			res = tag_from_filename(pi, relevant_path, af);
418 			free(full_path);
419 
420 			if (!res) {
421 				pd_printf(PD_ICON_FAIL, _("Error tagging \"%s\""), file_name_utf8);
422 				pd_printf(PD_ICON_NONE, _("File name does not match expected format"));
423 				goto _continue;
424 			}
425 		}
426 
427 		/* write new tag to file */
428 		res = audio_file_write_changes(af);
429 		if (res != AF_OK) {
430 			save_errno = errno;
431 			pd_printf(PD_ICON_FAIL, _("Error tagging \"%s\""), file_name_utf8);
432 
433 			if (res == AF_ERR_FILE)
434 				pd_printf(PD_ICON_NONE, "%s (%d)", strerror(save_errno), save_errno);
435 			else
436 				pd_printf(PD_ICON_NONE, _("Unknown error (%d)"), res);
437 
438 			goto _continue;
439 		}
440 
441 		pd_printf(PD_ICON_OK, _("Tagged \"%s\""), file_name_utf8);
442 		count_tagged++;
443 
444 	_continue:
445 		free(file_name_utf8);
446 		if (af) {
447 			audio_file_delete(af);
448 			af = NULL;
449 		}
450 	}
451 
452 	pd_printf(PD_ICON_INFO, _("Done (tagged %d of %d files)"), count_tagged, count_total);
453 	pd_end();
454 
455 	if (pi != NULL)
456 		free_parse_info(pi);
457 }
458 
459 
460 static void start_operation()
461 {
462 	GEList *file_list;
463 
464 	if (gtk_combo_box_get_active(combo_tag_apply) == APPLY_TO_ALL)
465 		file_list = fl_get_all_files();
466 	else
467 		file_list = fl_get_selected_files();
468 
469 	if (g_elist_length(file_list) == 0) {
470 		pd_start(NULL);
471 		pd_printf(PD_ICON_FAIL, _("No files selected"));
472 		pd_end();
473 
474 		g_elist_free(file_list);
475 		return;
476 	}
477 
478 	cursor_set_wait();
479 	gtk_widget_set_sensitive(GTK_WIDGET(b_tag_go), FALSE);
480 
481 	mru_add(format_mru, gtk_entry_get_text(ent_tag_format));
482 	gtk_combo_set_popdown_strings(combo_tag_format, GLIST(format_mru->list));
483 
484 	tag_files(file_list);
485 
486 	gtk_widget_set_sensitive(GTK_WIDGET(b_tag_go), TRUE);
487 	cursor_set_normal();
488 
489 	g_elist_free(file_list);
490 }
491 
492 
493 static void update_interface_fname()
494 {
495 	static gboolean last_title = FALSE;
496 	static gboolean last_artist = FALSE;
497 	static gboolean last_album = FALSE;
498 	static gboolean last_year = FALSE;
499 	static gboolean last_comment = FALSE;
500 	static gboolean last_track = FALSE;
501 	static gboolean last_genre = FALSE;
502 
503 	const gchar *format = gtk_entry_get_text(ent_tag_format);
504 	gboolean value;
505 	gboolean save;
506 
507 	/* inhibit proccessing signals in 'cb_filed_changed' */
508 	save = ignore_field_changed;
509 	ignore_field_changed = TRUE;
510 
511 	if (strstr(format, "<title>")) from_fn_title = TRUE;
512 	else from_fn_title = FALSE;
513 	if (strstr(format, "<artist>")) from_fn_artist = TRUE;
514 	else from_fn_artist = FALSE;
515 	if (strstr(format, "<album>")) from_fn_album = TRUE;
516 	else from_fn_album = FALSE;
517 	if (strstr(format, "<year>")) from_fn_year = TRUE;
518 	else from_fn_year = FALSE;
519 	if (strstr(format, "<comment>")) from_fn_comment = TRUE;
520 	else from_fn_comment = FALSE;
521 	if (strstr(format, "<track>")) from_fn_track = TRUE;
522 	else from_fn_track = FALSE;
523 	if (strstr(format, "<genre>")) from_fn_genre = TRUE;
524 	else from_fn_genre = FALSE;
525 
526 	value = *use_filename && from_fn_title;
527 	if (last_title != value) {
528 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_title), value);
529 		gtk_entry_set_text(ent_title, (value ? "<from filename>" : ""));
530 		gtk_widget_set_sensitive(GTK_WIDGET(cb_title), !value);
531 		gtk_widget_set_sensitive(GTK_WIDGET(ent_title), !value);
532 		last_title = value;
533 	}
534 	value = *use_filename && from_fn_artist;
535 	if (last_artist != value) {
536 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_artist), value);
537 		gtk_entry_set_text(ent_artist, (value ? "<from filename>" : ""));
538 		gtk_widget_set_sensitive(GTK_WIDGET(cb_artist), !value);
539 		gtk_widget_set_sensitive(GTK_WIDGET(ent_artist), !value);
540 		last_artist = value;
541 	}
542 	value = *use_filename && from_fn_album;
543 	if (last_album != value) {
544 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_album), value);
545 		gtk_entry_set_text(ent_album, (value ? "<from filename>" : ""));
546 		gtk_widget_set_sensitive(GTK_WIDGET(cb_album), !value);
547 		gtk_widget_set_sensitive(GTK_WIDGET(ent_album), !value);
548 		last_album = value;
549 	}
550 	value = *use_filename && from_fn_year;
551 	if (last_year != value) {
552 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_year), value);
553 		gtk_entry_set_text(ent_year, (value ? "<fn>" : ""));
554 		gtk_widget_set_sensitive(GTK_WIDGET(cb_year), !value);
555 		gtk_widget_set_sensitive(GTK_WIDGET(ent_year), !value);
556 		last_year = value;
557 	}
558 	value = *use_filename && from_fn_comment;
559 	if (last_comment != value) {
560 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_comment), value);
561 		gtk_entry_set_text(ent_comment, (value ? "<from filename>" : ""));
562 		gtk_widget_set_sensitive(GTK_WIDGET(cb_comment), !value);
563 		gtk_widget_set_sensitive(GTK_WIDGET(ent_comment), !value);
564 		last_comment = value;
565 	}
566 	value = *use_filename && from_fn_track;
567 	if (last_track != value) {
568 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_track), value);
569 		gtk_entry_set_text(ent_track, (value ? "<fn>" : ""));
570 		gtk_widget_set_sensitive(GTK_WIDGET(cb_track), !value);
571 		gtk_widget_set_sensitive(GTK_WIDGET(ent_track), !value);
572 		if (value)
573 			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_track_auto), FALSE);
574 		gtk_widget_set_sensitive(GTK_WIDGET(cb_track_auto), !value);
575 		last_track = value;
576 	}
577 	value = *use_filename && from_fn_genre;
578 	if (last_genre != value) {
579 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_genre), value);
580 		gtk_entry_set_text(ent_genre, (value ? "<from filename>" : ""));
581 		gtk_widget_set_sensitive(GTK_WIDGET(cb_genre), !value);
582 		gtk_widget_set_sensitive(GTK_WIDGET(combo_genre), !value);
583 		last_genre = value;
584 	}
585 
586 	ignore_field_changed = save;
587 }
588 
589 
590 /*** UI callbacks ***********************************************************/
591 
592 void cb_tag_go(GtkButton *button, gpointer user_data)
593 {
594 	start_operation();
595 }
596 
597 void cb_update_filename(GObject *obj, gpointer data)
598 {
599 	*use_filename = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_use_filename));
600 	update_interface_fname();
601 }
602 
603 void cb_field_changed(GObject *obj, gpointer data)
604 {
605 	if (ignore_field_changed)
606 		return;
607 
608 	if ((void*)obj == (void*)ent_title)
609 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_title), TRUE);
610 	else if ((void*)obj == (void*)ent_artist)
611 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_artist), TRUE);
612 	else if ((void*)obj == (void*)ent_album)
613 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_album), TRUE);
614 	else if ((void*)obj == (void*)ent_year)
615 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_year), TRUE);
616 	else if ((void*)obj == (void*)ent_genre)
617 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_genre), TRUE);
618 	else if ((void*)obj == (void*)ent_comment)
619 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_comment), TRUE);
620 	else if ((void*)obj == (void*)ent_track || (void*)obj == (void*)cb_track_auto)
621 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_track), TRUE);
622 }
623 
624 void cb_check_changed(GtkToggleButton *widget, gpointer data)
625 {
626 	gboolean save;
627 
628 	if (gtk_toggle_button_get_active(widget))
629 		return;
630 
631 	/* inhibit proccessing signals in 'cb_field_changed' */
632 	save = ignore_field_changed;
633 	ignore_field_changed = TRUE;
634 
635 	if ((void*)widget == (void*)cb_title)
636 		gtk_entry_set_text(ent_title, "");
637 	else if ((void*)widget == (void*)cb_artist)
638 		gtk_entry_set_text(ent_artist, "");
639 	else if ((void*)widget == (void*)cb_album)
640 		gtk_entry_set_text(ent_album, "");
641 	else if ((void*)widget == (void*)cb_year)
642 		gtk_entry_set_text(ent_year, "");
643 	else if ((void*)widget == (void*)cb_genre)
644 		gtk_entry_set_text(ent_genre, "");
645 	else if ((void*)widget == (void*)cb_comment)
646 		gtk_entry_set_text(ent_comment, "");
647 	else if ((void*)widget == (void*)cb_track) {
648 		gtk_entry_set_text(ent_track, "");
649 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_track_auto), FALSE);
650 	}
651 
652 	ignore_field_changed = save;
653 }
654 
655 void cb_show_tag_chconv(GtkButton *button, gpointer user_data)
656 {
657 	chconv_display(CHCONV_TAG);
658 }
659 
660 void cb_tag_help(GtkButton *button, gpointer user_data)
661 {
662 	help_display(HELP_TAG_FORMAT);
663 }
664 
665 static void cb_file_selection_changed(GtkTreeSelection *selection, gpointer data)
666 {
667 	if (fl_count_selected() > 0)
668 		gtk_combo_box_set_active(combo_tag_apply, APPLY_TO_SELECTED);
669 	else
670 		gtk_combo_box_set_active(combo_tag_apply, APPLY_TO_ALL);
671 }
672 
673 
674 /*** public functions *******************************************************/
675 
676 void tt_init(GladeXML *xml)
677 {
678 	GtkStyle *style;
679 	GtkWidget *w;
680 	GEList *genre_list;
681 	GEList *format_list;
682 
683 	/*
684 	 * get the widgets from glade
685 	 */
686 
687 	combo_tag_format = GTK_COMBO(glade_xml_get_widget(xml, "combo_tag_format"));
688 	ent_tag_format = GTK_ENTRY(glade_xml_get_widget(xml, "ent_tag_format"));
689 	b_tag_edit_format = GTK_BUTTON(glade_xml_get_widget(xml, "b_tag_edit_format"));
690 	cb_use_filename = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_use_filename"));
691 	b_tag_go = GTK_BUTTON(glade_xml_get_widget(xml, "b_tag_go"));
692 	combo_tag_apply = GTK_COMBO_BOX(glade_xml_get_widget(xml, "combo_tag_apply"));
693 
694 	cb_title = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_title"));
695 	cb_artist = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_artist"));
696 	cb_album = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_album"));
697 	cb_year = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_year"));
698 	cb_comment = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_comment"));
699 	cb_track = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_track"));
700 	cb_genre = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_genre"));
701 
702 	ent_title = GTK_ENTRY(glade_xml_get_widget(xml, "ent_title2"));
703 	ent_artist = GTK_ENTRY(glade_xml_get_widget(xml, "ent_artist2"));
704 	ent_album = GTK_ENTRY(glade_xml_get_widget(xml, "ent_album2"));
705 	ent_year = GTK_ENTRY(glade_xml_get_widget(xml, "ent_year2"));
706 	ent_comment = GTK_ENTRY(glade_xml_get_widget(xml, "ent_comment2"));
707 	ent_track = GTK_ENTRY(glade_xml_get_widget(xml, "ent_track2"));
708 	cb_track_auto = GTK_CHECK_BUTTON(glade_xml_get_widget(xml, "cb_track_auto"));
709 	combo_genre = GTK_COMBO(glade_xml_get_widget(xml, "combo_genre2"));
710 	ent_genre = GTK_ENTRY(glade_xml_get_widget(xml, "ent_genre2"));
711 
712 	w_main = GTK_WINDOW(glade_xml_get_widget(xml, "w_main"));
713 
714 	/* initialize some widgets' state */
715 	gtk_combo_box_set_active(combo_tag_apply, APPLY_TO_ALL);
716 
717 	genre_list = genre_create_list(TRUE);
718 	g_elist_prepend(genre_list, "");
719 	gtk_combo_set_popdown_strings(combo_genre, GLIST(genre_list));
720 	g_elist_free(genre_list);
721 
722 	/* connect signals */
723 	g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(glade_xml_get_widget(xml, "tv_files"))),
724 			 "changed", G_CALLBACK(cb_file_selection_changed), NULL);
725 
726 
727 	/*
728 	 * set the title colors
729 	 */
730 
731 	w = glade_xml_get_widget(xml, "lab_tag_title");
732 	gtk_widget_ensure_style(w);
733 	style = gtk_widget_get_style(w);
734 
735 	gtk_widget_modify_fg(w, GTK_STATE_NORMAL, &style->text[GTK_STATE_SELECTED]);
736 
737 	w = glade_xml_get_widget(xml, "box_tag_title");
738 	gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &style->base[GTK_STATE_SELECTED]);
739 
740 
741 	/*
742 	 * get the preference values, or set them to defaults
743 	 */
744 
745 	/* use_filename */
746 	use_filename = pref_get_ref("tt:use_filename");
747 	if (!use_filename) {
748 		gboolean temp = TRUE;
749 		use_filename = pref_set("tt:use_filename", PREF_BOOLEAN, &temp);
750 	}
751 
752 	/* format_mru */
753 	format_list = pref_get_ref("tt:format_mru");
754 	if (!format_list) {
755 		GEList *temp_list = g_elist_new();
756 		g_elist_append(temp_list, "<track>. <title>");
757 		g_elist_append(temp_list, "<artist> - <title>");
758 		g_elist_append(temp_list, "<artist> - <album>/<track>. <title>");
759 		g_elist_append(temp_list, "<artist>/<album>/<track>. <title>");
760 		g_elist_append(temp_list, "<artist>/<album> (<year>)/<track>. <title>");
761 		format_list = pref_set("tt:format_mru", PREF_STRING | PREF_LIST, temp_list);
762 		g_elist_free(temp_list);
763 	}
764 	format_mru = mru_new_from_list(10, format_list);
765 
766 
767 	/*
768 	 * synchronize the interface state
769 	 */
770 
771 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_use_filename), *use_filename);
772 	gtk_combo_set_popdown_strings(combo_tag_format, GLIST(format_mru->list));
773 
774 	update_interface_fname();
775 }
776 
777