diff --git a/aegisub/src/libresrc/default_config.json b/aegisub/src/libresrc/default_config.json index f0238777b..fcda84e47 100644 --- a/aegisub/src/libresrc/default_config.json +++ b/aegisub/src/libresrc/default_config.json @@ -349,6 +349,7 @@ }, "Subtitle" : { + "Character Limit" : 40, "Default Resolution" : { "Auto" : true, "Height" : 720, diff --git a/aegisub/src/libresrc/osx/default_config.json b/aegisub/src/libresrc/osx/default_config.json index 70d2bf383..33c62eb84 100644 --- a/aegisub/src/libresrc/osx/default_config.json +++ b/aegisub/src/libresrc/osx/default_config.json @@ -349,6 +349,7 @@ }, "Subtitle" : { + "Character Limit" : 40, "Default Resolution" : { "Auto" : true, "Height" : 720, diff --git a/aegisub/src/preferences.cpp b/aegisub/src/preferences.cpp index 7e55b0217..3cf62ea99 100644 --- a/aegisub/src/preferences.cpp +++ b/aegisub/src/preferences.cpp @@ -199,6 +199,7 @@ Interface::Interface(wxTreebook *book, Preferences *parent): OptionPage(book, pa OptionAdd(edit_box, _("Enable syntax highlighting"), "Subtitle/Highlight/Syntax"); OptionBrowse(edit_box, _("Dictionaries path"), "Path/Dictionary"); OptionFont(edit_box, "Subtitle/Edit Box/"); + OptionAdd(edit_box, _("Maximum characters per line"), "Subtitle/Character Limit", 0, 1000); wxFlexGridSizer *grid = PageSizer(_("Grid")); OptionAdd(grid, _("Allow grid to take focus"), "Subtitle/Grid/Focus Allow"); diff --git a/aegisub/src/subs_edit_box.cpp b/aegisub/src/subs_edit_box.cpp index 27efa2c67..519ba0834 100644 --- a/aegisub/src/subs_edit_box.cpp +++ b/aegisub/src/subs_edit_box.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,7 @@ #include "ass_dialogue.h" #include "ass_file.h" #include "command/command.h" +#include "compat.h" #include "dialog_search_replace.h" #include "include/aegisub/context.h" #include "include/aegisub/hotkey.h" @@ -120,6 +122,10 @@ SubsEditBox::SubsEditBox(wxWindow *parent, agi::Context *context) Bind(wxEVT_COMMAND_COMBOBOX_SELECTED, &SubsEditBox::OnEffectChange, this, Effect->GetId()); TopSizer->Add(Effect, 3, wxALIGN_CENTER, 5); + CharCount = new wxTextCtrl(this, -1, "0", wxDefaultPosition, wxSize(30, -1), wxTE_READONLY | wxTE_CENTER); + CharCount->SetToolTip(_("Number of characters in the longest line of this subtitle.")); + TopSizer->Add(CharCount, 0, wxALIGN_CENTER, 5); + // Middle controls MiddleSizer = new wxBoxSizer(wxHORIZONTAL); @@ -287,6 +293,7 @@ void SubsEditBox::OnCommit(int type) { if (type & AssFile::COMMIT_DIAG_TEXT) { TextEdit->SetTextTo(line->Text); + UpdateCharacterCount(line->Text); } if (type & AssFile::COMMIT_DIAG_META) { @@ -375,6 +382,7 @@ void SubsEditBox::OnChange(wxStyledTextEvent &event) { if (event.GetModificationType() & wxSTC_STARTACTION) commitId = -1; CommitText(_("modify text")); + UpdateCharacterCount(line->Text); } } @@ -514,3 +522,13 @@ void SubsEditBox::CallCommand(const char *cmd_name) { cmd::call(cmd_name, c); TextEdit->SetFocus(); } + +void SubsEditBox::UpdateCharacterCount(wxString const& text) { + size_t length = MaxLineLength(text); + CharCount->SetValue(wxString::Format("%lu", (unsigned long)length)); + size_t limit = (size_t)OPT_GET("Subtitle/Character Limit")->GetInt(); + if (limit && length > limit) + CharCount->SetBackgroundColour(to_wx(OPT_GET("Colour/Subtitle/Syntax/Background/Error")->GetColor())); + else + CharCount->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +} diff --git a/aegisub/src/subs_edit_box.h b/aegisub/src/subs_edit_box.h index 9cc03ec36..ff5efdd0f 100644 --- a/aegisub/src/subs_edit_box.h +++ b/aegisub/src/subs_edit_box.h @@ -103,6 +103,7 @@ class SubsEditBox : public wxPanel { Placeholder *Effect; wxRadioButton *ByTime; wxRadioButton *ByFrame; + wxTextCtrl *CharCount; wxSizer *TopSizer; wxSizer *MiddleBotSizer; @@ -184,6 +185,9 @@ class SubsEditBox : public wxPanel { /// @brief Enable or disable frame timing mode void UpdateFrameTiming(agi::vfr::Framerate const& fps); + /// Update the character count box for the given text + void UpdateCharacterCount(wxString const& text); + /// Call a command the restore focus to the edit box void CallCommand(const char *cmd_name); diff --git a/aegisub/src/utils.cpp b/aegisub/src/utils.cpp index 1ae63d4d6..5cd185cc9 100644 --- a/aegisub/src/utils.cpp +++ b/aegisub/src/utils.cpp @@ -391,6 +391,51 @@ void CleanCache(wxString const& directory, wxString const& file_type, int64_t ma LOG_D("utils/clean_cache") << "thread started successfully"; } +size_t MaxLineLength(wxString const& text) { + size_t max_line_length = 0; + size_t current_line_length = 0; + bool last_was_slash = false; + bool in_ovr = false; + + for (auto const& c : text) { + if (in_ovr) { + in_ovr = c != '}'; + continue; + } + + if (c == '\\') { + current_line_length += last_was_slash; // for the slash before this one + last_was_slash = true; + continue; + } + + if (last_was_slash) { + last_was_slash = false; + if (c == 'h') { + ++current_line_length; + continue; + } + + if (c == 'n' || c == 'N') { + max_line_length = std::max(max_line_length, current_line_length); + current_line_length = 0; + continue; + } + + // Not actually an escape so add the character for the slash and fall through + ++current_line_length; + } + + if (c == '{') + in_ovr = true; + else + ++current_line_length; + } + + current_line_length += last_was_slash; + return std::max(max_line_length, current_line_length); +} + // OS X implementation in osx_utils.mm #ifndef __WXOSX_COCOA__ void AddFullScreenButton(wxWindow *) { } diff --git a/aegisub/src/utils.h b/aegisub/src/utils.h index c22fdd8c0..4330b845c 100644 --- a/aegisub/src/utils.h +++ b/aegisub/src/utils.h @@ -74,6 +74,9 @@ bool StringEmptyOrWhitespace(const wxString &str); int AegiStringToInt(const wxString &str,int start=0,int end=-1); int AegiStringToFix(const wxString &str,size_t decimalPlaces,int start=0,int end=-1); +/// Get the length in characters of the longest line in the given text +size_t MaxLineLength(wxString const& text); + /// @brief Launch a new copy of Aegisub. /// /// Contrary to what the name suggests, this does not close the currently