/* * 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 "krySSACommandStripper.h" #include "gui_main_script.h" #include "gui_main_tab_video.h" #include "gui_main_style.h" #include "gui_main_video.h" #include "gui_status_bar.h" #include "gui_text_editor.h" #include "gui_event_list.h" #include "sound.h" #include "stringutils.h" #include "kryTextFileReader.h" #include "krySubSSA.h" #include "krySubReader.h" #include "krySubReaderSSA.h" #include "krySubReaderSRT.h" #include "krySubReaderTXT.h" #include "krySubWriter.h" #include "krySubWriterSSA.h" #include "krySubWriterSRT.h" #include "krySubWriterTXT.h" #include "krySubWriterAdobeEncore.h" extern struct sabbu app; extern krySabbu *appObj; /* * Closes the currently open script and sets up new empty one. */ void krySabbu::ScriptNew() { GtkTreeSelection *selection = gui_event_list_get_selection(app.ui.event_list); GtkTreePath *path = gtk_tree_path_new_from_string("0"); app.script = new kryScript(); char *type = app.prefs->GetString("DefaultScriptType"); if(!type || !strcmp(type, "ASS")) app.script->SetType(KRY_FORMAT_ASS); else if(!strcmp(type, "SRT")) app.script->SetType(KRY_FORMAT_SRT); if(app.prefs->GetInt("SaveUTF16", 0)) app.script->SetEncoding(ENCODING_UTF16); this->InvokeSignal(krySabbu::SIGNAL_SCRIPT_CHANGED, app.script); app.script->SetModifiedFlag(FALSE); app.script->DisableSignal(kryScript::SIGNAL_MODIFIED); app.script->AddDefaultStyle(); app.ui.style_editor.current = NULL; gui_event_list_fill_from_list_detailed(app.ui.event_list, &(app.script->GetEventList())); // adds the blank lines gui_text_editor_set_text(app.ui.text_editor, ""); gui_main_title_update(); gtk_spin_button_set_value(app.ui.spin_layer, 0); // TODO: why are we checking here? both of the combos should be created // before this function is invoked if(app.ui.combo_style && app.ui.style_editor.combo_style) gui_main_style_list_fill(); gtk_tree_selection_select_path(selection, path); gtk_tree_path_free(path); app.script->EnableSignal(kryScript::SIGNAL_MODIFIED); app.script->SetModifiedFlag(FALSE); gui_main_set_time_modified(FALSE); } /* * Callback for the 'Script -> New' menu item. */ void gui_main_menu_script_new(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { if(!appObj->ScriptClose()) return; appObj->ScriptNew(); gui_main_style_list_fill(); app.ui.style_editor.current = app.script->GetStyle("Default"); } /* * Asks the user for a file and then tries to open it as SSA or ASS. */ kryScript *gui_main_script_open() { char *filename = gui_main_open_file(__("FileDialog|Open Script"), file_filter_script_ssa); if(!filename) return NULL; kryList error_list; kryScript *script = new kryScript(); krySubReaderSSA *reader = new krySubReaderSSA(filename); krySubReader::retval rv = reader->ReadScript(script, &error_list); delete reader; if(rv != krySubReader::SCRIPT_OK) { gui_warning(_("The specified file could not be read or is not a valid script")); delete script; return NULL; } if(error_list.GetLength() > 0 && !gui_main_error_dialog_show(_("The following errors occured while loading the script:"), _("You may either continue and Open the script or you can Close the script."), &error_list)) { delete script; return NULL; } return script; } /* * Opens the given script file. * If filename is NULL, prompts the user for a filename. */ gboolean krySabbu::ScriptOpen(char *filename) { int rv; kryScript *script; if(app.script->GetModifiedFlag() && !gui_main_script_save(FALSE, TRUE, FALSE)) return FALSE; if(!filename) { filename = gui_main_open_file(__("FileDialog|Open Script"), file_filter_script); } else { filename = kry_strdup(filename); } if(!filename) return FALSE; gui_main_disable(); gui_status_bar_text_and_progress_mode(app.ui.status_bar, __("Status|Loading Script...")); script = new kryScript(); kryList error_list; krySubReaderSSA *reader = new krySubReaderSSA(filename); rv = reader->ReadScript(script, &error_list); delete reader; if(rv == krySubReader::SCRIPT_WRONGFORMAT) { delete script; script = new kryScript(); krySubReaderSRT *reader = new krySubReaderSRT(filename); rv = reader->ReadScript(script, &error_list); delete reader; } script->SetModifiedFlag(FALSE); if(rv == krySubReader::SCRIPT_WRONGFORMAT) { rv = gui_question_yes_no(_("This doesn't seem to be a script file. \n\nImport as a text file?")); delete script; if(rv) { script = new kryScript(); krySubReaderTXT *reader = new krySubReaderTXT(filename); reader->ReadScript(script, &error_list); delete reader; } else { gui_status_bar_text_only_mode(app.ui.status_bar); gui_main_enable(); return FALSE; } } else if(rv != krySubReader::SCRIPT_OK) { gui_error("'%s' could not be loaded", filename); gui_status_bar_text_only_mode(app.ui.status_bar); gui_main_enable(); delete script; return FALSE; } if(error_list.GetLength() > 0 && !gui_main_error_dialog_show(_("The following errors occured while loading the script:"), _("You may either continue and Open the script or you can Close the script. \n\nErrors may indicate possible data loss if the script is saved. Be careful."), &error_list)) { gui_status_bar_text_only_mode(app.ui.status_bar); gui_main_enable(); delete script; return FALSE; } char *base = KRY_TS(g_path_get_basename(filename)); for(int i = strlen(base) - 1; i >= 0; i--) { if(base[i] == '.') { base[i] = 0; break; } } script->SetSuggestedFilename(base); kry_free(base); gui_status_bar_text_only_mode(app.ui.status_bar); gui_status_bar_text_and_progress_mode(app.ui.status_bar, __("Status|Filling Event List...")); // we don't want to get another dialog asking us to save since we would have already // asked the user and would have already saved the file if necessary app.script->SetModifiedFlag(FALSE); appObj->ScriptClose(); app.script = script; this->InvokeSignal(krySabbu::SIGNAL_SCRIPT_CHANGED, script); gui_event_list_fill_from_list_detailed(app.ui.event_list, &app.script->GetEventList()); gui_main_recent_list_add(app.ui.menu.script.open_recent, &app.ui.list_recent_scripts, app.ui.cb_recent_scripts, kry_strdup(filename)); gui_text_editor_set_text(app.ui.text_editor, ""); gui_status_bar_text_only_mode(app.ui.status_bar); gui_main_enable(); gui_main_focus(); gui_main_title_update(); app.ui.style_editor.current = (kryStyle *) app.script->GetStyle("Default"); gui_main_style_list_fill(); GtkTreePath *path = gtk_tree_path_new_from_string("0"); gtk_tree_selection_select_path(gui_event_list_get_selection(app.ui.event_list), path); gtk_tree_path_free(path); return TRUE; } /* * Callback for the 'Script -> Open' menu item. */ void gui_main_menu_script_open(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { appObj->ScriptOpen(NULL); } /* * Callback for the 'Script -> Open Recent -> X' menu item. */ void gui_main_menu_script_openrecent(GtkWidget *widget, gpointer callback_data) { appObj->ScriptOpen((char *) callback_data); } void gui_main_menu_script_autosave(gboolean value, gpointer data) { app.ui.autosave = value; app.prefs->SetInt("AutoSave", app.ui.autosave); } /* * Callback for the 'Script -> Save' menu item. */ void gui_main_menu_script_save(GtkWidget *menu, guint param) { if(gui_main_script_save(FALSE, FALSE, FALSE)) { gui_status_bar_push_text_with_color(app.ui.status_bar, STATUS_SAVED, __("Status|Script Saved"), app.ui.status_bar_color_table->Get(STATUS_COLOR_SUCCESS)); gui_main_title_update(); } } char *gui_main_script_get_type_ext(enum kryScriptType type) { switch(type) { case KRY_FORMAT_SSA: return "SSA"; case KRY_FORMAT_ASS: return "ASS"; case KRY_FORMAT_SRT: return "SRT"; case KRY_FORMAT_TEXT: return "TXT"; case KRY_FORMAT_ENCORE: return "TXT"; default: return "???"; } } char *gui_main_script_get_type_name(enum kryScriptType type) { switch(type) { case KRY_FORMAT_SSA: return "SubStation Alpha"; case KRY_FORMAT_ASS: return "Advanced SubStation Alpha"; case KRY_FORMAT_SRT: return "SubRip"; case KRY_FORMAT_TEXT: return "Text"; case KRY_FORMAT_ENCORE: return "Adobe Encore"; default: return "Unknown Format"; } } /* * Saves the currently opened file. * ask_file: If TRUE, requests a filename from user * ask_confirm: If TRUE, asks the user to confirm the save * NOTE: if ask_confirm is TRUE and users says 'No', this function * still returns TRUE as that 'No' (this is actually correct if you think about it) * returns FALSE if save fails, TRUE otherwise */ gboolean gui_main_script_save(gboolean ask_file, gboolean ask_confirm, gboolean utf16) { gboolean reload_list = FALSE; gboolean convert_ssa_to_srt = FALSE; gboolean convert_srt_to_ssa = FALSE; char *filename; char *orig_filename = NULL; char *error = NULL; int rv; if(ask_confirm) { GtkDialog *dialog = GTK_DIALOG(gtk_message_dialog_new(GTK_WINDOW(app.ui.window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("The script has been modified since the last save.\n\nWould you like to save the changes?"))); gtk_dialog_add_buttons(dialog, GTK_STOCK_YES, 1, GTK_STOCK_NO, 2, GTK_STOCK_CANCEL, 3, NULL); rv = gtk_dialog_run(dialog); gtk_widget_destroy(GTK_WIDGET(dialog)); // user clicks 'No' if(rv == 2) { return TRUE; } // user clicks 'Cancel' else if(rv == 3) { return FALSE; } } if(app.script->GetFilename() == NULL || ask_file) // new document { int i; char *base; char *ext; if(app.script->GetType() == KRY_FORMAT_ASS) filename = gui_main_save_file(__("FileDialog|Save Script"), file_filter_script_s_ass, "ass", app.script->GetSuggestedFilename()); else if(app.script->GetType() == KRY_FORMAT_SSA) filename = gui_main_save_file(__("FileDialog|Save Script"), file_filter_script_s_ssa, "ssa", app.script->GetSuggestedFilename()); else filename = gui_main_save_file(__("FileDialog|Save Script"), file_filter_script_s_srt, "srt", app.script->GetSuggestedFilename()); if(!filename) return FALSE; base = g_path_get_basename(filename); for(i = strlen(base) - 1; i >= 0 && base[i] != '.'; i--); i++; ext = base + i; // going from .ssa to .ass, we do not produce a warning as nothing is lost if(app.script->GetType() != KRY_FORMAT_ASS && !strcmp(ext, "ass")) { app.script->SetType(KRY_FORMAT_ASS); } else if(app.script->GetType() == KRY_FORMAT_SRT && !strcmp(ext, "ssa")) { app.script->SetType(KRY_FORMAT_SSA); reload_list = TRUE; convert_srt_to_ssa = TRUE; } else if(app.script->GetType() == KRY_FORMAT_SRT && !strcmp(ext, "ass")) { app.script->SetType(KRY_FORMAT_ASS); reload_list = TRUE; convert_srt_to_ssa = TRUE; } else if((app.script->GetType() == KRY_FORMAT_SSA || app.script->GetType() == KRY_FORMAT_ASS) && !strcmp(ext, "srt")) { char *from_id = gui_main_script_get_type_name(app.script->GetType()); char *to_id = gui_main_script_get_type_name(KRY_FORMAT_SRT); rv = gui_question_yes_no(_("Saving as an '%s' file will DISCARD data that is specific to the '%s' format.\n\nAre you sure you want to continue?"), to_id, from_id); if(!rv) { kry_free(base); return FALSE; } app.script->SetType(KRY_FORMAT_SRT); reload_list = TRUE; convert_ssa_to_srt = TRUE; } else if(app.script->GetType() == KRY_FORMAT_ASS && !strcmp(ext, "ssa")) { char *from_id = gui_main_script_get_type_name(app.script->GetType()); char *to_id = gui_main_script_get_type_name(KRY_FORMAT_SSA); rv = gui_question_yes_no(_("Saving as a '%s' file will DISCARD data that is specific to the '%s' format.\n\nAre you sure you want to continue?"), to_id, from_id); if(!rv) { kry_free(base); return FALSE; } app.script->SetType(KRY_FORMAT_SSA); kryListIterator iter; app.script->GetStyleNameIterator(&iter); char *style_name; while((style_name = iter.GetNext())) { kryStyle *style = app.script->GetStyle(style_name); if(style->GetType() == KRY_STYLE_COMMENT) continue; style->SetUnderline(FALSE); style->SetStrikethrough(FALSE); style->SetFontAngleZ(0); style->SetType(KRY_STYLE_SSA); } } if(convert_srt_to_ssa || convert_ssa_to_srt) { kryListIterator iter; app.script->GetEventIterator(&iter); while(kryEvent *event = iter.GetNext()) { char *text = event->GetText(); if(!text) continue; char *new_text = NULL; if(convert_srt_to_ssa) { new_text = string_replace(text, "\n", "\\N"); } else if(convert_ssa_to_srt) { krySSACommandStripper stripper(text); new_text = stripper.GetString(); } event->SetText(new_text); kry_free(new_text); } } kry_free(base); if(app.script->GetFilename()) orig_filename = kry_strdup(app.script->GetFilename()); app.script->SetFilename(filename); gui_main_recent_list_add(app.ui.menu.script.open_recent, &app.ui.list_recent_scripts, app.ui.cb_recent_scripts, kry_strdup(filename)); } enum file_encoding encoding = app.script->GetEncoding(); if(utf16) app.script->SetEncoding(ENCODING_UTF16); krySubWriter *writer = NULL; if(app.script->GetType() == KRY_FORMAT_SSA || app.script->GetType() == KRY_FORMAT_ASS) { writer = new krySubWriterSSA(app.script); if(app.opts.format_script) ((krySubWriterSSA *) writer)->EnableFormatScript(); } else if(app.script->GetType() == KRY_FORMAT_SRT) { writer = new krySubWriterSRT(app.script); } else { error = _("Unknown format"); } if(writer) { error = writer->WriteScript(); delete writer; } if(error) { gui_error(_("Error saving script: %s"), error); app.script->SetFilename(orig_filename); // if save fails, we revert to the previous filename kry_free(orig_filename); app.script->SetEncoding(encoding); return FALSE; } // if 'Save As' succeeds, destroy the old filename else if(app.script->GetFilename() != orig_filename && orig_filename) { kry_free(orig_filename); } char *fname = kry_strdup(app.script->GetFilename()); for(int i = strlen(fname) - 1; i >= 0; i--) { if(fname[i] == '.') { fname[i] = 0; break; } } app.script->SetSuggestedFilename(fname); kry_free(fname); if(app.script->GetType() == KRY_FORMAT_SSA) gui_main_set_ui_mode(UI_MODE_SSA); else if(app.script->GetType() == KRY_FORMAT_ASS) gui_main_set_ui_mode(UI_MODE_ASS); else if(app.script->GetType() == KRY_FORMAT_SRT) gui_main_set_ui_mode(UI_MODE_SRT); if(app.script->GetFilename()) gtk_widget_set_sensitive(GTK_WIDGET(app.ui.menu.script.close), TRUE); else gtk_widget_set_sensitive(GTK_WIDGET(app.ui.menu.script.close), FALSE); if(app.script->GetEncoding() == ENCODING_UTF16) gtk_widget_set_sensitive(GTK_WIDGET(app.ui.menu.script.save_as_utf16), FALSE); else gtk_widget_set_sensitive(GTK_WIDGET(app.ui.menu.script.save_as_utf16), TRUE); if(reload_list) gui_event_list_fill_from_list_detailed(app.ui.event_list, &app.script->GetEventList()); kry_marker_value_changed(app.ui.marker_event_start, kry_marker_get_value(app.ui.marker_event_start)); kry_marker_value_changed(app.ui.marker_event_end, kry_marker_get_value(app.ui.marker_event_end)); app.script->SetModifiedFlag(FALSE); return TRUE; } /* * Callback for the 'Script -> Save' menu item. */ void gui_main_menu_script_save_as(GtkWidget *widget, guint param) { if(gui_main_script_save(TRUE, FALSE, param)) { gui_status_bar_push_text_with_color(app.ui.status_bar, STATUS_SAVED, __("Status|Script Saved"), app.ui.status_bar_color_table->Get(STATUS_COLOR_SUCCESS)); gui_main_title_update(); } } /* * Callback for the 'Script -> Close' menu item. */ void gui_main_menu_script_close(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { if(!appObj->ScriptClose()) return; appObj->ScriptNew(); } void gui_main_menu_script_revert_autosave(gpointer callback_data, guint callback_action, GtkWidget *menu_item) { } /* * Closes the currently opened script. * NOTE: a new script must be created or loaded before using any script operations * otherwise unexpected behaviour will occur */ gboolean krySabbu::ScriptClose() { app.ui.tab_video.posx = -1; app.ui.tab_video.posy = -1; if(app.script->GetModifiedFlag()) if(!gui_main_script_save(FALSE, TRUE, FALSE)) return FALSE; // everything went fine, destroy the data delete app.script; app.script = NULL; gui_event_list_clear(app.ui.event_list); this->InvokeSignal(krySabbu::SIGNAL_SCRIPT_CHANGED, app.script); return TRUE; } gboolean gui_main_script_autosave(gpointer data) { char *filename_orig = NULL; char *filename; char *base = NULL; char *ptr; char *target; char *ext = "ssa"; char *dir; char *best_choice = NULL; int count = 0; int i; time_t best_choice_time = 0; struct _stat stat_info; if(!app.script || !app.ui.autosave) return TRUE; if(!app.script->GetFilename()) { int index = 0; int event_count = 0; kryListIterator event_iter; app.script->GetEventIterator(&event_iter); while(kryEvent *event = event_iter.GetNext()) { if(event->GetType() != kryEvent::EVENT_BLANK) event_count = index + 1; index++; } if(event_count < 3) return TRUE; } if(!app.script->GetModifiedFlag()) return TRUE; if(app.script->GetFilename()) filename_orig = kry_strdup(app.script->GetFilename()); if(!filename_orig) { dir = sabbu_get_config_dir(); if(app.script->GetID()) { char *tmpfile = kry_strdup_printf(KRY_LOC "%d.ssa", app.script->GetID()); filename_orig = KRY_TS(g_build_filename(dir, tmpfile, NULL)); kry_free(tmpfile); } else { srand(time(0)); for(;;) { struct _stat stat_info; int id = rand(); char *tmpfile = kry_strdup_printf(KRY_LOC "%d.ssa", id); target = KRY_TS(g_build_filename(dir, tmpfile, NULL)); kry_free(tmpfile); if(stat(target, &stat_info) == -1) { app.script->SetID(id); filename_orig = target; break; } kry_free(target); } } kry_free(dir); } dir = KRY_TS(g_path_get_dirname(filename_orig)); filename = KRY_TS(g_path_get_basename(filename_orig)); kry_free(filename_orig); for(ptr = filename, i = strlen(ptr) - 1; i >= 0; i--) { if(ptr[i] == '.') { ptr[i] = 0; base = kry_strdup(filename); ext = kry_strdup(ptr+i+1); break; } } if(!base) { base = kry_strdup(filename); } target = KRY_TS(g_build_filename(dir, "SabbuAutoSave", NULL));; if(stat(target, &stat_info) == -1) #ifndef _WINDOWS mkdir(target, S_IWUSR | S_IRUSR | S_IXUSR); #else mkdir(target); #endif if(stat(target, &stat_info) == -1) { gui_error("Could not create directory for Autosave files (%s)", target); kry_free(filename); kry_free(base); kry_free(dir); kry_free(target); return TRUE; } kry_free(target); for(;;) { char *tmpfile = kry_strdup_printf(KRY_LOC "%s_autosave_%d.%s", base, 1 + count, ext); target = KRY_TS(g_build_filename(dir, "SabbuAutoSave", tmpfile, NULL)); kry_free(tmpfile); if(stat(target, &stat_info) == -1) { best_choice = kry_strdup(target); kry_free(target); break; } if(best_choice_time == 0 || stat_info.st_mtime < best_choice_time) { if(best_choice) kry_free(best_choice); best_choice = kry_strdup(target); best_choice_time = stat_info.st_mtime; } kry_free(target); count++; if(count == 5) break; } krySubWriter *writer = NULL; if(app.script->GetType() == KRY_FORMAT_SSA || app.script->GetType() == KRY_FORMAT_ASS) { writer = new krySubWriterSSA(app.script, best_choice); if(app.opts.format_script) ((krySubWriterSSA*) writer)->EnableFormatScript(); } else if(app.script->GetType() == KRY_FORMAT_SRT) { writer = new krySubWriterSRT(app.script, best_choice); } if(writer) { writer->WriteScript(); delete writer; } gui_status_bar_push_text_with_color(app.ui.status_bar, STATUS_AUTOSAVED, __("Status|AutoSave created"), app.ui.status_bar_color_table->Get(STATUS_COLOR_SUCCESS)); kry_free(best_choice); kry_free(filename); kry_free(base); kry_free(dir); return TRUE; }