/* * Copyright (C) 2004-2005 Vadim Berezniker * http://www.kryptolus.com * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include "stdafx.h" #include "common.h" #include "sabbu.h" #include "kryArray.h" #include "kryString.h" #include "krySSACommandParser.h" #include "krySSACommandHighlighter.h" #include "gui_status_bar.h" #include "gui_text_editor.h" #include "gui_time_entry.h" #include "gui_main_event.h" #include "gui_waveforms.h" #include "gui_event_list.h" #include "sound.h" #include "sound_out.h" #include "gui_peakfile.h" extern struct sabbu app; GMutex *audio_play_status_mutex = NULL; /* Moves iterator N rows before/after selection (Doesn't affect list cursor). If offset is positive, the cursor is moved N rows after last selected rows. If offset is negative the cursor is moved N rows before the first selected row. */ gboolean gui_main_event_seek(int offset, GtkTreeIter *iter, gboolean can_select_blank) { GtkTreeModel *model; GtkTreeSelection *selection = gui_event_list_get_selection(app.ui.event_list); GList *sel_items_orig = gtk_tree_selection_get_selected_rows(selection, &model); GList *sel_items = sel_items_orig; GtkTreePath *path; kryEvent *line; gboolean skip_comment_lines = TRUE; if(!sel_items) return FALSE; /* Find the last path in the selection */ if(offset > 0) { while(sel_items && sel_items->next) sel_items = sel_items -> next; } path = (GtkTreePath *) sel_items->data; gtk_tree_model_get_iter(model, iter, path); gtk_tree_model_get(model, iter, PTR_COLUMN, &line, -1); if(line->GetType() == kryEvent::EVENT_COMMENT_DIALOG) skip_comment_lines = FALSE; while(offset > 0) { gtk_tree_path_next(path); if(!gtk_tree_model_get_iter(model, iter, path)) { g_list_foreach(sel_items, (GFunc)gtk_tree_path_free, NULL); g_list_free(sel_items); return FALSE; } gtk_tree_model_get(model, iter, PTR_COLUMN, &line, -1); if((!skip_comment_lines || line->GetType() != kryEvent::EVENT_COMMENT_DIALOG) && line->GetType() != kryEvent::EVENT_COMMENT_STRING && line->GetType() != kryEvent::EVENT_COMMENT_BLANK) { offset--; } } while(offset < 0) { if(!gtk_tree_path_prev(path)) { g_list_foreach(sel_items_orig, (GFunc) gtk_tree_path_free, NULL); g_list_free(sel_items_orig); return FALSE; } gtk_tree_model_get_iter(model, iter, path); gtk_tree_model_get(model, iter, PTR_COLUMN, &line, -1); if(line->GetType() != kryEvent::EVENT_COMMENT_DIALOG && line->GetType() != kryEvent::EVENT_COMMENT_STRING && line->GetType() != kryEvent::EVENT_COMMENT_BLANK) offset++; } if(!gtk_tree_model_get_iter(model, iter, path)) { g_list_foreach(sel_items_orig, (GFunc)gtk_tree_path_free, NULL); g_list_free(sel_items_orig); return FALSE; } g_list_foreach(sel_items_orig, (GFunc) gtk_tree_path_free, NULL); g_list_free(sel_items_orig); gtk_tree_model_get(model, iter, PTR_COLUMN, &line, -1); if(can_select_blank == FALSE && line->GetType() == kryEvent::EVENT_BLANK) return FALSE; return TRUE; } gboolean gui_main_event_select(int offset, GtkTreeIter *iter, gboolean can_select_blank, gboolean lock_selection) { GtkTreeSelection *selection = gui_event_list_get_selection(app.ui.event_list); GtkTreePath *path; if(!gui_main_event_seek(offset, iter, can_select_blank)) return FALSE; if(offset != 0 && !lock_selection) gtk_tree_selection_unselect_all(selection); path = gtk_tree_model_get_path(GTK_TREE_MODEL(gui_event_list_get_store(app.ui.event_list)), iter); gui_event_list_scroll_if_necessary(app.ui.event_list, path, offset < 0); if(offset != 0 && !lock_selection) gtk_tree_selection_select_iter(selection, iter); gtk_tree_path_free(path); return TRUE; } /* * Plays the sound associated with the event. * Offset is the relative row number to the currently selected row. * * If offset is 0 and multiple rows are selected, the interval between * the earliest time and the latest time is played. * * The start and time markers are reset to the values stored in the event. * The time interval where the event is located is focused on the waveforms. */ void gui_main_play_row(int offset) { kryEvent *event; GtkTreeIter iter; KryMarker *marker_start, *marker_end; GtkTreeSelection *selection; GtkTreeModel *model; GList *selitems; kryEvent *start_event = NULL, *end_event = NULL; if(!gui_main_event_select(offset, &iter, FALSE, FALSE)) return; selection = gui_event_list_get_selection(app.ui.event_list); selitems = gtk_tree_selection_get_selected_rows(selection, &model); gui_main_audio_play_mode(SOUND_PLAYING_OTHER); for(GList *ptr = selitems; ptr; ptr = ptr->next) { GtkTreePath *path = (GtkTreePath *) ptr->data; gtk_tree_model_get_iter(model, &iter, path); gtk_tree_model_get(model, &iter, PTR_COLUMN, &event, -1); if(!event || event->GetType() == kryEvent::EVENT_BLANK || event->GetType() == kryEvent::EVENT_COMMENT_STRING || event->GetType() == kryEvent::EVENT_COMMENT_BLANK || event->GetStart() == 0 && event->GetEnd() == 0) { continue; } if(!start_event || event->GetStart() < start_event->GetStart()) start_event = event; if(!end_event || event->GetEnd() > end_event->GetEnd()) end_event = event; } g_list_foreach(selitems, (GFunc) gtk_tree_path_free, NULL); g_list_free(selitems); if(!start_event) return; kry_marker_set_value(app.ui.marker_active_start, start_event->GetStart()); kry_marker_set_value(app.ui.marker_active_end, end_event->GetEnd()); gui_main_set_time_modified(FALSE); if(!app.ui.sound_info) return; kry_waveform_group_focus_selected(app.ui.waveform_group); marker_start = KRY_MARKER(kry_marker_copy(app.ui.marker_active_start)); marker_end = KRY_MARKER(kry_marker_copy(app.ui.marker_active_end)); gui_main_play_time_range(marker_start, marker_end); g_object_unref(G_OBJECT(marker_start)); g_object_unref(G_OBJECT(marker_end)); } /* * Callback for the 'Audio -> Play Previous 0.25 seconds' menu item. */ void gui_main_menu_audio_play_previous(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { KryMarker *marker_start; long start; if(kry_marker_get_value(app.ui.marker_active_start) == -1) return; gui_main_audio_play_mode(SOUND_PLAYING_OTHER); start = kry_marker_get_value(app.ui.marker_active_start) - 250; if(start < 0) start = 0; marker_start = KRY_MARKER(kry_marker_copy(app.ui.marker_active_start)); kry_marker_set_value(marker_start, start); gui_main_play_time_range(marker_start, app.ui.marker_active_start); g_object_unref(G_OBJECT(marker_start)); } /* * Callback for the 'Audio -> Play Next 0.5 seconds' menu item. */ void gui_main_menu_audio_play_next(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { KryMarker *marker_end; long end; if(kry_marker_get_value(app.ui.marker_active_end) == -1) return; gui_main_audio_play_mode(SOUND_PLAYING_OTHER); end = kry_marker_get_value(app.ui.marker_active_end) + 500; marker_end = KRY_MARKER(kry_marker_copy(app.ui.marker_active_end)); kry_marker_set_value(marker_end, end); gui_main_play_time_range(app.ui.marker_active_end, marker_end); g_object_unref(G_OBJECT(marker_end)); } /* * Callback for the 'Audio -> Play Last 0.5 seconds' menu item. */ void gui_main_menu_audio_play_last(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { KryMarker *marker_start; long start; if(kry_marker_get_value(app.ui.marker_active_end) == -1) return; gui_main_audio_play_mode(SOUND_PLAYING_OTHER); start = kry_marker_get_value(app.ui.marker_active_end) - 500; if(start < 0) start = 0; marker_start = KRY_MARKER(kry_marker_copy(app.ui.marker_active_end)); kry_marker_set_value(marker_start, start); gui_main_play_time_range(marker_start, app.ui.marker_active_end); g_object_unref(G_OBJECT(marker_start)); gui_main_focus_on_wave(); } void gui_main_menu_audio_stop() { if(app.ui.sound_play_status == SOUND_PLAYING_FROM_START) { gui_main_menu_audio_play_start(NULL, 0, NULL); return; } sound_stop(); gui_main_audio_play_mode(SOUND_NOT_PLAYING); kry_marker_set_value(app.ui.marker_current, -1); gui_main_focus_on_wave(); } void gui_main_audio_play_mode(enum sound_play_status status) { if(!audio_play_status_mutex) return; g_mutex_lock(audio_play_status_mutex); if(app.ui.sound_play_status == status) { g_mutex_unlock(audio_play_status_mutex); return; } if(status == SOUND_NOT_PLAYING) { sound_signal_pos_change(-1); } else if(status == SOUND_PLAYING_FROM_START) { kry_waveform_group_marker_set_autoscroll(app.ui.waveform_group, app.ui.marker_current); } else { kry_waveform_group_marker_set_autoscroll(app.ui.waveform_group, NULL); } /*if(status != SOUND_NOT_PLAYING) { gtk_widget_show(GTK_WIDGET(app.ui.tool_play_stop)); gtk_widget_hide(GTK_WIDGET(app.ui.tool_play_start)); } else { gtk_widget_show(GTK_WIDGET(app.ui.tool_play_start)); gtk_widget_hide(GTK_WIDGET(app.ui.tool_play_stop)); }*/ app.ui.sound_play_status = status; g_mutex_unlock(audio_play_status_mutex); } /* * Callback for the 'Audio -> Play From Start Marker' menu item. */ void gui_main_menu_audio_play_start(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { KryMarker *marker_end; KryMarker *marker_start; long start = 0; if(kry_marker_get_value(app.ui.marker_active_start) != -1) start = kry_marker_get_value(app.ui.marker_active_start); if(!app.ui.sound_info) return; g_mutex_lock(audio_play_status_mutex); if(app.ui.sound_play_status == SOUND_PLAYING_FROM_START) { g_mutex_unlock(audio_play_status_mutex); sound_stop(); gui_main_audio_play_mode(SOUND_NOT_PLAYING); kry_marker_set_value(app.ui.marker_active_start, kry_marker_get_value(app.ui.marker_current)); kry_marker_set_value(app.ui.marker_active_end, kry_marker_get_value(app.ui.marker_current) + app.opts.time_advance); kry_marker_set_value(app.ui.marker_current, -1); kry_waveform_group_focus_selected(app.ui.waveform_group); return; } else if(app.ui.sound_play_status == SOUND_PLAYING_OTHER) { sound_stop(); } g_mutex_unlock(audio_play_status_mutex); marker_end = KRY_MARKER(kry_marker_new(MARKER_END, 10)); kry_marker_set_value(marker_end, -1); marker_start = KRY_MARKER(kry_marker_copy(app.ui.marker_active_start)); kry_marker_set_value(marker_start, start); gui_main_audio_play_mode(SOUND_PLAYING_FROM_START); gui_main_play_time_range(marker_start, marker_end); g_object_unref(G_OBJECT(marker_end)); g_object_unref(G_OBJECT(marker_start)); gui_main_focus_on_wave(); } /* * Callback for the 'Audio -> Play Current Row' menu item. */ void gui_main_menu_audio_play_current_row(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { gui_main_play_row(0); } /* * Callback for the 'Audio -> Play Previous Row' menu item. */ void gui_main_menu_audio_play_previous_row(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { gui_main_event_current_save_text(); gui_main_play_row(-1); } /* * Callback for the 'Audio -> Play Next Row' menu item. */ void gui_main_menu_audio_play_next_row(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { gui_main_event_current_save_text(); gui_main_play_row(1); } void gui_main_advance_markers() { if(kry_marker_get_value(app.ui.marker_event_start) == -1) kry_marker_set_value(app.ui.marker_event_start, 0); if(kry_marker_get_value(app.ui.marker_event_end) == -1) kry_marker_set_value(app.ui.marker_event_end, 0); int advance = app.opts.time_advance; if(advance == 0) advance = 2000; kry_marker_set_value(app.ui.marker_event_start, kry_marker_get_value(app.ui.marker_event_end)); kry_marker_set_value(app.ui.marker_event_end, kry_marker_get_value(app.ui.marker_event_end) + advance); } void gui_main_save_selection_cb() { int start = kry_marker_get_value(app.ui.marker_active_start); int end = kry_marker_get_value(app.ui.marker_active_end); if(start < 0) start = 0; if(end < 0) end = 0; if(end <= start) return; if(!app.ui.sound_info) return; char *file = gui_main_save_file("Save Selection", file_filter_audio, "wav", app.script->GetSuggestedFilename()); if(!file) return; sound_stop(); struct sound_info *data = app.ui.sound_info; int frame_start = (int) ((data->sfinfo.samplerate / 1000.0) * start); int frame_current = frame_start; int frame_end = (int) ((data->sfinfo.samplerate / 1000.0) * end); int toRead; const int bufferLen = 65536; char buffer[bufferLen]; if(sf_seek(data->snd, frame_start, SEEK_SET) == -1) return; SF_INFO out_format; out_format.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; out_format.samplerate = data->sfinfo.samplerate; out_format.channels = data->sfinfo.channels; char *file_locale = g_locale_from_utf8(file, -1, NULL, NULL, NULL); SNDFILE *sound_out = sf_open(file_locale, SFM_WRITE, &out_format); kry_free(file_locale); if(!sound_out) { gui_error("The file '%s' could not be opened for output: %s", file, sf_strerror(NULL)); kry_free(file); return; } kry_free(file); for(;;) { if(frame_end != -1 && ((frame_end - frame_current) * data->sfinfo.channels) < bufferLen / 2) { toRead = (frame_end - frame_current) * data->sfinfo.channels; } else { toRead = bufferLen / 2; } if(data->sfinfo.channels == 2 && toRead % 2 != 0) toRead--; int read; read = (long) sf_read_short(data->snd, (short *) buffer, toRead); memset((char *) buffer + read*2, 0, bufferLen - read * 2); if(read == 0) break; sf_write_short(sound_out, (short *) buffer, read); frame_current += (read / data->sfinfo.channels); } sf_close(sound_out); gui_status_bar_push_text_with_color(app.ui.status_bar, STATUS_AUDIO_SAVED, __("Status|Audio Saved"), app.ui.status_bar_color_table->Get(STATUS_COLOR_SUCCESS)); } gboolean gui_main_grab_times(enum grab_flags flags) { GtkTreeModel *model; GtkTreeSelection *selection = gui_event_list_get_selection(app.ui.event_list); GList *sel_items = gtk_tree_selection_get_selected_rows(selection, &model); GtkTreePath *path; GtkTreeIter iter; kryEvent *line; kryEventDetailed *event_detailed; gboolean first_item = TRUE; char *text; char *start, *end, *dur; char *layer; unsigned int old_start = 0, old_end = 0; gboolean advance = flags & GRAB_ADVANCE; gboolean enter_key = flags & GRAB_ENTERKEY; gboolean check_old_time = FALSE; if(!sel_items) { gui_main_error_sound(); return FALSE; } sound_stop(); gui_main_audio_play_mode(SOUND_NOT_PLAYING); if(kry_marker_get_value(app.ui.marker_active_start) == -1) kry_marker_set_value(app.ui.marker_active_start, 0); if(kry_marker_get_value(app.ui.marker_active_end) == -1) kry_marker_set_value(app.ui.marker_active_end, 0); app.script->SetModifiedFlag(TRUE); text = gui_text_editor_get_text(app.ui.text_editor); if(strstr(text, "\xA\xA") != NULL) { g_list_foreach(sel_items, (GFunc) gtk_tree_path_free, NULL); g_list_free(sel_items); kry_free(text); gui_error(_("You may not have blank lines in a SRT script")); return FALSE; } while(sel_items) { path = (GtkTreePath *) sel_items->data; gtk_tree_model_get_iter(model, &iter, path); gtk_tree_model_get(model, &iter, PTR_COLUMN, &line, -1); event_detailed = (kryEventDetailed *) line; // don't allow grabbing to "separator" lines if(line->GetType() == kryEvent::EVENT_COMMENT_BLANK) { gui_error(_("You cannot assign text/times to a separator line (a line that is blank in the script file itself)")); advance = FALSE; sel_items = sel_items->next; continue; } // when grabbing to multiple lines, only set the first line's text // this is useful when multiple lines are set to the same time // the set text if either of the "time only" flags is set if(first_item && !(flags & GRAB_TIME_START_ONLY || flags & GRAB_TIME_END_ONLY)) { line->SetText(text); gtk_list_store_set(GTK_LIST_STORE(model), &iter, TEXT_COLUMN, line->GetText(), -1); first_item = FALSE; } // modify comment text if(line->GetType() == kryEvent::EVENT_COMMENT_STRING) { // there is nothing else to be done, go to next row sel_items = sel_items->next; continue; } // save data to an empty row else if(line->GetType() == kryEvent::EVENT_BLANK) { event_detailed->SetEffect(""); event_detailed->SetType(kryEvent::EVENT_DIALOG); char *style_name = KRY_TS(gtk_combo_box_get_active_text(app.ui.combo_style)); const char *name = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(app.ui.combo_name)))); event_detailed->SetStyle(style_name); event_detailed->SetName((char *) name); gtk_list_store_set(GTK_LIST_STORE(model), &iter, STYLE_COLUMN, event_detailed->GetStyle(), NAME_COLUMN, event_detailed->GetName(), -1); gui_event_list_update_blank_count(app.ui.event_list); gui_event_list_append_blank_rows(app.ui.event_list, 10 - app.script->GetBlankCount()); kry_free(style_name); } else { check_old_time = TRUE; old_start = line->GetStart(); old_end = line->GetEnd(); } line->SetTime(kry_marker_get_value(app.ui.marker_active_start), kry_marker_get_value(app.ui.marker_active_end)); start = time_mili_to_string(kry_marker_get_value(app.ui.marker_active_start), app.ui.ui_mode == UI_MODE_SRT); end = time_mili_to_string(kry_marker_get_value(app.ui.marker_active_end), app.ui.ui_mode == UI_MODE_SRT); dur = time_mili_to_string(kry_marker_get_value(app.ui.marker_active_end) - kry_marker_get_value(app.ui.marker_active_start), app.ui.ui_mode == UI_MODE_SRT); gtk_list_store_set(GTK_LIST_STORE(model), &iter, START_COLUMN, start, END_COLUMN, end, DUR_COLUMN, dur, -1); if(line->IsDetailed()) { layer = kry_strdup_printf(KRY_LOC "%d", event_detailed->GetLayer()); gtk_list_store_set(GTK_LIST_STORE(model), &iter, LAYER_COLUMN, layer, STYLE_COLUMN, event_detailed->GetStyle(), -1); kry_free(layer); } kry_free(start); kry_free(end); kry_free(dur); sel_items = sel_items->next; } kry_free(text); if(advance) { gboolean time_changed = FALSE; if(check_old_time && (line->GetStart() != old_start || line->GetEnd() != old_end)) time_changed = TRUE; if(gui_main_event_select(1, &iter, TRUE, TRUE)) { kryEvent *line2; gtk_tree_model_get(model, &iter, PTR_COLUMN, &line2, -1); if(line2->GetType() != kryEvent::EVENT_DIALOG || (line2->GetStart() == 0 && line2->GetEnd() == 0) || app.opts.disable_automatic_wave_jump || !enter_key) { int adv = app.opts.time_advance; if(app.ui.karaoke_mode != KARAOKE_MODE_OFF) adv = app.opts.time_advance / 5; kry_marker_set_value(app.ui.marker_active_start, line->GetEnd()); kry_marker_set_value(app.ui.marker_active_end, line->GetEnd() + adv); } else { kry_marker_set_value(app.ui.marker_active_start, line2->GetStart()); kry_marker_set_value(app.ui.marker_active_end, line2->GetEnd()); kry_waveform_group_focus_selected(app.ui.waveform_group); } gui_main_set_time_modified(FALSE); if(time_changed) g_object_set_data(G_OBJECT(gui_event_list_get_view(app.ui.event_list)), "disable-auto-focus", (void *) TRUE); gtk_tree_selection_unselect_all(selection); gtk_tree_selection_select_iter(selection, &iter); if(time_changed) g_object_set_data(G_OBJECT(gui_event_list_get_view(app.ui.event_list)), "disable-auto-focus", (void *) FALSE); } } g_list_foreach(sel_items, (GFunc) gtk_tree_path_free, NULL); g_list_free(sel_items); return TRUE; } gboolean gui_main_grab_times_cb(GtkWidget *widget, gboolean advance) { gboolean rv = gui_main_grab_times(advance ? GRAB_ADVANCE : GRAB_DEFAULT); gui_main_focus_on_wave(); return rv; } void gui_main_play_time_range(KryMarker *marker_start, KryMarker *marker_end) { if((kry_marker_get_value(marker_end) != -1 && kry_marker_get_value(marker_end) <= kry_marker_get_value(marker_start)) || app.ui.sound_info == NULL) return; gdk_threads_leave(); sound_play(app.ui.sound_info->snd, &app.ui.sound_info->sfinfo, marker_start, marker_end); gdk_threads_enter(); } void gui_main_play_selected(GtkWidget *widget, gpointer data) { if(kry_marker_get_value(app.ui.marker_active_start) == -1 || kry_marker_get_value(app.ui.marker_active_end) == -1 || !app.ui.sound_info) return; g_mutex_lock(audio_play_status_mutex); if(app.opts.play_selection_stop && app.ui.sound_play_status != SOUND_NOT_PLAYING) { g_mutex_unlock(audio_play_status_mutex); sound_stop(); gui_main_audio_play_mode(SOUND_NOT_PLAYING); } else { g_mutex_unlock(audio_play_status_mutex); gui_main_audio_play_mode(SOUND_PLAYING_OTHER); gui_main_play_time_range(app.ui.marker_active_start, app.ui.marker_active_end); } gui_main_focus_on_wave(); } void gui_main_menu_audio_openrecent(GtkWidget *menu, char *filename) { gui_audio_open(filename); } gboolean gui_audio_open(char *filename) { struct sound_info *sound_info; if(!audio_play_status_mutex) audio_play_status_mutex = g_mutex_new(); gui_main_disable(); gui_status_bar_text_and_progress_mode(app.ui.status_bar, __("Status|Opening Audio...")); char *error = NULL; sound_info = sound_open(filename, &error); if(!sound_info) { gui_error(_("The audio file '%s' could not be opened: %s"), filename, error); } else { gui_main_recent_list_add(app.ui.menu.audio.open_recent, &app.ui.list_recent_audio, app.ui.cb_recent_audio, kry_strdup(filename)); gui_waveforms_destroy(); gui_waveforms_setup(sound_info); gui_main_update_sensitivity(); } gui_status_bar_text_only_mode(app.ui.status_bar); gui_main_enable(); gui_main_focus_on_wave(); if(!sound_info) return FALSE; return TRUE; } /* * Callback for the 'Audio -> Open' menu item. */ void gui_main_menu_file_audio_open(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { char *filename = gui_main_open_file(__("FileDialog|Open Audio"), file_filter_audio); if(!filename) return; gui_audio_open(filename); kry_free(filename); } void gui_main_audio_close() { if(!app.ui.sound_info) return; gui_waveforms_destroy(); // setup empty waveform gui_waveforms_setup(NULL); gui_main_update_sensitivity(); } /* * Callback for the 'Audio -> Close' menu item. */ void gui_main_menu_file_audio_close(GtkWidget *menu_item, guint param) { gui_main_audio_close(); }