// Copyright (c) 2005, Ghassan Nassar // 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 // // Website: http://aegisub.cellosoft.com // Contact: mailto:zeratul@cellosoft.com // #ifndef NO_SPELLCHECKER #include "aspell_wrap.h" #include "main.h" #include "dialog_spellcheck.h" #include "aspell_wrap.h" #include #include #include DialogSpellCheck::DialogSpellCheck (wxWindow *parent, AssFile *_subs, wxArrayInt* selList, SubtitlesGrid *_grid) : wxDialog (parent,-1,_T("Document Spell Checker"),wxDefaultPosition,wxDefaultSize,wxDEFAULT_DIALOG_STYLE,_T("DialogSpellCheck")) { // Suggestions wxSizer *SuggestionBox = new wxStaticBoxSizer(wxVERTICAL,this,_T("ErrorBox")); Textbox = new wxTextCtrl(this, TEXTBOX_LINE, _T(""), wxDefaultPosition, wxSize(185,55), wxTE_RICH2 | wxTE_WORDWRAP, wxDefaultValidator, _T("Displayed Line")); SuggestionsList = new wxListBox(this, LIST_SUGGESTIONS, wxDefaultPosition, wxSize(185,95), 0, NULL, wxLB_SINGLE | wxLB_SORT, wxDefaultValidator, _T("Suggestions")); SuggestionBox->Add(Textbox,0,wxRIGHT,5); SuggestionBox->AddSpacer(5); SuggestionBox->Add(SuggestionsList,1,wxEXPAND | wxRIGHT | wxALIGN_RIGHT,5); //// RButtons styles list wxSizer *RButtons = new wxStaticBoxSizer(wxVERTICAL, this, _T("Extra Commands")); SkipError = new wxButton(this, BUTTON_SKIP, _T("Skip this error"), wxDefaultPosition, wxSize(80,25), 0, wxDefaultValidator, _T("Skip This Error")); AddtoCustomDic = new wxButton(this, BUTTON_ADDCUSTOM, _T("Add to Dict."), wxDefaultPosition, wxSize(80,25), 0, wxDefaultValidator, _T("Add To Custom Dictionary")); Options = new wxButton(this, BUTTON_PREFERENCES, _T("Options"), wxDefaultPosition, wxSize(80,25), 0, wxDefaultValidator, _T("Preferences")); RButtons->AddSpacer(20); RButtons->Add(SkipError,0, wxALL | wxALIGN_CENTER_VERTICAL,0); RButtons->AddSpacer(20); RButtons->Add(AddtoCustomDic,0, wxALL | wxALIGN_CENTER_VERTICAL,0); RButtons->AddSpacer(20); RButtons->Add(Options,0, wxALL | wxALIGN_CENTER_VERTICAL,0); RButtons->AddSpacer(20); ////Lower Buttons wxSizer *LButtons = new wxBoxSizer(wxHORIZONTAL); AcceptSuggestion = new wxButton(this, BUTTON_ACCEPT, _T("Accept Suggestion"), wxDefaultPosition, wxSize(100,20), 0, wxDefaultValidator, _T("Accept")); Exit = new wxButton(this, BUTTON_EXIT, _T("Exit"), wxDefaultPosition, wxSize(100,20), 0, wxDefaultValidator, _T("Exit")); LButtons->AddSpacer(30); LButtons->Add(AcceptSuggestion,0, wxALL | wxALIGN_CENTER_HORIZONTAL ,0); LButtons->AddSpacer(30); LButtons->Add(Exit,0, wxALL | wxALIGN_CENTER_HORIZONTAL,0); LButtons->AddSpacer(30); //// TOP General layout wxSizer *MBox = new wxBoxSizer(wxHORIZONTAL); MBox->Add(SuggestionBox,0,wxRIGHT,5); MBox->Add(RButtons,0,wxLEFT,5); //wxButton *CloseButton = new wxButton(this, wxID_CLOSE, _T(""), wxDefaultPosition, wxSize(100,25), 0, wxDefaultValidator, _T("Close")); MainSizer = new wxBoxSizer(wxVERTICAL); MainSizer->Add(MBox ,0,wxEXPAND | wxLEFT | wxRIGHT | wxTOP,5); LButtons->AddSpacer(5); MainSizer->Add(LButtons,0,wxEXPAND | wxLEFT | wxRIGHT |wxTOP ,5); //MainSizer->Add(CloseButton,0,wxBOTTOM | wxALIGN_CENTER,5); //// Set sizer SetSizer(MainSizer); MainSizer->SetSizeHints(this); subs = _subs; grid = _grid; lnList = *selList; aspellConfig = NULL; aspellSpeller = NULL; aspellChecker = NULL; InitSpellCheck(); } DialogSpellCheck::~DialogSpellCheck(void) { if (pDictionaryChanged) { if (wxYES == ::wxMessageBox(_T("Would you like to save any of your changes to your personal dictionary?"), _T("Save Changes"), wxYES_NO | wxICON_QUESTION)) aspell_speller_save_all_word_lists(aspellSpeller); } if (aspellChecker != NULL) delete_aspell_document_checker(aspellChecker); if (aspellConfig != NULL) delete_aspell_config(aspellConfig); if (aspellSpeller != NULL) delete_aspell_speller(aspellSpeller); Aspell.Unload(); } bool DialogSpellCheck::SetOptions(wxString Option,wxString Value){ if (aspellConfig == NULL) aspellConfig = new_aspell_config(); if (aspellConfig == NULL) return FALSE; aspell_config_replace(aspellConfig, Option.mb_str(wxConvUTF8), Value.mb_str(wxConvUTF8)); return true; } bool DialogSpellCheck::SetDefaultOptions(){ wxString defaultDataDir = AegisubApp::folderName; defaultDataDir.Append(_T("data\\")); wxString defaultDictDir = AegisubApp::folderName; defaultDictDir.Append(_T("dict\\")); if (wxDir::Exists(defaultDataDir)) //&& wxDir::Exists(defaultDictDir)) { SetOptions(wxString(_T("encoding")), _T("utf-8")); SetOptions(wxString(_T("lang")), wxString(_T("en-US"))); //1* SetOptions(wxString(_T("data-dir")), defaultDataDir); SetOptions(wxString(_T("dict-dir")), defaultDataDir); //defaultDictDir SetOptions(wxString(_T("local-data-dir")), defaultDataDir); } wxString l2Checka = defaultDataDir; l2Checka.Append( _T("\\en.dat")); wxString l2Checkb = defaultDataDir; // Set to the same place for now. It's regularily in two folders. l2Checkb.Append(_T("\\en_phonet.dat")); if (!wxFile::Exists(l2Checka) || !wxFile::Exists(l2Checkb)) { wxMessageBox(_T("The Files: \"en.dat\" and \"en_phonet.dat\", in \".\\data\" are necessary for proper execution. \nProcess will continue, but with **unpredicable** execution. \nFix: Reinstall Dictionaries."), _T("Warning"),wxICON_EXCLAMATION | wxOK | wxSTAY_ON_TOP); // 2 } if (aspell_config_error(aspellConfig) != 0) { wxString A(aspell_config_error_message(aspellConfig),wxConvUTF8); wxMessageBox(wxString::Format(_T("Error: %s\n"), A) , _T("Warning"),wxICON_ERROR | wxOK | wxSTAY_ON_TOP); return false; } return true; } bool DialogSpellCheck::Uninit(){ Aspell.Unload(); // Here for when I get it dynamically linked. return true; // this is useless for now, but not in the future. } void DialogSpellCheck::UILock(bool enable){ if (enable) { SkipError->Enable(false); AcceptSuggestion->Enable(false); AddtoCustomDic->Enable(false); SuggestionsList->Enable(false); Textbox->Enable(false); } else{ SkipError->Enable(true); AcceptSuggestion->Enable(true); AddtoCustomDic->Enable(true); SuggestionsList->Enable(true); Textbox->Enable(true); } } void DialogSpellCheck::LineSetUp(){ bool alt = false; if (lnList.GetCount() > 0) if (lnList.Item(0) == -1) alt = true; if ((lnList.GetCount() == 0) || (alt == true)) { start = 0; end = grid->GetNumberRows() - 1; curLineNumber = 0; current_line = grid->GetDialogue(curLineNumber); return; } else { curLineNumber = lnList.Item(0); lnList.RemoveAt(0,1); current_line = grid->GetDialogue(curLineNumber); start = -1; end = -1; return; } // something went drastically wrong... return; } bool DialogSpellCheck::IncLines(){ // Used for lines 1+, line 0 is handled by LineSetUp //if (a) return false; // used as an aux check for end of line. if ((start == -1) && (end == -1)){ if (lnList.GetCount() > 0) { curLineNumber = lnList.Item(0); lnList.RemoveAt(0,1); current_line = grid->GetDialogue(curLineNumber); return true; } else return false; } else if (start == 0){ if (curLineNumber < end){ curLineNumber++; current_line = grid->GetDialogue(curLineNumber); return true; } else return false; } return false; // shouldnt every really happen but vs stops yelling about it. } void DialogSpellCheck::Stage0(){ // Stage 0, used intially. LineSetUp(); if (!LineComputation()) return; LineBegin(); Stage2(); } void DialogSpellCheck::Stage1(){ // Stage 1, used after stage 0 is initiated. if (!LineComputation()){ if (!IncLines()){ UILock(true);//init lines. return; } } LineBegin(); Stage2(); } void DialogSpellCheck::Stage2(){ // Stage 2, rather useless to be honest but, it makes things clear. Precheck(); // Repeat here till line is checked. } void DialogSpellCheck::Stage3(){ // Stage 3: Kill things that need to be killed find next line and continue. LineEnd(); Stage1(); } bool DialogSpellCheck::Precheck() { /* Using Document Checker and Settings previously set, find errors in the line. Also check if the word was told to be ignored.*/ if ((token = aspell_document_checker_next_misspelling(aspellChecker), token.len != 0)){ token.offset += nDiff; const wxString misspelling = current_block.Mid(token.offset, token.len); misspelled_word = misspelling; if (lIgnore.Index(misspelling) != wxNOT_FOUND) // implement button to actually DO this. return true; Populate(misspelling); UILock(false); } else { Stage3(); return false; } return true; } void DialogSpellCheck::Populate(wxString misspelling){ Textbox->Clear(); Textbox->AppendText(current_block); int c_start = current_block.Find(misspelling); Textbox->SetStyle(c_start, c_start + misspelling.Length(), wxTextAttr(*wxRED, wxNullColour, wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, _T(""), wxFONTENCODING_DEFAULT) )); SuggestionsList->Clear(); SuggestionsList->InsertItems(GetSuggestions(misspelling),0); } void DialogSpellCheck::LineEnd(){ nLine = false; nDiff = 0; token.len = 0; token.offset = 0; delete_aspell_document_checker(aspellChecker); aspellChecker = NULL; UILock(true); } bool DialogSpellCheck::LineBegin(){ if (aspellSpeller == NULL) return false; AspellCanHaveError* ret = new_aspell_document_checker(aspellSpeller); if (aspell_error(ret) != 0) { wxString A(aspell_error_message(ret),wxConvUTF8); wxMessageBox(wxString::Format(_T("Error: %s\n"), A)); return false; } aspellChecker = to_aspell_document_checker(ret); aspell_document_checker_process(aspellChecker , current_block.mb_str(wxConvUTF8), -1); return true; } wxArrayString DialogSpellCheck::GetSuggestions(wxString strMisspelledWord) { const char * suggest; const wxString * misspelling = &strMisspelledWord; wxArrayString wxStringList; const AspellWordList * suggestions = aspell_speller_suggest(aspellSpeller, misspelling->mb_str(wxConvUTF8), misspelling->Length()); AspellStringEnumeration * elements = aspell_word_list_elements(suggestions); wxStringList.IsEmpty(); while ( (suggest = aspell_string_enumeration_next(elements)) != NULL ){ wxString StringADD(suggest,wxConvUTF8); wxStringList.Add(StringADD); } delete_aspell_string_enumeration(elements); return wxStringList; } wxArrayString DialogSpellCheck::GetWordListAsArray(){ const char * suggested; wxArrayString wxStringList; const AspellWordList* PersonalWordList = aspell_speller_personal_word_list(aspellSpeller); AspellStringEnumeration* elements = aspell_word_list_elements(PersonalWordList); wxStringList.IsEmpty(); while ( (suggested = aspell_string_enumeration_next(elements)) != NULL ){ wxString StringADD(suggested,wxConvUTF8); wxStringList.Add(StringADD); } delete_aspell_string_enumeration(elements); return wxStringList; } int DialogSpellCheck::AddWordToDictionary(wxString strWord){ const wxString * correctterm = &strWord; aspell_speller_add_to_personal(aspellSpeller, correctterm->mb_str(wxConvUTF8), strWord.Length()); pDictionaryChanged = TRUE; return TRUE; } void DialogSpellCheck::InitSpellCheck(){ nLine = false; curLineNumber = 0; curBlockNumber = -1; start = -1; end = -1; pDictionaryChanged = false; token.len = 0; token.offset = 0; nDiff = 0; UILock(false); Aspell.Load(); if (aspellConfig == NULL) aspellConfig = new_aspell_config(); if (aspellConfig == NULL){ wxMessageBox(_T("Critical Error! Config Settings Are Not Present.")); UILock(true); // Forces the user to exit. return; } SetDefaultOptions(); AspellCanHaveError* ret = new_aspell_speller(aspellConfig); if (aspell_error(ret) != 0){ wxString A(aspell_error_message(ret),wxConvUTF8); wxMessageBox(wxString::Format(_T("Error: %s\n"), A)); delete_aspell_can_have_error(ret); UILock(true); // Forces the user to exit. return; } aspellSpeller = to_aspell_speller(ret); Stage0(); } BEGIN_EVENT_TABLE(DialogSpellCheck, wxEvtHandler) EVT_BUTTON(wxID_CLOSE, DialogSpellCheck::OnClose) EVT_BUTTON(BUTTON_ACCEPT, DialogSpellCheck::OnAccept) EVT_BUTTON(BUTTON_EXIT, DialogSpellCheck::OnExit) EVT_BUTTON(BUTTON_PREFERENCES, DialogSpellCheck::OnOptions) EVT_BUTTON(BUTTON_SKIP, DialogSpellCheck::OnSkip) EVT_BUTTON(BUTTON_ADDCUSTOM, DialogSpellCheck::OnAdd) EVT_LISTBOX_DCLICK(LIST_SUGGESTIONS, DialogSpellCheck::OnListDoubleClick) EVT_TEXT(TEXTBOX_LINE, DialogSpellCheck::OnTextChange) END_EVENT_TABLE() ///////// //EVENTS void DialogSpellCheck::OnClose (wxCommandEvent &event) { if ((Textbox->GetValue() != current_block) && (Textbox->GetValue() != _T(""))) { wxMessageDialog Question(this, _T( "You've selected:Exit. Yet, the current line has not yet been saved. Would you like to save the changes?"), _T("Exit Warning"), wxICON_QUESTION | wxSTAY_ON_TOP | wxYES_NO | wxYES_DEFAULT, wxDefaultPosition); int a = Question.ShowModal(); if (a == wxID_YES){ BlockStore(); } } Destroy(); } void DialogSpellCheck::OnAccept (wxCommandEvent &event) { if (AcceptSuggestion->GetLabel() != _T("Replace w\\ Edit?")) ComputeAction(ACTION_ACCEPT); else ComputeAction(ACTION_USER_CUSTOM); } void DialogSpellCheck::OnExit (wxCommandEvent &event) { if ((Textbox->GetValue() != current_block) && (Textbox->GetValue() != _T(""))) { wxMessageDialog Question(this, _T( "You've selected:Exit. Yet, the current line has not yet been saved. Would you like to save the changes?"), _T("Exit Warning"), wxICON_QUESTION | wxSTAY_ON_TOP | wxYES_NO | wxYES_DEFAULT, wxDefaultPosition); int a = Question.ShowModal(); if (a == wxID_YES){ BlockStore(); } } Destroy(); } void DialogSpellCheck::OnOptions (wxCommandEvent &event) { //TODO } void DialogSpellCheck::OnSkip (wxCommandEvent &event) { ComputeAction(ACTION_SKIP); } void DialogSpellCheck::OnAdd (wxCommandEvent &event) { ComputeAction(ACTION_ADD); } void DialogSpellCheck::OnListDoubleClick (wxCommandEvent &event) { ComputeAction(ACTION_ACCEPT); } void DialogSpellCheck::OnTextChange (wxCommandEvent &event) { if (Textbox->GetValue() != current_block) AcceptSuggestion->SetLabel(_T("Replace w\\ Edit?")); else AcceptSuggestion->SetLabel(_T("Accept Suggestion")); } void DialogSpellCheck::ComputeAction(int action) { const wxString correct_word = (SuggestionsList->GetSelection() == wxNOT_FOUND) ? _T("") : SuggestionsList->GetString(SuggestionsList->GetSelection()); if ((SuggestionsList->GetSelection() == wxNOT_FOUND) && (action == ACTION_ACCEPT)) return; const wxString misspelling = misspelled_word; switch (action) { case ACTION_ACCEPT: nDiff += correct_word.Length() - token.len; // Let the spell checker know what the correct replacement was aspell_speller_store_replacement(aspellSpeller, misspelling.mb_str(wxConvUTF8), token.len, correct_word.mb_str(wxConvUTF8), correct_word.Length()); current_block.replace(token.offset, token.len, correct_word);// Replace the misspelled word with the replacement */ BlockStore(); Textbox->Clear(); Textbox->AppendText(current_block); UILock(true); break; case ACTION_SKIP: break; case ACTION_ADD: AddWordToDictionary(misspelled_word); break; case ACTION_USER_CUSTOM: current_block = Textbox->GetValue(); BlockStore(); Textbox->Clear(); Textbox->AppendText(current_block); curBlockNumber--; token.len = 0; token.offset = 0; default: break; } Stage2(); } bool DialogSpellCheck::LineComputation() { AssDialogueBlockPlain *curPlain; current_line->ParseASSTags(); size_t size_blocks = current_line->Blocks.size(); for (size_t i=(curBlockNumber+1);iBlocks.at(i)); if (curPlain) { current_block = curPlain->GetText(); curBlockNumber = i; return true; } } curPlain = 0; curBlockNumber = -1; current_line->ClearBlocks(); return false; } void DialogSpellCheck::BlockStore() { current_line->ParseASSTags(); AssDialogueBlockPlain *curPlain = AssDialogueBlock::GetAsPlain(current_line->Blocks.at(curBlockNumber)); if (curPlain) { curPlain->text = current_block; current_line->UpdateText(); current_line->UpdateData(); grid->SetRowToLine(curLineNumber,current_line); subs->FlagAsModified(); } curPlain = 0; current_line->ClearBlocks(); } #endif