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