// Copyright (c) 2005, Rodrigo Braz Monteiro // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Aegisub Group nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Aegisub Project http://www.aegisub.org/ #include "ass_dialogue.h" #include "ass_file.h" #include "ass_style.h" #include "ass_style_storage.h" #include "charset_detect.h" #include "compat.h" #include "dialog_manager.h" #include "dialog_style_editor.h" #include "dialogs.h" #include "include/aegisub/context.h" #include "help_button.h" #include "libresrc/libresrc.h" #include "options.h" #include "persist_location.h" #include "selection_controller.h" #include "subtitle_format.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Keep this last so wxUSE_CHOICEDLG is set. namespace { class DialogStyleManager final : public wxDialog { agi::Context *c; ///< Project context std::unique_ptr persist; agi::signal::Connection commit_connection; agi::signal::Connection active_line_connection; std::shared_future font_list; /// Styles in the current subtitle file std::vector styleMap; /// Style storage manager AssStyleStorage Store; wxComboBox *CatalogList; wxListBox *StorageList; wxListBox *CurrentList; wxButton *CatalogDelete; wxButton *MoveToLocal; wxButton *MoveToStorage; wxButton *StorageNew; wxButton *StorageEdit; wxButton *StorageCopy; wxButton *StorageDelete; wxButton *StorageMoveUp; wxButton *StorageMoveDown; wxButton *StorageMoveTop; wxButton *StorageMoveBottom; wxButton *StorageSort; wxButton *CurrentNew; wxButton *CurrentEdit; wxButton *CurrentCopy; wxButton *CurrentDelete; wxButton *CurrentMoveUp; wxButton *CurrentMoveDown; wxButton *CurrentMoveTop; wxButton *CurrentMoveBottom; wxButton *CurrentSort; /// Load the list of available storages void LoadCatalog(); /// Load the style list from the subtitles file void LoadCurrentStyles(int commit_type); /// Enable/disable all of the buttons as appropriate void UpdateButtons(); /// Move styles up or down /// @param storage Storage or current file styles /// @param type 0: up; 1: top; 2: down; 3: bottom; 4: sort void MoveStyles(bool storage, int type); /// Open the style editor for the given style on the script /// @param style Style to edit, or nullptr for new /// @param new_name Default new name for copies void ShowCurrentEditor(AssStyle *style, std::string const& new_name = ""); /// Open the style editor for the given style in the storage /// @param style Style to edit, or nullptr for new /// @param new_name Default new name for copies void ShowStorageEditor(AssStyle *style, std::string const& new_name = ""); /// Save the storage and update the view after a change void UpdateStorage(); void OnChangeCatalog(); void OnCatalogNew(); void OnCatalogDelete(); void OnCopyToCurrent(); void OnCopyToStorage(); void OnCurrentCopy(); void OnCurrentDelete(); void OnCurrentEdit(); void OnCurrentImport(); void OnCurrentNew(); void OnStorageCopy(); void OnStorageDelete(); void OnStorageEdit(); void OnStorageNew(); void OnKeyDown(wxKeyEvent &event); void PasteToCurrent(); void PasteToStorage(); template void CopyToClipboard(wxListBox *list, T const& v); void OnActiveLineChanged(AssDialogue *new_line); public: DialogStyleManager(agi::Context *context); }; wxBitmapButton *add_bitmap_button(wxWindow *parent, wxSizer *sizer, wxBitmap const& img, wxString const& tooltip) { wxBitmapButton *btn = new wxBitmapButton(parent, -1, img); btn->SetToolTip(tooltip); sizer->Add(btn, wxSizerFlags().Expand()); return btn; } wxSizer *make_move_buttons(wxWindow *parent, wxButton **up, wxButton **down, wxButton **top, wxButton **bottom, wxButton **sort) { wxSizer *sizer = new wxBoxSizer(wxVERTICAL); sizer->AddStretchSpacer(1); *up = add_bitmap_button(parent, sizer, GETIMAGE(arrow_up_24), _("Move style up")); *down = add_bitmap_button(parent, sizer, GETIMAGE(arrow_down_24), _("Move style down")); *top = add_bitmap_button(parent, sizer, GETIMAGE(arrow_up_stop_24), _("Move style to top")); *bottom = add_bitmap_button(parent, sizer, GETIMAGE(arrow_down_stop_24), _("Move style to bottom")); *sort = add_bitmap_button(parent, sizer, GETIMAGE(arrow_sort_24), _("Sort styles alphabetically")); sizer->AddStretchSpacer(1); return sizer; } wxSizer *make_edit_buttons(wxWindow *parent, wxString move_label, wxButton **move, wxButton **nw, wxButton **edit, wxButton **copy, wxButton **del) { wxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); *move = new wxButton(parent, -1, move_label); *nw = new wxButton(parent, -1, _("&New")); *edit = new wxButton(parent, -1, _("&Edit")); *copy = new wxButton(parent, -1, _("&Copy")); *del = new wxButton(parent, -1, _("&Delete")); sizer->Add(*nw, wxSizerFlags(1).Expand().Border(wxRIGHT)); sizer->Add(*edit, wxSizerFlags(1).Expand().Border(wxRIGHT)); sizer->Add(*copy, wxSizerFlags(1).Expand().Border(wxRIGHT)); sizer->Add(*del, wxSizerFlags(1).Expand()); return sizer; } template std::string unique_name(Func name_checker, std::string const& source_name) { if (name_checker(source_name)) { std::string name = from_wx(wxString::Format(_("%s - Copy"), to_wx(source_name))); for (int i = 2; name_checker(name); ++i) name = from_wx(wxString::Format(_("%s - Copy (%d)"), to_wx(source_name), i)); return name; } return source_name; } template void add_styles(Func1 name_checker, Func2 style_adder) { auto cb = GetClipboard(); int failed_to_parse = 0; for (auto tok : agi::Split(cb, '\n')) { tok = boost::trim_copy(tok); if (tok.empty()) continue; try { AssStyle *s = new AssStyle(agi::str(tok)); s->name = unique_name(name_checker, s->name); style_adder(s); } catch (...) { ++failed_to_parse; } } if (failed_to_parse) wxMessageBox(_("Could not parse style"), _("Could not parse style"), wxOK | wxICON_EXCLAMATION); } int confirm_delete(int n, wxWindow *parent, wxString const& title) { return wxMessageBox( wxString::Format(wxPLURAL("Are you sure you want to delete this style?", "Are you sure you want to delete these %d styles?", n), n), title, wxYES_NO | wxICON_EXCLAMATION, parent); } int get_single_sel(wxListBox *lb) { wxArrayInt selections; int n = lb->GetSelections(selections); return n == 1 ? selections[0] : -1; } DialogStyleManager::DialogStyleManager(agi::Context *context) : wxDialog(context->parent, -1, _("Styles Manager")) , c(context) , commit_connection(c->ass->AddCommitListener(&DialogStyleManager::LoadCurrentStyles, this)) , active_line_connection(c->selectionController->AddActiveLineListener(&DialogStyleManager::OnActiveLineChanged, this)) , font_list(std::async(std::launch::async, []() -> wxArrayString { wxArrayString fontList = wxFontEnumerator::GetFacenames(); fontList.Sort(); return fontList; })) { using std::bind; SetIcon(GETICON(style_toolbutton_16)); // Catalog wxSizer *CatalogBox = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Catalog of available storages")); CatalogList = new wxComboBox(this,-1, "", wxDefaultPosition, wxSize(-1,-1), 0, nullptr, wxCB_READONLY); wxButton *CatalogNew = new wxButton(this, -1, _("New")); CatalogDelete = new wxButton(this, -1, _("Delete")); CatalogBox->Add(CatalogList,1,wxEXPAND | wxRIGHT | wxALIGN_RIGHT,5); CatalogBox->Add(CatalogNew,0,wxRIGHT,5); CatalogBox->Add(CatalogDelete,0,0,0); // Storage styles list wxSizer *StorageButtons = make_edit_buttons(this, _("Copy to ¤t script ->"), &MoveToLocal, &StorageNew, &StorageEdit, &StorageCopy, &StorageDelete); wxSizer *StorageListSizer = new wxBoxSizer(wxHORIZONTAL); StorageList = new wxListBox(this, -1, wxDefaultPosition, wxSize(240,250), 0, nullptr, wxLB_EXTENDED); StorageListSizer->Add(StorageList,1,wxEXPAND | wxRIGHT,0); StorageListSizer->Add(make_move_buttons(this, &StorageMoveUp, &StorageMoveDown, &StorageMoveTop, &StorageMoveBottom, &StorageSort), wxSizerFlags().Expand()); wxSizer *StorageBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Storage")); StorageBox->Add(StorageListSizer,1,wxEXPAND | wxBOTTOM,5); StorageBox->Add(MoveToLocal,0,wxEXPAND | wxBOTTOM,5); StorageBox->Add(StorageButtons,0,wxEXPAND | wxBOTTOM,0); // Local styles list wxButton *CurrentImport = new wxButton(this, -1, _("&Import from script...")); wxSizer *CurrentButtons = make_edit_buttons(this, _("<- Copy to &storage"), &MoveToStorage, &CurrentNew, &CurrentEdit, &CurrentCopy, &CurrentDelete); wxSizer *MoveImportSizer = new wxBoxSizer(wxHORIZONTAL); MoveImportSizer->Add(MoveToStorage,1,wxEXPAND | wxRIGHT,5); MoveImportSizer->Add(CurrentImport,1,wxEXPAND,0); wxSizer *CurrentListSizer = new wxBoxSizer(wxHORIZONTAL); CurrentList = new wxListBox(this, -1, wxDefaultPosition, wxSize(240,250), 0, nullptr, wxLB_EXTENDED); CurrentListSizer->Add(CurrentList,1,wxEXPAND | wxRIGHT,0); CurrentListSizer->Add(make_move_buttons(this, &CurrentMoveUp, &CurrentMoveDown, &CurrentMoveTop, &CurrentMoveBottom, &CurrentSort), wxSizerFlags().Expand()); wxSizer *CurrentBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Current script")); CurrentBox->Add(CurrentListSizer,1,wxEXPAND | wxBOTTOM,5); CurrentBox->Add(MoveImportSizer,0,wxEXPAND | wxBOTTOM,5); CurrentBox->Add(CurrentButtons,0,wxEXPAND | wxBOTTOM,0); // Buttons wxStdDialogButtonSizer *buttonSizer = CreateStdDialogButtonSizer(wxCANCEL | wxHELP); buttonSizer->GetCancelButton()->SetLabel(_("Close")); Bind(wxEVT_BUTTON, bind(&HelpButton::OpenPage, "Styles Manager"), wxID_HELP); // General layout wxSizer *StylesSizer = new wxBoxSizer(wxHORIZONTAL); StylesSizer->Add(StorageBox,0,wxRIGHT | wxEXPAND,5); StylesSizer->Add(CurrentBox,0,wxLEFT | wxEXPAND,0); wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL); MainSizer->Add(CatalogBox,0,wxEXPAND | wxLEFT | wxRIGHT | wxTOP,5); MainSizer->Add(StylesSizer,1,wxEXPAND | wxALL,5); MainSizer->Add(buttonSizer,0,wxBOTTOM | wxEXPAND,5); SetSizerAndFit(MainSizer); // Position window persist = agi::make_unique(this, "Tool/Style Manager"); // Populate lists LoadCatalog(); LoadCurrentStyles(AssFile::COMMIT_STYLES | AssFile::COMMIT_DIAG_META); //Set key handlers for lists CatalogList->Bind(wxEVT_KEY_DOWN, &DialogStyleManager::OnKeyDown, this); StorageList->Bind(wxEVT_KEY_DOWN, &DialogStyleManager::OnKeyDown, this); CurrentList->Bind(wxEVT_KEY_DOWN, &DialogStyleManager::OnKeyDown, this); StorageMoveUp->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { MoveStyles(true, 0); }); StorageMoveTop->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { MoveStyles(true, 1); }); StorageMoveDown->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { MoveStyles(true, 2); }); StorageMoveBottom->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { MoveStyles(true, 3); }); StorageSort->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { MoveStyles(true, 4); }); CurrentMoveUp->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { MoveStyles(false, 0); }); CurrentMoveTop->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { MoveStyles(false, 1); }); CurrentMoveDown->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { MoveStyles(false, 2); }); CurrentMoveBottom->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { MoveStyles(false, 3); }); CurrentSort->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { MoveStyles(false, 4); }); CatalogNew->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnCatalogNew(); }); CatalogDelete->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnCatalogDelete(); }); StorageNew->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnStorageNew(); }); StorageEdit->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnStorageEdit(); }); StorageCopy->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnStorageCopy(); }); StorageDelete->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnStorageDelete(); }); CurrentNew->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnCurrentNew(); }); CurrentEdit->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnCurrentEdit(); }); CurrentCopy->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnCurrentCopy(); }); CurrentDelete->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnCurrentDelete(); }); CurrentImport->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnCurrentImport(); }); MoveToLocal->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnCopyToCurrent(); }); MoveToStorage->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { OnCopyToStorage(); }); CatalogList->Bind(wxEVT_COMBOBOX, [=](wxCommandEvent&) { OnChangeCatalog(); }); StorageList->Bind(wxEVT_LISTBOX, [=](wxCommandEvent&) { UpdateButtons(); }); StorageList->Bind(wxEVT_LISTBOX_DCLICK, [=](wxCommandEvent&) { OnStorageEdit(); }); CurrentList->Bind(wxEVT_LISTBOX, [=](wxCommandEvent&) { UpdateButtons(); }); CurrentList->Bind(wxEVT_LISTBOX_DCLICK, [=](wxCommandEvent&) { OnCurrentEdit(); }); } void DialogStyleManager::LoadCurrentStyles(int commit_type) { if (commit_type & AssFile::COMMIT_STYLES || commit_type == AssFile::COMMIT_NEW) { CurrentList->Clear(); styleMap.clear(); for (auto& style : c->ass->Styles) { CurrentList->Append(to_wx(style.name)); styleMap.push_back(&style); } } if (commit_type & AssFile::COMMIT_DIAG_META) { AssDialogue *dia = c->selectionController->GetActiveLine(); CurrentList->DeselectAll(); if (dia && commit_type != AssFile::COMMIT_NEW) CurrentList->SetStringSelection(to_wx(dia->Style)); else CurrentList->SetSelection(0); } UpdateButtons(); } void DialogStyleManager::OnActiveLineChanged(AssDialogue *new_line) { if (new_line) { CurrentList->DeselectAll(); CurrentList->SetStringSelection(to_wx(new_line->Style)); UpdateButtons(); } } void DialogStyleManager::UpdateStorage() { Store.Save(); StorageList->Clear(); StorageList->Append(to_wx(Store.GetNames())); UpdateButtons(); } void DialogStyleManager::OnChangeCatalog() { std::string catalog(from_wx(CatalogList->GetStringSelection())); c->ass->Properties.style_storage = catalog; Store.LoadCatalog(catalog); UpdateStorage(); } void DialogStyleManager::LoadCatalog() { CatalogList->Clear(); // Get saved style catalogs auto catalogs = AssStyleStorage::GetCatalogs(); for (auto const& c : catalogs) CatalogList->Append(c); // Create a default storage if there are none if (CatalogList->IsListEmpty()) { Store.LoadCatalog("Default"); Store.push_back(agi::make_unique()); Store.Save(); CatalogList->Append("Default"); } // Set to default if available std::string pickStyle = c->ass->Properties.style_storage; if (pickStyle.empty()) pickStyle = "Default"; int opt = CatalogList->FindString(to_wx(pickStyle), false); CatalogList->SetSelection(opt == wxNOT_FOUND ? 0 : opt); OnChangeCatalog(); } void DialogStyleManager::OnCatalogNew() { wxString name = wxGetTextFromUser(_("New storage name:"), _("New catalog entry"), "", this); if (!name) return; // Remove bad characters from the name wxString badchars = wxFileName::GetForbiddenChars(wxPATH_DOS); int badchars_removed = 0; for (wxUniCharRef chr : name) { if (badchars.find(chr) != badchars.npos) { chr = '_'; ++badchars_removed; } } // Make sure that there is no storage with the same name (case insensitive search since Windows filenames are case insensitive) if (CatalogList->FindString(name, false) != wxNOT_FOUND) { wxMessageBox(_("A catalog with that name already exists."), _("Catalog name conflict"), wxOK | wxICON_ERROR | wxCENTER); return; } // Warn about bad characters if (badchars_removed) { wxMessageBox( wxString::Format(_("The specified catalog name contains one or more illegal characters. They have been replaced with underscores instead.\nThe catalog has been renamed to \"%s\"."), name), _("Invalid characters")); } // Add to list of storages CatalogList->Append(name); CatalogList->SetStringSelection(name); OnChangeCatalog(); } void DialogStyleManager::OnCatalogDelete() { if (CatalogList->GetCount() == 1) return; wxString name = CatalogList->GetStringSelection(); wxString message = wxString::Format(_("Are you sure you want to delete the storage \"%s\" from the catalog?"), name); int option = wxMessageBox(message, _("Confirm delete"), wxYES_NO | wxICON_EXCLAMATION , this); if (option == wxYES) { agi::fs::Remove(config::path->Decode("?user/catalog/" + from_wx(name) + ".sty")); CatalogList->Delete(CatalogList->GetSelection()); CatalogList->SetSelection(0); OnChangeCatalog(); } } void DialogStyleManager::OnCopyToStorage() { wxArrayInt selections; int n = CurrentList->GetSelections(selections); wxArrayString copied; copied.reserve(n); for (int i = 0; i < n; i++) { wxString styleName = CurrentList->GetString(selections[i]); if (AssStyle *style = Store.GetStyle(from_wx(styleName))) { if (wxYES == wxMessageBox(wxString::Format(_("There is already a style with the name \"%s\" in the current storage. Overwrite?"),styleName), _("Style name collision"), wxYES_NO)) { *style = *styleMap.at(selections[i]); copied.push_back(styleName); } } else { Store.push_back(agi::make_unique(*styleMap.at(selections[i]))); copied.push_back(styleName); } } UpdateStorage(); for (auto const& style_name : copied) StorageList->SetStringSelection(style_name, true); UpdateButtons(); } void DialogStyleManager::OnCopyToCurrent() { wxArrayInt selections; int n = StorageList->GetSelections(selections); wxArrayString copied; copied.reserve(n); for (int i = 0; i < n; i++) { wxString styleName = StorageList->GetString(selections[i]); if (AssStyle *style = c->ass->GetStyle(from_wx(styleName))) { if (wxYES == wxMessageBox(wxString::Format(_("There is already a style with the name \"%s\" in the current script. Overwrite?"), styleName), _("Style name collision"), wxYES_NO)) { *style = *Store[selections[i]]; copied.push_back(styleName); } } else { c->ass->Styles.push_back(*new AssStyle(*Store[selections[i]])); copied.push_back(styleName); } } c->ass->Commit(_("style copy"), AssFile::COMMIT_STYLES); CurrentList->DeselectAll(); for (auto const& style_name : copied) CurrentList->SetStringSelection(style_name, true); UpdateButtons(); } template void DialogStyleManager::CopyToClipboard(wxListBox *list, T const& v) { wxArrayInt selections; list->GetSelections(selections); std::string data; for(size_t i = 0; i < selections.size(); ++i) { if (i) data += "\r\n"; AssStyle *s = v[selections[i]]; s->UpdateData(); data += s->GetEntryData(); } SetClipboard(data); } void DialogStyleManager::PasteToCurrent() { add_styles( [=](std::string const& str) { return c->ass->GetStyle(str); }, [=](AssStyle *s) { c->ass->Styles.push_back(*s); }); c->ass->Commit(_("style paste"), AssFile::COMMIT_STYLES); } void DialogStyleManager::PasteToStorage() { add_styles( [=](std::string const& str) { return Store.GetStyle(str); }, [=](AssStyle *s) { Store.push_back(std::unique_ptr(s)); }); UpdateStorage(); StorageList->SetStringSelection(to_wx(Store.back()->name)); UpdateButtons(); } void DialogStyleManager::ShowStorageEditor(AssStyle *style, std::string const& new_name) { DialogStyleEditor editor(this, style, c, &Store, new_name, font_list.get()); if (editor.ShowModal()) { UpdateStorage(); StorageList->SetStringSelection(to_wx(editor.GetStyleName())); UpdateButtons(); } } void DialogStyleManager::OnStorageNew() { ShowStorageEditor(nullptr); } void DialogStyleManager::OnStorageEdit() { int sel = get_single_sel(StorageList); if (sel == -1) return; ShowStorageEditor(Store[sel]); } void DialogStyleManager::OnStorageCopy() { int sel = get_single_sel(StorageList); if (sel == -1) return; ShowStorageEditor(Store[sel], unique_name( [=](std::string const& str) { return Store.GetStyle(str); }, Store[sel]->name)); } void DialogStyleManager::OnStorageDelete() { wxArrayInt selections; int n = StorageList->GetSelections(selections); if (confirm_delete(n, this, _("Confirm delete from storage")) == wxYES) { for (int i = 0; i < n; i++) Store.Delete(selections[i] - i); UpdateStorage(); } } void DialogStyleManager::ShowCurrentEditor(AssStyle *style, std::string const& new_name) { DialogStyleEditor editor(this, style, c, nullptr, new_name, font_list.get()); if (editor.ShowModal()) { CurrentList->DeselectAll(); CurrentList->SetStringSelection(to_wx(editor.GetStyleName())); UpdateButtons(); } } void DialogStyleManager::OnCurrentNew() { ShowCurrentEditor(nullptr); } void DialogStyleManager::OnCurrentEdit() { int sel = get_single_sel(CurrentList); if (sel == -1) return; ShowCurrentEditor(styleMap[sel]); } void DialogStyleManager::OnCurrentCopy() { int sel = get_single_sel(CurrentList); if (sel == -1) return; ShowCurrentEditor(styleMap[sel], unique_name( [=](std::string const& str) { return c->ass->GetStyle(str); }, styleMap[sel]->name)); } void DialogStyleManager::OnCurrentDelete() { wxArrayInt selections; int n = CurrentList->GetSelections(selections); if (confirm_delete(n, this, _("Confirm delete from current")) == wxYES) { for (int i = 0; i < n; i++) { delete styleMap.at(selections[i]); } c->ass->Commit(_("style delete"), AssFile::COMMIT_STYLES); } } void DialogStyleManager::OnCurrentImport() { auto filename = OpenFileSelector(_("Open subtitles file"), "Path/Last/Subtitles", "", "", SubtitleFormat::GetWildcards(0), this); if (filename.empty()) return; std::string charset; try { charset = CharSetDetect::GetEncoding(filename); } catch (agi::UserCancelException const&) { return; } AssFile temp; try { auto reader = SubtitleFormat::GetReader(filename, charset); if (!reader) wxMessageBox("Unsupported subtitle format", "Error", wxOK | wxICON_ERROR | wxCENTER, this); else reader->ReadFile(&temp, filename, 0, charset); } catch (agi::Exception const& err) { wxMessageBox(to_wx(err.GetMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, this); } catch (...) { wxMessageBox("Unknown error", "Error", wxOK | wxICON_ERROR | wxCENTER, this); return; } // Get styles auto styles = temp.GetStyles(); if (styles.empty()) { wxMessageBox(_("The selected file has no available styles."), _("Error Importing Styles")); return; } // Get selection wxArrayInt selections; int res = GetSelectedChoices(this, selections, _("Choose styles to import:"), _("Import Styles"), to_wx(styles)); if (res == -1 || selections.empty()) return; bool modified = false; // Loop through selection for (auto const& sel : selections) { // Check if there is already a style with that name if (AssStyle *existing = c->ass->GetStyle(styles[sel])) { int answer = wxMessageBox( wxString::Format(_("There is already a style with the name \"%s\" in the current script. Overwrite?"), styles[sel]), _("Style name collision"), wxYES_NO); if (answer == wxYES) { modified = true; *existing = *temp.GetStyle(styles[sel]); } continue; } // Copy modified = true; c->ass->Styles.push_back(*new AssStyle(*temp.GetStyle(styles[sel]))); } // Update if (modified) c->ass->Commit(_("style import"), AssFile::COMMIT_STYLES); } void DialogStyleManager::UpdateButtons() { CatalogDelete->Enable(CatalogList->GetCount() > 1); // Get storage selection wxArrayInt sels; int n = StorageList->GetSelections(sels); StorageEdit->Enable(n == 1); StorageCopy->Enable(n == 1); StorageDelete->Enable(n > 0); MoveToLocal->Enable(n > 0); int firstStor = -1; int lastStor = -1; if (n) { firstStor = sels[0]; lastStor = sels[n-1]; } // Check if selection is continuous bool contStor = true; for (int i = 1; i < n; ++i) { if (sels[i] != sels[i-1]+1) { contStor = false; break; } } int itemsStor = StorageList->GetCount(); StorageMoveUp->Enable(contStor && firstStor > 0); StorageMoveTop->Enable(contStor && firstStor > 0); StorageMoveDown->Enable(contStor && lastStor != -1 && lastStor < itemsStor-1); StorageMoveBottom->Enable(contStor && lastStor != -1 && lastStor < itemsStor-1); StorageSort->Enable(itemsStor > 1); // Get current selection n = CurrentList->GetSelections(sels); CurrentEdit->Enable(n == 1); CurrentCopy->Enable(n == 1); CurrentDelete->Enable(n > 0); MoveToStorage->Enable(n > 0); int firstCurr = -1; int lastCurr = -1; if (n) { firstCurr = sels[0]; lastCurr = sels[n-1]; } // Check if selection is continuous bool contCurr = true; for (int i = 1; i < n; ++i) { if (sels[i] != sels[i-1]+1) { contCurr = false; break; } } int itemsCurr = CurrentList->GetCount(); CurrentMoveUp->Enable(contCurr && firstCurr > 0); CurrentMoveTop->Enable(contCurr && firstCurr > 0); CurrentMoveDown->Enable(contCurr && lastCurr != -1 && lastCurr < itemsCurr-1); CurrentMoveBottom->Enable(contCurr && lastCurr != -1 && lastCurr < itemsCurr-1); CurrentSort->Enable(itemsCurr > 1); } struct cmp_name { template bool operator()(T const& lft, T const& rgt) const { return lft->name < rgt->name; } }; template static void do_move(Cont& styls, int type, int& first, int& last, bool storage) { auto begin = styls.begin(); // Move up if (type == 0) { if (first == 0) return; rotate(begin + first - 1, begin + first, begin + last + 1); first--; last--; } // Move to top else if (type == 1) { rotate(begin, begin + first, begin + last + 1); last = last - first; first = 0; } // Move down else if (type == 2) { if (last + 1 == (int)styls.size()) return; rotate(begin + first, begin + last + 1, begin + last + 2); first++; last++; } // Move to bottom else if (type == 3) { rotate(begin + first, begin + last + 1, styls.end()); first = styls.size() - (last - first + 1); last = styls.size() - 1; } // Sort else if (type == 4) { // Get confirmation if (storage) { int res = wxMessageBox(_("Are you sure? This cannot be undone!"), _("Sort styles"), wxYES_NO | wxCENTER); if (res == wxNO) return; } sort(styls.begin(), styls.end(), cmp_name()); first = 0; last = 0; } } void DialogStyleManager::MoveStyles(bool storage, int type) { wxListBox *list = storage ? StorageList : CurrentList; // Get selection wxArrayInt sels; int n = list->GetSelections(sels); if (n == 0 && type != 4) return; int first = 0, last = 0; if (n) { first = sels.front(); last = sels.back(); } if (storage) { do_move(Store, type, first, last, true); UpdateStorage(); } else { do_move(styleMap, type, first, last, false); // Replace styles size_t curn = 0; for (auto it = c->ass->Styles.begin(); it != c->ass->Styles.end(); ++it) { auto new_style_at_pos = c->ass->Styles.iterator_to(*styleMap[curn]); EntryList::node_algorithms::swap_nodes(it.pointed_node(), new_style_at_pos.pointed_node()); if (++curn == styleMap.size()) break; it = new_style_at_pos; } c->ass->Commit(_("style move"), AssFile::COMMIT_STYLES); } for (int i = 0 ; i < (int)list->GetCount(); ++i) { if (i < first || i > last) list->Deselect(i); else list->Select(i); } UpdateButtons(); } void DialogStyleManager::OnKeyDown(wxKeyEvent &event) { wxWindow *focus = wxWindow::FindFocus(); switch(event.GetKeyCode()) { case WXK_DELETE : if (focus == StorageList) OnStorageDelete(); else if (focus == CurrentList) OnCurrentDelete(); break; case 'C' : case 'c' : if (event.CmdDown()) { if (focus == StorageList) CopyToClipboard(StorageList, Store); else if (focus == CurrentList) CopyToClipboard(CurrentList, styleMap); } break; case 'V' : case 'v' : if (event.CmdDown()) { if (focus == StorageList) PasteToStorage(); else if (focus == CurrentList) PasteToCurrent(); } break; default: event.Skip(); break; } } } void ShowStyleManagerDialog(agi::Context *c) { c->dialog->Show(c); }