1 /* wavbreaker - A tool to split a wave file up into multiple waves.
2  * Copyright (C) 2002-2004 Timothy Robinson
3  *
4  * This file copyright (c) 2007 Thomas Perl
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 
21 #include <gtk/gtk.h>
22 
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <libgen.h>
27 
28 #include "sample.h"
29 #include "wavbreaker.h"
30 #include "popupmessage.h"
31 #include "wav.h"
32 
33 #include "gettext.h"
34 
35 enum {
36     COLUMN_FILENAME,
37     COLUMN_BASENAME,
38     COLUMN_LENGTH,
39     COLUMN_LENGTH_STRING,
40     NUM_COLUMNS
41 };
42 
43 static GtkWidget *window;
44 static GtkWidget *ok_button;
45 static GtkWidget *remove_button;
46 
47 static GtkTreeView *treeview = NULL;
48 static GtkListStore *store = NULL;
49 
50 static char folder[4096] = {0};
51 
52 static SampleInfo common_sample_info;
53 static WriteInfo write_info;
54 
55 gboolean file_merge_progress_idle_func(gpointer data);
56 
57 int get_merge_files_count()
58 {
59     GtkTreeIter iter;
60     int i = 0;
61 
62     if( gtk_tree_model_get_iter_first( GTK_TREE_MODEL(store), &iter) == TRUE) {
63         do {
64             i++;
65         } while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &iter) == TRUE);
66     }
67 
68     /* Only enable when we have more than one file to merge */
69     gtk_widget_set_sensitive( GTK_WIDGET( ok_button), (i>1)?TRUE:FALSE);
70     gtk_widget_set_sensitive( GTK_WIDGET( remove_button), (i>0)?TRUE:FALSE);
71 
72     return i;
73 }
74 
75 static void ok_button_clicked(GtkWidget *widget, gpointer user_data)
76 {
77     GtkWidget *dialog;
78     GValue value;
79     GtkTreeIter iter;
80     GList *filenames = NULL;
81     char *tmp;
82     GtkWidget *checkbutton;
83 
84     GtkFileFilter *filter_all;
85     GtkFileFilter *filter_supported;
86 
87     gtk_tree_model_get_iter_first( GTK_TREE_MODEL(store), &iter);
88     do {
89         memset (&value, 0, sizeof (GValue));
90         gtk_tree_model_get_value( GTK_TREE_MODEL(store), &iter, 0, &value);
91         tmp = (char*)g_value_peek_pointer( &value);
92         filenames = g_list_append(filenames, g_strdup(tmp));
93     } while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &iter) == TRUE);
94 
95     filter_all = gtk_file_filter_new();
96     gtk_file_filter_set_name( filter_all, _("All files"));
97     gtk_file_filter_add_pattern( filter_all, "*");
98 
99     filter_supported = gtk_file_filter_new();
100     gtk_file_filter_set_name( filter_supported, _("Supported files"));
101     gtk_file_filter_add_pattern( filter_supported, "*.wav");
102 
103     dialog = gtk_file_chooser_dialog_new( _("Select filename for merged wave file"),
104                                           GTK_WINDOW(window),
105                                           GTK_FILE_CHOOSER_ACTION_SAVE,
106                                           _("_Cancel"), GTK_RESPONSE_CANCEL,
107                                           _("_Save"), GTK_RESPONSE_ACCEPT,
108                                           NULL);
109 
110     gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter_all);
111     gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter_supported);
112     gtk_file_chooser_set_filter( GTK_FILE_CHOOSER(dialog), filter_supported);
113 
114     gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE);
115 
116     checkbutton = (GtkWidget*)gtk_check_button_new_with_label( _("Open file in wavbreaker after merge"));
117     gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), checkbutton, FALSE, FALSE, 0);
118     gtk_widget_show( GTK_WIDGET(checkbutton));
119 
120     if( strlen( folder) > 0) {
121         gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), folder);
122     }
123 
124     gtk_file_chooser_set_current_name( GTK_FILE_CHOOSER(dialog), "merged.wav");
125 
126     if( gtk_dialog_run( GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
127         tmp = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog));
128         write_info.pct_done = 0.0;
129         sample_merge_files( tmp, filenames, &write_info);
130         gtk_widget_destroy(GTK_WIDGET(user_data));
131         g_idle_add( file_merge_progress_idle_func, NULL);
132     }
133 
134     gtk_widget_destroy( GTK_WIDGET(dialog));
135 }
136 
137 static void add_filename( char* filename)
138 {
139     GtkTreeIter iter;
140     SampleInfo sampleinfo;
141     unsigned int length = 0;
142     char* length_str;
143     int files = get_merge_files_count();
144 
145     wav_read_header( filename, &sampleinfo, 0);
146 
147     if( files == 0) {
148         /* Adding first file, saving sample info for later comparison */
149         memcpy( &common_sample_info, &sampleinfo, sizeof(SampleInfo));
150     } else {
151         /* Compare the format info of the first file with the current info */
152         if( common_sample_info.channels != sampleinfo.channels ||
153             common_sample_info.samplesPerSec != sampleinfo.samplesPerSec ||
154             common_sample_info.avgBytesPerSec != sampleinfo.avgBytesPerSec ||
155             common_sample_info.blockAlign != sampleinfo.blockAlign ||
156             common_sample_info.bitsPerSample != sampleinfo.bitsPerSample ||
157             sampleinfo.channels == 0 ||
158             sampleinfo.samplesPerSec == 0 ||
159             sampleinfo.bitsPerSample < 8) {
160             popupmessage_show( window, _("Wrong file format - skipping file"), filename);
161             return;
162         }
163     }
164 
165     length = sampleinfo.numBytes / (sampleinfo.channels*sampleinfo.samplesPerSec*sampleinfo.bitsPerSample/8);
166     length_str = (char*)malloc( 20);
167     sprintf( length_str, "%02d:%02d", length/60, length%60);
168 
169     gtk_list_store_append( store, &iter);
170 
171     gtk_list_store_set( store, &iter, COLUMN_FILENAME, filename,
172                                       COLUMN_BASENAME, basename(filename),
173                                       COLUMN_LENGTH, length,
174                                       COLUMN_LENGTH_STRING, length_str,
175                                       -1);
176 }
177 
178 static void add_button_clicked( GtkWidget *widget, gpointer user_data)
179 {
180     GtkWidget *dialog;
181 
182     GtkFileFilter *filter_all;
183     GtkFileFilter *filter_supported;
184 
185     int i;
186 
187     filter_all = gtk_file_filter_new();
188     gtk_file_filter_set_name( filter_all, _("All files"));
189     gtk_file_filter_add_pattern( filter_all, "*");
190 
191     filter_supported = gtk_file_filter_new();
192     gtk_file_filter_set_name( filter_supported, _("Supported files"));
193     gtk_file_filter_add_pattern( filter_supported, "*.wav");
194 
195     dialog = gtk_file_chooser_dialog_new(_("Add wave file to merge"), GTK_WINDOW(window),
196         GTK_FILE_CHOOSER_ACTION_OPEN,
197         _("_Cancel"), GTK_RESPONSE_CANCEL,
198         _("_Open"), GTK_RESPONSE_ACCEPT,
199         NULL);
200 
201     gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter_all);
202     gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter_supported);
203     gtk_file_chooser_set_filter( GTK_FILE_CHOOSER(dialog), filter_supported);
204 
205     gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), TRUE);
206 
207     if( strlen( folder) > 0) {
208         gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), folder);
209     }
210 
211     if (gtk_dialog_run( GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
212         const char *current_folder = gtk_file_chooser_get_current_folder( GTK_FILE_CHOOSER(dialog));
213         if (current_folder) {
214             strcpy(folder, current_folder);
215         }
216 
217         GSList* filenames;
218         filenames = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog));
219 
220         for( i=0; i<g_slist_length( filenames); i++) {
221             add_filename( (char*)g_slist_nth_data( filenames, i));
222             g_free( g_slist_nth_data( filenames, i));
223         }
224 
225         g_slist_free( filenames);
226     }
227 
228     gtk_widget_destroy(dialog);
229     get_merge_files_count();
230 }
231 
232 void remove_selected_row( gpointer data, gpointer user_data)
233 {
234     GtkTreePath *path = (GtkTreePath*)data;
235     GtkTreeIter iter;
236 
237     gtk_tree_model_get_iter( GTK_TREE_MODEL(store), &iter, path);
238     gtk_list_store_remove( store, &iter);
239     gtk_tree_path_free( path);
240 }
241 
242 static void remove_button_clicked( GtkWidget *widget, gpointer user_data)
243 {
244     GtkTreeSelection *selection;
245     GtkTreeModel *model;
246     GList *list;
247 
248     selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(treeview));
249     list = gtk_tree_selection_get_selected_rows( selection, &model);
250     list = g_list_reverse( list);
251     g_list_foreach( list, remove_selected_row, NULL);
252     g_list_free( list);
253     get_merge_files_count();
254 }
255 
256 void guimerge_show( GtkWidget *main_window)
257 {
258     GtkWidget *vbox;
259     GtkWidget *hbox;
260     GtkWidget *vbbox;
261     GtkWidget *button;
262 
263     GtkTreeSelection *selection;
264     GtkTreeViewColumn *column;
265     GtkCellRenderer *renderer;
266     GtkWidget *sw;
267 
268     window = gtk_window_new( GTK_WINDOW_TOPLEVEL);
269 
270     GtkWidget *header_bar = gtk_header_bar_new();
271     gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(header_bar), TRUE);
272     gtk_header_bar_set_title(GTK_HEADER_BAR(header_bar), _("Merge wave files"));
273     gtk_window_set_titlebar(GTK_WINDOW(window), header_bar);
274 
275     gtk_widget_realize( window);
276     gtk_window_set_modal( GTK_WINDOW(window), TRUE);
277     gtk_window_set_transient_for( GTK_WINDOW(window), GTK_WINDOW(main_window));
278     gtk_window_set_type_hint( GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
279     gtk_window_set_position( GTK_WINDOW(window), GTK_WIN_POS_CENTER_ON_PARENT);
280 
281     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
282     gtk_container_add(GTK_CONTAINER(window), vbox);
283     gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
284     gtk_box_set_spacing( GTK_BOX(vbox), 6);
285 
286     hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
287     gtk_box_set_spacing( GTK_BOX(hbox), 6);
288     gtk_box_pack_start( GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
289 
290     if (!store) {
291         store = gtk_list_store_new( NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING);
292     }
293     gtk_list_store_clear(store);
294 
295     /* create the scrolled window for the list */
296     sw = gtk_scrolled_window_new( NULL, NULL);
297     gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
298     gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
299 
300     treeview = GTK_TREE_VIEW( gtk_tree_view_new_with_model( GTK_TREE_MODEL(store)));
301     gtk_container_add( GTK_CONTAINER(sw), GTK_WIDGET(treeview));
302 
303     selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(treeview));
304     gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE);
305 
306     /* Basename Column */
307     column = gtk_tree_view_column_new();
308     renderer = gtk_cell_renderer_text_new();
309     gtk_tree_view_column_set_title( column, _("File Name"));
310     gtk_tree_view_column_set_expand(column, TRUE);
311     gtk_tree_view_column_pack_start( column, renderer, TRUE);
312     gtk_tree_view_column_add_attribute(column, renderer, "text", COLUMN_BASENAME);
313     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
314     gtk_tree_view_column_set_resizable(column, TRUE);
315     gtk_tree_view_append_column( GTK_TREE_VIEW(treeview), column);
316 
317     /* Length Column */
318     column = gtk_tree_view_column_new();
319     renderer = gtk_cell_renderer_text_new();
320     gtk_tree_view_column_set_title( column, _("Length"));
321     gtk_tree_view_column_pack_start( column, renderer, TRUE);
322     gtk_tree_view_column_add_attribute(column, renderer, "text", COLUMN_LENGTH_STRING);
323     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
324     gtk_tree_view_column_set_resizable(column, TRUE);
325     gtk_tree_view_append_column( GTK_TREE_VIEW(treeview), column);
326 
327     gtk_box_pack_start( GTK_BOX(hbox), sw, TRUE, TRUE, 0);
328 
329     vbbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
330     gtk_box_pack_start( GTK_BOX(hbox), vbbox, FALSE, FALSE, 0);
331     gtk_box_set_spacing(GTK_BOX(vbbox), 5);
332 
333     button = gtk_button_new_with_mnemonic(_("_Add"));
334     gtk_box_pack_start( GTK_BOX(vbbox), button, FALSE, FALSE, 0);
335     g_signal_connect( G_OBJECT(button), "clicked", (GCallback)add_button_clicked, window);
336 
337     remove_button = gtk_button_new_with_mnemonic(_("_Remove"));
338     gtk_box_pack_start( GTK_BOX(vbbox), remove_button, FALSE, FALSE, 0);
339     g_signal_connect( G_OBJECT(remove_button), "clicked", (GCallback)remove_button_clicked, window);
340     gtk_widget_set_sensitive( GTK_WIDGET(remove_button), FALSE);
341 
342     gtk_box_pack_start(GTK_BOX(vbbox), gtk_label_new(""), TRUE, TRUE, 0);
343 
344     ok_button = gtk_button_new_with_label(_("Merge"));
345     g_signal_connect(G_OBJECT(ok_button), "clicked", (GCallback)ok_button_clicked, window);
346     gtk_box_pack_start( GTK_BOX(vbbox), ok_button, FALSE, FALSE, 0);
347     gtk_widget_set_sensitive( GTK_WIDGET(ok_button), FALSE);
348 
349     gtk_window_resize( GTK_WINDOW(window), 500, 300);
350     gtk_widget_show_all(window);
351 }
352 
353 gboolean file_merge_progress_idle_func(gpointer data) {
354     static GtkWidget *window;
355     static GtkWidget *pbar;
356     static GtkWidget *vbox;
357     static GtkWidget *label;
358     static GtkWidget *status_label;
359     static int cur_file_displayed = 0;
360     static double fraction;
361 
362     if (window == NULL) {
363         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
364         gtk_widget_realize(window);
365         gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
366         gtk_window_set_modal(GTK_WINDOW(window), TRUE);
367         gtk_window_set_transient_for(GTK_WINDOW(window),
368                 GTK_WINDOW(wavbreaker_get_main_window()));
369         gtk_window_set_type_hint(GTK_WINDOW(window),
370                 GDK_WINDOW_TYPE_HINT_DIALOG);
371         gtk_window_set_position(GTK_WINDOW(window),
372                 GTK_WIN_POS_CENTER_ON_PARENT);
373         gdk_window_set_functions(gtk_widget_get_window(window), GDK_FUNC_MOVE);
374 
375         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
376         gtk_container_add(GTK_CONTAINER(window), vbox);
377         gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
378 
379         gtk_window_set_title( GTK_WINDOW(window), _("Merging wave files"));
380 
381         gchar *markup = g_markup_printf_escaped("<span size=\"larger\" weight=\"bold\">%s</span>",
382                 gtk_window_get_title(GTK_WINDOW(window)));
383         label = gtk_label_new(NULL);
384         gtk_label_set_markup(GTK_LABEL(label), markup);
385         g_free(markup);
386         g_object_set(G_OBJECT(label), "xalign", 0.0f, "yalign", 0.5f, NULL);
387         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 5);
388 
389         label = gtk_label_new( _("The selected files are now being merged. This can take some time."));
390         gtk_label_set_line_wrap( GTK_LABEL(label), TRUE);
391         g_object_set(G_OBJECT(label), "xalign", 0.0f, "yalign", 0.5f, NULL);
392         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 5);
393 
394         pbar = gtk_progress_bar_new();
395         gtk_box_pack_start(GTK_BOX(vbox), pbar, FALSE, TRUE, 5);
396 
397         status_label = gtk_label_new( NULL);
398         g_object_set(G_OBJECT(label), "xalign", 0.0f, "yalign", 0.5f, NULL);
399         gtk_label_set_ellipsize( GTK_LABEL(status_label), PANGO_ELLIPSIZE_MIDDLE);
400         gtk_box_pack_start(GTK_BOX(vbox), status_label, FALSE, TRUE, 5);
401 
402         gtk_widget_show_all(GTK_WIDGET(window));
403         cur_file_displayed = -1;
404     }
405 
406     if (write_info.sync) {
407         write_info.sync = 0;
408         gtk_widget_destroy(window);
409         window = NULL;
410 
411         popupmessage_show(NULL, _("Operation successful"), _("The files have been merged."));
412 
413         return FALSE;
414     }
415 
416     if (cur_file_displayed != write_info.cur_file) {
417         gchar *bn = g_path_get_basename(write_info.cur_filename);
418         gchar *tmp = g_strdup_printf(_("Adding %s"), bn);
419         g_free(bn);
420         gchar *msg = g_markup_printf_escaped("<i>%s</i>", tmp);
421         g_free(tmp);
422         gtk_label_set_markup(GTK_LABEL(status_label), msg);
423         g_free(msg);
424 
425         cur_file_displayed = write_info.cur_file;
426     }
427 
428     fraction = 1.00*(write_info.cur_file-1+write_info.pct_done)/write_info.num_files;
429     gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pbar), fraction);
430 
431     gchar *msg;
432     if (write_info.num_files > 1) {
433         // TODO: i18n plural forms
434         msg = g_strdup_printf(_("%d of %d files merged"), write_info.cur_file-1, write_info.num_files);
435     } else {
436         msg = g_strdup_printf(_("%d of 1 file merged"), write_info.cur_file-1);
437     }
438     gtk_progress_bar_set_text( GTK_PROGRESS_BAR(pbar), msg);
439     g_free(msg);
440 
441     return TRUE;
442 }
443 
444