// Copyright (c) 2005, Rodrigo Braz Monteiro // Copyright (c) 2010, Thomas Goyne // 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/ /// @file subs_edit_box.cpp /// @brief Main subtitle editing area, including toolbars around the text control /// @ingroup main_ui #include "subs_edit_box.h" #include "ass_dialogue.h" #include "ass_file.h" #include "base_grid.h" #include "command/command.h" #include "compat.h" #include "dialog_style_editor.h" #include "include/aegisub/context.h" #include "include/aegisub/hotkey.h" #include "initial_line_state.h" #include "options.h" #include "project.h" #include "placeholder_ctrl.h" #include "selection_controller.h" #include "subs_edit_ctrl.h" #include "text_selection_controller.h" #include "timeedit_ctrl.h" #include "tooltip_manager.h" #include "utils.h" #include "validators.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace std { template struct hash> { size_t operator()(boost::flyweight const& ss) const { return hash()(&ss.get()); } }; } namespace { /// Work around wxGTK's fondness for generating events from ChangeValue void change_value(wxTextCtrl *ctrl, wxString const& value) { if (value != ctrl->GetValue()) ctrl->ChangeValue(value); } wxString new_value(wxComboBox *ctrl, wxCommandEvent &evt) { #ifdef __WXGTK__ return ctrl->GetValue(); #else return evt.GetString(); #endif } void time_edit_char_hook(wxKeyEvent &event) { // Force a modified event on Enter if (event.GetKeyCode() == WXK_RETURN) { TimeEdit *edit = static_cast(event.GetEventObject()); edit->SetValue(edit->GetValue()); } else event.Skip(); } } SubsEditBox::SubsEditBox(wxWindow *parent, agi::Context *context) : wxPanel(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxRAISED_BORDER, "SubsEditBox") , c(context) , undo_timer(GetEventHandler()) { using std::bind; // Top controls top_sizer = new wxBoxSizer(wxHORIZONTAL); comment_box = new wxCheckBox(this,-1,_("&Comment")); comment_box->SetToolTip(_("Comment this line out. Commented lines don't show up on screen.")); #ifdef __WXGTK__ // Only supported in wxgtk comment_box->SetCanFocus(false); #endif top_sizer->Add(comment_box, 0, wxRIGHT | wxALIGN_CENTER, 5); style_box = MakeComboBox("Default", wxCB_READONLY, &SubsEditBox::OnStyleChange, _("Style for this line")); style_edit_button = new wxButton(this, -1, _("Edit"), wxDefaultPosition, wxSize(GetTextExtent(_("Edit")).GetWidth() + 20, -1)); style_edit_button->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { if (active_style) { wxArrayString font_list = wxFontEnumerator::GetFacenames(); font_list.Sort(); DialogStyleEditor(this, active_style, c, nullptr, "", font_list).ShowModal(); } }); top_sizer->Add(style_edit_button, wxSizerFlags().Center().Border(wxRIGHT)); actor_box = new Placeholder(this, _("Actor"), wxSize(110, -1), wxCB_DROPDOWN | wxTE_PROCESS_ENTER, _("Actor name for this speech. This is only for reference, and is mainly useless.")); Bind(wxEVT_TEXT, &SubsEditBox::OnActorChange, this, actor_box->GetId()); Bind(wxEVT_COMBOBOX, &SubsEditBox::OnActorChange, this, actor_box->GetId()); top_sizer->Add(actor_box, wxSizerFlags(2).Center().Border(wxRIGHT)); effect_box = new Placeholder(this, _("Effect"), wxSize(80,-1), wxCB_DROPDOWN | wxTE_PROCESS_ENTER, _("Effect for this line. This can be used to store extra information for karaoke scripts, or for the effects supported by the renderer.")); Bind(wxEVT_TEXT, &SubsEditBox::OnEffectChange, this, effect_box->GetId()); Bind(wxEVT_COMBOBOX, &SubsEditBox::OnEffectChange, this, effect_box->GetId()); top_sizer->Add(effect_box, 3, wxALIGN_CENTER, 5); char_count = new wxTextCtrl(this, -1, "0", wxDefaultPosition, wxSize(30, -1), wxTE_READONLY | wxTE_CENTER); char_count->SetToolTip(_("Number of characters in the longest line of this subtitle.")); top_sizer->Add(char_count, 0, wxALIGN_CENTER, 5); // Middle controls middle_left_sizer = new wxBoxSizer(wxHORIZONTAL); layer = new wxSpinCtrl(this,-1,"",wxDefaultPosition,wxSize(50,-1), wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER,0,0x7FFFFFFF,0); layer->SetToolTip(_("Layer number")); middle_left_sizer->Add(layer, wxSizerFlags().Center()); middle_left_sizer->AddSpacer(5); start_time = MakeTimeCtrl(_("Start time"), TIME_START); end_time = MakeTimeCtrl(_("End time"), TIME_END); middle_left_sizer->AddSpacer(5); duration = MakeTimeCtrl(_("Line duration"), TIME_DURATION); middle_left_sizer->AddSpacer(5); margin[0] = MakeMarginCtrl(_("Left Margin (0 = default from style)"), 0, _("left margin change")); margin[1] = MakeMarginCtrl(_("Right Margin (0 = default from style)"), 1, _("right margin change")); margin[2] = MakeMarginCtrl(_("Vertical Margin (0 = default from style)"), 2, _("vertical margin change")); middle_left_sizer->AddSpacer(5); // Middle-bottom controls middle_right_sizer = new wxBoxSizer(wxHORIZONTAL); MakeButton("edit/style/bold"); MakeButton("edit/style/italic"); MakeButton("edit/style/underline"); MakeButton("edit/style/strikeout"); MakeButton("edit/font"); middle_right_sizer->AddSpacer(5); MakeButton("edit/color/primary"); MakeButton("edit/color/secondary"); MakeButton("edit/color/outline"); MakeButton("edit/color/shadow"); middle_right_sizer->AddSpacer(5); MakeButton("grid/line/next/create"); middle_right_sizer->AddSpacer(10); by_time = MakeRadio(_("T&ime"), true, _("Time by h:mm:ss.cs")); by_frame = MakeRadio(_("F&rame"), false, _("Time by frame number")); by_frame->Enable(false); split_box = new wxCheckBox(this,-1,_("Show Original")); split_box->SetToolTip(_("Show the contents of the subtitle line when it was first selected above the edit box. This is sometimes useful when editing subtitles or translating subtitles into another language.")); split_box->Bind(wxEVT_CHECKBOX, &SubsEditBox::OnSplit, this); middle_right_sizer->Add(split_box, wxSizerFlags().Center().Left()); // Main sizer wxSizer *main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(top_sizer,0,wxEXPAND | wxALL,3); main_sizer->Add(middle_left_sizer,0,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,3); main_sizer->Add(middle_right_sizer,0,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,3); // Text editor edit_ctrl = new SubsTextEditCtrl(this, wxSize(300,50), wxBORDER_SUNKEN, c); edit_ctrl->Bind(wxEVT_CHAR_HOOK, &SubsEditBox::OnKeyDown, this); secondary_editor = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxSize(300,50), wxBORDER_SUNKEN | wxTE_MULTILINE | wxTE_READONLY); main_sizer->Add(secondary_editor,1,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,3); main_sizer->Add(edit_ctrl,1,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,3); main_sizer->Hide(secondary_editor); bottom_sizer = new wxBoxSizer(wxHORIZONTAL); bottom_sizer->Add(MakeBottomButton("edit/revert"), wxSizerFlags().Border(wxRIGHT)); bottom_sizer->Add(MakeBottomButton("edit/clear"), wxSizerFlags().Border(wxRIGHT)); bottom_sizer->Add(MakeBottomButton("edit/clear/text"), wxSizerFlags().Border(wxRIGHT)); bottom_sizer->Add(MakeBottomButton("edit/insert_original")); main_sizer->Add(bottom_sizer); main_sizer->Hide(bottom_sizer); SetSizerAndFit(main_sizer); edit_ctrl->Bind(wxEVT_STC_MODIFIED, &SubsEditBox::OnChange, this); edit_ctrl->SetModEventMask(wxSTC_MOD_INSERTTEXT | wxSTC_MOD_DELETETEXT | wxSTC_STARTACTION); Bind(wxEVT_TEXT, &SubsEditBox::OnLayerEnter, this, layer->GetId()); Bind(wxEVT_SPINCTRL, &SubsEditBox::OnLayerEnter, this, layer->GetId()); Bind(wxEVT_CHECKBOX, &SubsEditBox::OnCommentChange, this, comment_box->GetId()); Bind(wxEVT_CHAR_HOOK, &SubsEditBox::OnKeyDown, this); Bind(wxEVT_SIZE, &SubsEditBox::OnSize, this); Bind(wxEVT_TIMER, [=](wxTimerEvent&) { commit_id = -1; }); wxSizeEvent evt; OnSize(evt); file_changed_slot = c->ass->AddCommitListener(&SubsEditBox::OnCommit, this); connections = agi::signal::make_vector({ context->project->AddTimecodesListener(&SubsEditBox::UpdateFrameTiming, this), context->selectionController->AddActiveLineListener(&SubsEditBox::OnActiveLineChanged, this), context->selectionController->AddSelectionListener(&SubsEditBox::OnSelectedSetChanged, this), context->initialLineState->AddChangeListener(&SubsEditBox::OnLineInitialTextChanged, this), }); context->textSelectionController->SetControl(edit_ctrl); edit_ctrl->SetFocus(); } SubsEditBox::~SubsEditBox() { c->textSelectionController->SetControl(nullptr); } wxTextCtrl *SubsEditBox::MakeMarginCtrl(wxString const& tooltip, int margin, wxString const& commit_msg) { wxTextCtrl *ctrl = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxSize(40,-1), wxTE_CENTRE | wxTE_PROCESS_ENTER, IntValidator()); ctrl->SetMaxLength(4); ctrl->SetToolTip(tooltip); middle_left_sizer->Add(ctrl, wxSizerFlags().Center()); Bind(wxEVT_TEXT, [=](wxCommandEvent&) { int value = agi::util::mid(0, atoi(ctrl->GetValue().utf8_str()), 9999); SetSelectedRows([&](AssDialogue *d) { d->Margin[margin] = value; }, commit_msg, AssFile::COMMIT_DIAG_META); }, ctrl->GetId()); return ctrl; } TimeEdit *SubsEditBox::MakeTimeCtrl(wxString const& tooltip, TimeField field) { TimeEdit *ctrl = new TimeEdit(this, -1, c, "", wxSize(GetTextExtent(wxS(" 0:00:00.000 ")).GetWidth(),-1), field == TIME_END); ctrl->SetToolTip(tooltip); Bind(wxEVT_TEXT, [=](wxCommandEvent&) { CommitTimes(field); }, ctrl->GetId()); ctrl->Bind(wxEVT_CHAR_HOOK, time_edit_char_hook); middle_left_sizer->Add(ctrl, wxSizerFlags().Center()); return ctrl; } void SubsEditBox::MakeButton(const char *cmd_name) { cmd::Command *command = cmd::get(cmd_name); wxBitmapButton *btn = new wxBitmapButton(this, -1, command->Icon(16)); ToolTipManager::Bind(btn, command->StrHelp(), "Subtitle Edit Box", cmd_name); middle_right_sizer->Add(btn, wxSizerFlags().Center().Expand()); btn->Bind(wxEVT_BUTTON, std::bind(&SubsEditBox::CallCommand, this, cmd_name)); } wxButton *SubsEditBox::MakeBottomButton(const char *cmd_name) { cmd::Command *command = cmd::get(cmd_name); wxButton *btn = new wxButton(this, -1, command->StrDisplay(c)); ToolTipManager::Bind(btn, command->StrHelp(), "Subtitle Edit Box", cmd_name); btn->Bind(wxEVT_BUTTON, std::bind(&SubsEditBox::CallCommand, this, cmd_name)); return btn; } wxComboBox *SubsEditBox::MakeComboBox(wxString const& initial_text, int style, void (SubsEditBox::*handler)(wxCommandEvent&), wxString const& tooltip) { wxString styles[] = { "Default" }; wxComboBox *ctrl = new wxComboBox(this, -1, initial_text, wxDefaultPosition, wxSize(110,-1), 1, styles, style | wxTE_PROCESS_ENTER); ctrl->SetToolTip(tooltip); top_sizer->Add(ctrl, wxSizerFlags(2).Center().Border(wxRIGHT)); Bind(wxEVT_COMBOBOX, handler, this, ctrl->GetId()); return ctrl; } wxRadioButton *SubsEditBox::MakeRadio(wxString const& text, bool start, wxString const& tooltip) { wxRadioButton *ctrl = new wxRadioButton(this, -1, text, wxDefaultPosition, wxDefaultSize, start ? wxRB_GROUP : 0); ctrl->SetToolTip(tooltip); Bind(wxEVT_RADIOBUTTON, &SubsEditBox::OnFrameTimeRadio, this, ctrl->GetId()); middle_right_sizer->Add(ctrl, wxSizerFlags().Center().Expand().Border(wxRIGHT)); return ctrl; } void SubsEditBox::OnCommit(int type) { wxEventBlocker blocker(this); initial_times.clear(); if (type == AssFile::COMMIT_NEW || type & AssFile::COMMIT_STYLES) { wxString style = style_box->GetValue(); style_box->Clear(); style_box->Append(to_wx(c->ass->GetStyles())); style_box->Select(style_box->FindString(style)); active_style = line ? c->ass->GetStyle(line->Style) : nullptr; } if (type == AssFile::COMMIT_NEW) { PopulateList(effect_box, &AssDialogue::Effect); PopulateList(actor_box, &AssDialogue::Actor); return; } else if (type & AssFile::COMMIT_STYLES) style_box->Select(style_box->FindString(to_wx(line->Style))); if (!(type ^ AssFile::COMMIT_ORDER)) return; SetControlsState(!!line); UpdateFields(type, true); } void SubsEditBox::UpdateFields(int type, bool repopulate_lists) { if (!line) return; if (type & AssFile::COMMIT_DIAG_TIME) { start_time->SetTime(line->Start); end_time->SetTime(line->End); SetDurationField(); } if (type & AssFile::COMMIT_DIAG_TEXT) { edit_ctrl->SetTextTo(line->Text); UpdateCharacterCount(line->Text); } if (type & AssFile::COMMIT_DIAG_META) { layer->SetValue(line->Layer); for (size_t i = 0; i < margin.size(); ++i) change_value(margin[i], std::to_wstring(line->Margin[i])); comment_box->SetValue(line->Comment); style_box->Select(style_box->FindString(to_wx(line->Style))); active_style = line ? c->ass->GetStyle(line->Style) : nullptr; style_edit_button->Enable(active_style != nullptr); if (repopulate_lists) PopulateList(effect_box, &AssDialogue::Effect); effect_box->ChangeValue(to_wx(line->Effect)); effect_box->SetStringSelection(to_wx(line->Effect)); if (repopulate_lists) PopulateList(actor_box, &AssDialogue::Actor); actor_box->ChangeValue(to_wx(line->Actor)); actor_box->SetStringSelection(to_wx(line->Actor)); } } void SubsEditBox::PopulateList(wxComboBox *combo, boost::flyweight AssDialogue::*field) { wxEventBlocker blocker(this); std::unordered_set> values; for (auto const& line : c->ass->Events) { auto const& value = line.*field; if (!value.get().empty()) values.insert(value); } wxArrayString arrstr; arrstr.reserve(values.size()); transform(values.begin(), values.end(), std::back_inserter(arrstr), (wxString (*)(std::string const&))to_wx); arrstr.Sort(); combo->Freeze(); long pos = combo->GetInsertionPoint(); wxString value = combo->GetValue(); combo->Set(arrstr); combo->ChangeValue(value); combo->SetStringSelection(value); combo->SetInsertionPoint(pos); combo->Thaw(); } void SubsEditBox::OnActiveLineChanged(AssDialogue *new_line) { wxEventBlocker blocker(this); line = new_line; commit_id = -1; UpdateFields(AssFile::COMMIT_DIAG_FULL, false); } void SubsEditBox::OnSelectedSetChanged() { initial_times.clear(); } void SubsEditBox::OnLineInitialTextChanged(std::string const& new_text) { if (split_box->IsChecked()) secondary_editor->SetValue(to_wx(new_text)); } void SubsEditBox::UpdateFrameTiming(agi::vfr::Framerate const& fps) { if (fps.IsLoaded()) { by_frame->Enable(true); } else { by_frame->Enable(false); by_time->SetValue(true); start_time->SetByFrame(false); end_time->SetByFrame(false); duration->SetByFrame(false); c->subsGrid->SetByFrame(false); } } void SubsEditBox::OnKeyDown(wxKeyEvent &event) { if (!osx::ime::process_key_event(edit_ctrl, event)) hotkey::check("Subtitle Edit Box", c, event); } void SubsEditBox::OnChange(wxStyledTextEvent &event) { if (line && edit_ctrl->GetTextRaw().data() != line->Text.get()) { if (event.GetModificationType() & wxSTC_STARTACTION) commit_id = -1; CommitText(_("modify text")); UpdateCharacterCount(line->Text); } } void SubsEditBox::Commit(wxString const& desc, int type, bool amend, AssDialogue *line) { file_changed_slot.Block(); commit_id = c->ass->Commit(desc, type, (amend && desc == last_commit_type) ? commit_id : -1, line); file_changed_slot.Unblock(); last_commit_type = desc; last_time_commit_type = -1; initial_times.clear(); undo_timer.Start(30000, wxTIMER_ONE_SHOT); } template void SubsEditBox::SetSelectedRows(setter set, wxString const& desc, int type, bool amend) { auto const& sel = c->selectionController->GetSelectedSet(); for_each(sel.begin(), sel.end(), set); Commit(desc, type, amend, sel.size() == 1 ? *sel.begin() : nullptr); } template void SubsEditBox::SetSelectedRows(T AssDialogueBase::*field, T value, wxString const& desc, int type, bool amend) { SetSelectedRows([&](AssDialogue *d) { d->*field = value; }, desc, type, amend); } template void SubsEditBox::SetSelectedRows(T AssDialogueBase::*field, wxString const& value, wxString const& desc, int type, bool amend) { boost::flyweight conv_value(from_wx(value)); SetSelectedRows([&](AssDialogue *d) { d->*field = conv_value; }, desc, type, amend); } void SubsEditBox::CommitText(wxString const& desc) { auto data = edit_ctrl->GetTextRaw(); SetSelectedRows(&AssDialogue::Text, boost::flyweight(data.data(), data.length()), desc, AssFile::COMMIT_DIAG_TEXT, true); } void SubsEditBox::CommitTimes(TimeField field) { auto const& sel = c->selectionController->GetSelectedSet(); for (AssDialogue *d : sel) { if (!initial_times.count(d)) initial_times[d] = {d->Start, d->End}; switch (field) { case TIME_START: initial_times[d].first = d->Start = start_time->GetTime(); d->End = std::max(d->Start, initial_times[d].second); break; case TIME_END: initial_times[d].second = d->End = end_time->GetTime(); d->Start = std::min(d->End, initial_times[d].first); break; case TIME_DURATION: if (by_frame->GetValue()) { auto const& fps = c->project->Timecodes(); d->End = fps.TimeAtFrame(fps.FrameAtTime(d->Start, agi::vfr::START) + duration->GetFrame() - 1, agi::vfr::END); } else d->End = d->Start + duration->GetTime(); initial_times[d].second = d->End; break; } } start_time->SetTime(line->Start); end_time->SetTime(line->End); if (field != TIME_DURATION) SetDurationField(); if (field != last_time_commit_type) commit_id = -1; last_time_commit_type = field; file_changed_slot.Block(); commit_id = c->ass->Commit(_("modify times"), AssFile::COMMIT_DIAG_TIME, commit_id, sel.size() == 1 ? *sel.begin() : nullptr); file_changed_slot.Unblock(); } void SubsEditBox::SetDurationField() { // With VFR, the frame count calculated from the duration in time can be // completely wrong (since the duration is calculated as if it were a start // time), so we need to explicitly set it with the correct units. if (by_frame->GetValue()) duration->SetFrame(end_time->GetFrame() - start_time->GetFrame() + 1); else duration->SetTime(end_time->GetTime() - start_time->GetTime()); } void SubsEditBox::OnSize(wxSizeEvent &evt) { int availableWidth = GetVirtualSize().GetWidth(); int midMin = middle_left_sizer->GetMinSize().GetWidth(); int botMin = middle_right_sizer->GetMinSize().GetWidth(); if (button_bar_split) { if (availableWidth > midMin + botMin) { GetSizer()->Detach(middle_right_sizer); middle_left_sizer->Add(middle_right_sizer,0,wxALIGN_CENTER_VERTICAL); button_bar_split = false; } } else { if (availableWidth < midMin) { middle_left_sizer->Detach(middle_right_sizer); GetSizer()->Insert(2,middle_right_sizer,0,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,3); button_bar_split = true; } } evt.Skip(); } void SubsEditBox::OnFrameTimeRadio(wxCommandEvent &event) { event.Skip(); bool byFrame = by_frame->GetValue(); start_time->SetByFrame(byFrame); end_time->SetByFrame(byFrame); duration->SetByFrame(byFrame); c->subsGrid->SetByFrame(byFrame); SetDurationField(); } void SubsEditBox::SetControlsState(bool state) { if (state == controls_enabled) return; controls_enabled = state; Enable(state); if (!state) { wxEventBlocker blocker(this); edit_ctrl->SetTextTo(""); } } void SubsEditBox::OnSplit(wxCommandEvent&) { Freeze(); GetSizer()->Show(secondary_editor, split_box->IsChecked()); GetSizer()->Show(bottom_sizer, split_box->IsChecked()); Fit(); SetMinSize(GetSize()); GetParent()->GetSizer()->Layout(); Thaw(); if (split_box->IsChecked()) secondary_editor->SetValue(to_wx(c->initialLineState->GetInitialText())); } void SubsEditBox::OnStyleChange(wxCommandEvent &evt) { SetSelectedRows(&AssDialogue::Style, new_value(style_box, evt), _("style change"), AssFile::COMMIT_DIAG_META); active_style = c->ass->GetStyle(line->Style); } void SubsEditBox::OnActorChange(wxCommandEvent &evt) { bool amend = evt.GetEventType() == wxEVT_TEXT; SetSelectedRows(&AssDialogue::Actor, new_value(actor_box, evt), _("actor change"), AssFile::COMMIT_DIAG_META, amend); PopulateList(actor_box, &AssDialogue::Actor); } void SubsEditBox::OnLayerEnter(wxCommandEvent &evt) { SetSelectedRows(&AssDialogue::Layer, evt.GetInt(), _("layer change"), AssFile::COMMIT_DIAG_META); } void SubsEditBox::OnEffectChange(wxCommandEvent &evt) { bool amend = evt.GetEventType() == wxEVT_TEXT; SetSelectedRows(&AssDialogue::Effect, new_value(effect_box, evt), _("effect change"), AssFile::COMMIT_DIAG_META, amend); PopulateList(effect_box, &AssDialogue::Effect); } void SubsEditBox::OnCommentChange(wxCommandEvent &evt) { SetSelectedRows(&AssDialogue::Comment, !!evt.GetInt(), _("comment change"), AssFile::COMMIT_DIAG_META); } void SubsEditBox::CallCommand(const char *cmd_name) { cmd::call(cmd_name, c); edit_ctrl->SetFocus(); } void SubsEditBox::UpdateCharacterCount(std::string const& text) { int ignore = agi::IGNORE_BLOCKS; if (OPT_GET("Subtitle/Character Counter/Ignore Whitespace")->GetBool()) ignore |= agi::IGNORE_WHITESPACE; if (OPT_GET("Subtitle/Character Counter/Ignore Punctuation")->GetBool()) ignore |= agi::IGNORE_PUNCTUATION; size_t length = agi::MaxLineLength(text, ignore); char_count->SetValue(std::to_wstring(length)); size_t limit = (size_t)OPT_GET("Subtitle/Character Limit")->GetInt(); if (limit && length > limit) char_count->SetBackgroundColour(to_wx(OPT_GET("Colour/Subtitle/Syntax/Background/Error")->GetColor())); else char_count->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); }