diff --git a/aegisub/src/agi_pre.h b/aegisub/src/agi_pre.h index 1e7a8830f..f742e0575 100644 --- a/aegisub/src/agi_pre.h +++ b/aegisub/src/agi_pre.h @@ -191,6 +191,7 @@ #include #include #include +#include #include #include #include diff --git a/aegisub/src/dialog_timing_processor.cpp b/aegisub/src/dialog_timing_processor.cpp index a994a110e..6426c55e0 100644 --- a/aegisub/src/dialog_timing_processor.cpp +++ b/aegisub/src/dialog_timing_processor.cpp @@ -36,142 +36,173 @@ #include "config.h" +#include "dialog_timing_processor.h" + #ifndef AGI_PRE #include -#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include #endif #include "ass_dialogue.h" #include "ass_file.h" #include "ass_time.h" -#include "dialog_timing_processor.h" #include "help_button.h" #include "include/aegisub/context.h" #include "libresrc/libresrc.h" #include "main.h" #include "selection_controller.h" #include "utils.h" -#include "validators.h" #include "video_context.h" -/// Window IDs -enum { - CHECK_ENABLE_LEADIN = 1850, - CHECK_ENABLE_LEADOUT, - CHECK_ENABLE_KEYFRAME, - CHECK_ENABLE_ADJASCENT, - BUTTON_SELECT_ALL, - BUTTON_SELECT_NONE, - TIMING_STYLE_LIST -}; +namespace { +using std::tr1::placeholders::_1; + +void set_ctrl_state(wxCommandEvent &evt, wxCheckBox *cb, wxTextCtrl *tc) { + tc->Enable(cb->IsChecked()); + evt.Skip(); +} + +wxTextCtrl *make_ctrl(wxWindow *parent, wxSizer *sizer, wxString const& desc, int *value, wxCheckBox *cb, wxString const& tooltip) { + wxIntegerValidator validator(value); + validator.SetMin(0); + wxTextCtrl *ctrl = new wxTextCtrl(parent, -1, "", wxDefaultPosition, wxSize(60,-1), 0, validator); + ctrl->SetToolTip(tooltip); + if (!desc.empty()) + sizer->Add(new wxStaticText(parent, -1, desc), wxSizerFlags().Center().Border(wxRIGHT)); + sizer->Add(ctrl, wxSizerFlags().Expand().Border(wxRIGHT)); + + ctrl->Enable(cb->IsChecked()); + cb->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, bind(set_ctrl_state, _1, cb, ctrl)); + + return ctrl; +} + +inline wxTextCtrl *make_ctrl(wxStaticBoxSizer *sizer, wxString const& desc, int *value, wxCheckBox *cb, wxString const& tooltip) { + return make_ctrl(sizer->GetStaticBox()->GetParent(), sizer, desc, value, cb, tooltip); +} + +wxCheckBox *make_check(wxStaticBoxSizer *sizer, wxString const& desc, const char *opt, wxString const& tooltip) { + wxCheckBox *cb = new wxCheckBox(sizer->GetStaticBox()->GetParent(), -1, desc); + cb->SetToolTip(tooltip); + cb->SetValue(OPT_GET(opt)->GetBool()); + sizer->Add(cb, wxSizerFlags().Border(wxRIGHT).Expand()); + return cb; +} +} DialogTimingProcessor::DialogTimingProcessor(agi::Context *c) -: wxDialog(c->parent,-1,_("Timing Post-Processor"),wxDefaultPosition,wxSize(400,250),wxDEFAULT_DIALOG_STYLE) +: wxDialog(c->parent, -1, _("Timing Post-Processor")) , c(c) { + using std::tr1::bind; + SetIcon(BitmapToIcon(GETIMAGE(timing_processor_toolbutton_24))); - // Set variables - wxString leadInTime(wxString::Format("%" PRId64, OPT_GET("Audio/Lead/IN")->GetInt())); - wxString leadOutTime(wxString::Format("%" PRId64, OPT_GET("Audio/Lead/OUT")->GetInt())); - wxString thresStartBefore(wxString::Format("%" PRId64, OPT_GET("Tool/Timing Post Processor/Threshold/Key Start Before")->GetInt())); - wxString thresStartAfter(wxString::Format("%" PRId64, OPT_GET("Tool/Timing Post Processor/Threshold/Key Start After")->GetInt())); - wxString thresEndBefore(wxString::Format("%" PRId64, OPT_GET("Tool/Timing Post Processor/Threshold/Key End Before")->GetInt())); - wxString thresEndAfter(wxString::Format("%" PRId64, OPT_GET("Tool/Timing Post Processor/Threshold/Key End After")->GetInt())); - wxString adjsThresTime(wxString::Format("%" PRId64, OPT_GET("Tool/Timing Post Processor/Threshold/Adjacent")->GetInt())); + // Read options + leadIn = OPT_GET("Audio/Lead/IN")->GetInt(); + leadOut = OPT_GET("Audio/Lead/OUT")->GetInt(); + beforeStart = OPT_GET("Tool/Timing Post Processor/Threshold/Key Start Before")->GetInt(); + beforeEnd = OPT_GET("Tool/Timing Post Processor/Threshold/Key End Before")->GetInt(); + afterStart = OPT_GET("Tool/Timing Post Processor/Threshold/Key Start After")->GetInt(); + afterEnd = OPT_GET("Tool/Timing Post Processor/Threshold/Key End After")->GetInt(); + adjDistance = OPT_GET("Tool/Timing Post Processor/Threshold/Adjacent")->GetInt(); -// Styles box + // Styles box wxSizer *LeftSizer = new wxStaticBoxSizer(wxVERTICAL,this,_("Apply to styles")); - wxArrayString styles = c->ass->GetStyles(); - StyleList = new wxCheckListBox(this,TIMING_STYLE_LIST,wxDefaultPosition,wxSize(150,150),styles); + StyleList = new wxCheckListBox(this, -1, wxDefaultPosition, wxSize(150,150), c->ass->GetStyles()); StyleList->SetToolTip(_("Select styles to process. Unchecked ones will be ignored.")); - wxButton *all = new wxButton(this,BUTTON_SELECT_ALL,_("&All")); + + wxButton *all = new wxButton(this,-1,_("&All")); all->SetToolTip(_("Select all styles.")); - wxButton *none = new wxButton(this,BUTTON_SELECT_NONE,_("&None")); + + wxButton *none = new wxButton(this,-1,_("&None")); none->SetToolTip(_("Deselect all styles.")); // Options box - wxSizer *optionsSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Options")); + wxStaticBoxSizer *optionsSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Options")); onlySelection = new wxCheckBox(this,-1,_("Affect &selection only")); onlySelection->SetValue(OPT_GET("Tool/Timing Post Processor/Only Selection")->GetBool()); optionsSizer->Add(onlySelection,1,wxALL,0); // Lead-in/out box - wxSizer *LeadSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Lead-in/Lead-out")); - hasLeadIn = new wxCheckBox(this,CHECK_ENABLE_LEADIN,_("Add lead &in:")); - hasLeadIn->SetToolTip(_("Enable adding of lead-ins to lines.")); - hasLeadIn->SetValue(OPT_GET("Tool/Timing Post Processor/Enable/Lead/IN")->GetBool()); - leadIn = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(80,-1),0,NumValidator(leadInTime)); - leadIn->SetToolTip(_("Lead in to be added, in milliseconds.")); - hasLeadOut = new wxCheckBox(this,CHECK_ENABLE_LEADOUT,_("Add lead &out:")); - hasLeadOut->SetToolTip(_("Enable adding of lead-outs to lines.")); - hasLeadOut->SetValue(OPT_GET("Tool/Timing Post Processor/Enable/Lead/OUT")->GetBool()); - leadOut = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(80,-1),0,NumValidator(leadOutTime)); - leadOut->SetToolTip(_("Lead out to be added, in milliseconds.")); - LeadSizer->Add(hasLeadIn,0,wxRIGHT|wxEXPAND,5); - LeadSizer->Add(leadIn,0,wxRIGHT|wxEXPAND,5); - LeadSizer->Add(hasLeadOut,0,wxRIGHT|wxEXPAND,5); - LeadSizer->Add(leadOut,0,wxRIGHT|wxEXPAND,0); + wxStaticBoxSizer *LeadSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Lead-in/Lead-out")); + + hasLeadIn = make_check(LeadSizer, _("Add lead &in:"), + "Tool/Timing Post Processor/Enable/Lead/IN", + _("Enable adding of lead-ins to lines.")); + make_ctrl(LeadSizer, "", &leadIn, hasLeadIn, _("Lead in to be added, in milliseconds.")); + + hasLeadOut = make_check(LeadSizer, _("Add lead &out:"), + "Tool/Timing Post Processor/Enable/Lead/OUT", + _("Enable adding of lead-outs to lines.")); + make_ctrl(LeadSizer, "", &leadOut, hasLeadOut, _("Lead out to be added, in milliseconds.")); + LeadSizer->AddStretchSpacer(1); // Adjacent subs sizer - wxSizer *AdjacentSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Make adjacent subtitles continuous")); - adjsEnable = new wxCheckBox(this,CHECK_ENABLE_ADJASCENT,_("&Enable")); - adjsEnable->SetToolTip(_("Enable snapping of subtitles together if they are within a certain distance of each other.")); - adjsEnable->SetValue(OPT_GET("Tool/Timing Post Processor/Enable/Adjacent")->GetBool()); - wxStaticText *adjsThresText = new wxStaticText(this,-1,_("Threshold:"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE); - adjacentThres = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(60,-1),0,NumValidator(adjsThresTime)); - adjacentThres->SetToolTip(_("Maximum difference between start and end time for two subtitles to be made continuous, in milliseconds.")); - adjacentBias = new wxSlider(this,-1,mid(0,int(OPT_GET("Tool/Timing Post Processor/Adjacent Bias")->GetDouble()*100),100),0,100,wxDefaultPosition,wxSize(-1,20)); + wxStaticBoxSizer *AdjacentSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Make adjacent subtitles continuous")); + adjsEnable = make_check(AdjacentSizer, _("&Enable"), + "Tool/Timing Post Processor/Enable/Adjacent", + _("Enable snapping of subtitles together if they are within a certain distance of each other.")); + + make_ctrl(AdjacentSizer, _("Threshold:"), &adjDistance, adjsEnable, + _("Maximum difference between start and end time for two subtitles to be made continuous, in milliseconds.")); + + adjacentBias = new wxSlider(this, -1, mid(0, OPT_GET("Tool/Timing Post Processor/Adjacent Bias")->GetDouble() * 100, 100), 0, 100, wxDefaultPosition, wxSize(-1,20)); adjacentBias->SetToolTip(_("Sets how to set the adjoining of lines. If set totally to left, it will extend start time of the second line; if totally to right, it will extend the end time of the first line.")); - AdjacentSizer->Add(adjsEnable,0,wxRIGHT|wxEXPAND,10); - AdjacentSizer->Add(adjsThresText,0,wxRIGHT|wxALIGN_CENTER,5); - AdjacentSizer->Add(adjacentThres,0,wxRIGHT|wxEXPAND,5); - AdjacentSizer->Add(new wxStaticText(this,-1,_("Bias: Start <- "),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE),0,wxALIGN_CENTER,0); - AdjacentSizer->Add(adjacentBias,1,wxEXPAND,0); - AdjacentSizer->Add(new wxStaticText(this,-1,_(" -> End"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE),0,wxALIGN_CENTER,0); + + AdjacentSizer->Add(new wxStaticText(this, -1, _("Bias: Start <- ")), wxSizerFlags().Center()); + AdjacentSizer->Add(adjacentBias, wxSizerFlags(1).Expand().Center()); + AdjacentSizer->Add(new wxStaticText(this, -1, _(" -> End")), wxSizerFlags().Center()); // Keyframes sizer - KeyframesSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Keyframe snapping")); + wxStaticBoxSizer *KeyframesSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Keyframe snapping")); wxSizer *KeyframesFlexSizer = new wxFlexGridSizer(2,5,5,0); - keysEnable = new wxCheckBox(this,CHECK_ENABLE_KEYFRAME,_("E&nable")); + + keysEnable = new wxCheckBox(this, -1, _("E&nable")); keysEnable->SetToolTip(_("Enable snapping of subtitles to nearest keyframe, if distance is within threshold.")); keysEnable->SetValue(OPT_GET("Tool/Timing Post Processor/Enable/Keyframe")->GetBool()); - wxStaticText *textStartBefore = new wxStaticText(this,-1,_("Starts before thres.:"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE); - keysStartBefore = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(60,-1),0,NumValidator(thresStartBefore)); - keysStartBefore->SetToolTip(_("Threshold for 'before start' distance, that is, how many frames a subtitle must start before a keyframe to snap to it.")); - wxStaticText *textStartAfter = new wxStaticText(this,-1,_("Starts after thres.:"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE); - keysStartAfter = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(60,-1),0,NumValidator(thresStartAfter)); - keysStartAfter->SetToolTip(_("Threshold for 'after start' distance, that is, how many frames a subtitle must start after a keyframe to snap to it.")); - wxStaticText *textEndBefore = new wxStaticText(this,-1,_("Ends before thres.:"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE); - keysEndBefore = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(60,-1),0,NumValidator(thresEndBefore)); - keysEndBefore->SetToolTip(_("Threshold for 'before end' distance, that is, how many frames a subtitle must end before a keyframe to snap to it.")); - wxStaticText *textEndAfter = new wxStaticText(this,-1,_("Ends after thres.:"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE); - keysEndAfter = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(60,-1),0,NumValidator(thresEndAfter)); - keysEndAfter->SetToolTip(_("Threshold for 'after end' distance, that is, how many frames a subtitle must end after a keyframe to snap to it.")); KeyframesFlexSizer->Add(keysEnable,0,wxRIGHT|wxEXPAND,10); - KeyframesFlexSizer->Add(textStartBefore,0,wxRIGHT|wxALIGN_CENTER,5); - KeyframesFlexSizer->Add(keysStartBefore,0,wxRIGHT|wxEXPAND,5); - KeyframesFlexSizer->Add(textStartAfter,0,wxRIGHT|wxALIGN_CENTER,5); - KeyframesFlexSizer->Add(keysStartAfter,0,wxRIGHT|wxEXPAND,0); + + // Keyframes are only available if timecodes are loaded + bool keysAvailable = c->videoController->KeyFramesLoaded() && c->videoController->TimecodesLoaded(); + if (!keysAvailable) { + keysEnable->SetValue(false); + keysEnable->Enable(false); + } + + make_ctrl(this, KeyframesFlexSizer, _("Starts before thres.:"), &beforeStart, keysEnable, + _("Threshold for 'before start' distance, that is, how many frames a subtitle must start before a keyframe to snap to it.")); + + make_ctrl(this, KeyframesFlexSizer, _("Starts after thres.:"), &afterStart, keysEnable, + _("Threshold for 'after start' distance, that is, how many frames a subtitle must start after a keyframe to snap to it.")); + KeyframesFlexSizer->AddStretchSpacer(1); - KeyframesFlexSizer->Add(textEndBefore,0,wxRIGHT|wxALIGN_CENTER,5); - KeyframesFlexSizer->Add(keysEndBefore,0,wxRIGHT|wxEXPAND,5); - KeyframesFlexSizer->Add(textEndAfter,0,wxRIGHT|wxALIGN_CENTER,5); - KeyframesFlexSizer->Add(keysEndAfter,0,wxRIGHT|wxEXPAND,0); + + make_ctrl(this, KeyframesFlexSizer, _("Ends before thres.:"), &beforeEnd, keysEnable, + _("Threshold for 'before end' distance, that is, how many frames a subtitle must end before a keyframe to snap to it.")); + + make_ctrl(this, KeyframesFlexSizer, _("Ends after thres.:"), &afterEnd, keysEnable, + _("Threshold for 'after end' distance, that is, how many frames a subtitle must end after a keyframe to snap to it.")); + KeyframesSizer->Add(KeyframesFlexSizer,0,wxEXPAND); KeyframesSizer->AddStretchSpacer(1); // Button sizer - wxStdDialogButtonSizer *ButtonSizer = new wxStdDialogButtonSizer(); - ApplyButton = new wxButton(this,wxID_OK); - ButtonSizer->AddButton(ApplyButton); - ButtonSizer->AddButton(new wxButton(this,wxID_CANCEL)); - ButtonSizer->AddButton(new HelpButton(this,"Timing Processor")); - ButtonSizer->Realize(); + wxStdDialogButtonSizer *ButtonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP); + ApplyButton = ButtonSizer->GetAffirmativeButton(); + ButtonSizer->GetHelpButton()->Bind(wxEVT_COMMAND_BUTTON_CLICKED, bind(&HelpButton::OpenPage, "Timing Processor")); // Right Sizer wxSizer *RightSizer = new wxBoxSizer(wxVERTICAL); @@ -188,12 +219,8 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c) StyleButtonsSizer->Add(none,1,0,0); // Left sizer - size_t len = StyleList->GetCount(); - for (size_t i=0;iCheck(i); - } - LeftSizer->Add(StyleList,1,wxBOTTOM|wxEXPAND,0); - LeftSizer->Add(StyleButtonsSizer,0,wxEXPAND,0); + LeftSizer->Add(StyleList, wxSizerFlags(1).Border(wxBOTTOM)); + LeftSizer->Add(StyleButtonsSizer, wxSizerFlags().Expand()); // Top Sizer wxSizer *TopSizer = new wxBoxSizer(wxHORIZONTAL); @@ -203,92 +230,46 @@ DialogTimingProcessor::DialogTimingProcessor(agi::Context *c) // Main Sizer wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL); MainSizer->Add(TopSizer,1,wxALL|wxEXPAND,5); - MainSizer->SetSizeHints(this); - SetSizer(MainSizer); - + SetSizerAndFit(MainSizer); CenterOnParent(); + Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, bind(&DialogTimingProcessor::UpdateControls, this)); + Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogTimingProcessor::OnApply, this, wxID_OK); + all->Bind(wxEVT_COMMAND_BUTTON_CLICKED, bind(&DialogTimingProcessor::CheckAll, this, true)); + none->Bind(wxEVT_COMMAND_BUTTON_CLICKED, bind(&DialogTimingProcessor::CheckAll, this, false)); + + CheckAll(true); +} + +void DialogTimingProcessor::CheckAll(bool value) { + size_t count = StyleList->GetCount(); + for (size_t i = 0; i < count; ++i) + StyleList->Check(i, value); UpdateControls(); } -/// @brief Update controls -/// void DialogTimingProcessor::UpdateControls() { - // Boxes - leadIn->Enable(hasLeadIn->IsChecked()); - leadOut->Enable(hasLeadOut->IsChecked()); - adjacentThres->Enable(adjsEnable->IsChecked()); - adjacentBias->Enable(adjsEnable->IsChecked()); - - // Keyframes are only available if timecodes are loaded - bool keysAvailable = c->videoController->KeyFramesLoaded(); - bool enableKeys = keysEnable->IsChecked() && keysAvailable; - keysStartBefore->Enable(enableKeys); - keysStartAfter->Enable(enableKeys); - keysEndBefore->Enable(enableKeys); - keysEndAfter->Enable(enableKeys); - if (!keysAvailable) { - keysEnable->SetValue(false); - keysEnable->Enable(false); - } - - // Apply button - int checked = 0; + // Only enable the OK button if it'll actually do something + bool any_checked = false; size_t len = StyleList->GetCount(); - for (size_t i=0;iIsChecked(i)) checked++; + for (size_t i = 0; i < len; ++i) { + if (StyleList->IsChecked(i)) { + any_checked = true; + break; + } } - ApplyButton->Enable(checked && (hasLeadIn->IsChecked() || hasLeadOut->IsChecked() || keysEnable->IsChecked() || adjsEnable->IsChecked())); -} - -BEGIN_EVENT_TABLE(DialogTimingProcessor,wxDialog) - EVT_CHECKBOX(CHECK_ENABLE_LEADIN,DialogTimingProcessor::OnCheckBox) - EVT_CHECKBOX(CHECK_ENABLE_LEADOUT,DialogTimingProcessor::OnCheckBox) - EVT_CHECKBOX(CHECK_ENABLE_KEYFRAME,DialogTimingProcessor::OnCheckBox) - EVT_CHECKBOX(CHECK_ENABLE_ADJASCENT,DialogTimingProcessor::OnCheckBox) - EVT_CHECKLISTBOX(TIMING_STYLE_LIST,DialogTimingProcessor::OnCheckBox) - EVT_BUTTON(wxID_OK,DialogTimingProcessor::OnApply) - EVT_BUTTON(BUTTON_SELECT_ALL,DialogTimingProcessor::OnSelectAll) - EVT_BUTTON(BUTTON_SELECT_NONE,DialogTimingProcessor::OnSelectNone) -END_EVENT_TABLE() - -void DialogTimingProcessor::OnCheckBox(wxCommandEvent &) { - UpdateControls(); -} - -void DialogTimingProcessor::OnSelectAll(wxCommandEvent &) { - size_t len = StyleList->GetCount(); - for (size_t i=0;iCheck(i); - } - UpdateControls(); -} - -void DialogTimingProcessor::OnSelectNone(wxCommandEvent &) { - size_t len = StyleList->GetCount(); - for (size_t i=0;iCheck(i,false); - } - UpdateControls(); + ApplyButton->Enable(any_checked && (hasLeadIn->IsChecked() || hasLeadOut->IsChecked() || keysEnable->IsChecked() || adjsEnable->IsChecked())); } void DialogTimingProcessor::OnApply(wxCommandEvent &) { // Save settings - long temp = 0; - leadIn->GetValue().ToLong(&temp); - OPT_SET("Audio/Lead/IN")->SetInt(temp); - leadOut->GetValue().ToLong(&temp); - OPT_SET("Audio/Lead/OUT")->SetInt(temp); - keysStartBefore->GetValue().ToLong(&temp); - OPT_SET("Tool/Timing Post Processor/Threshold/Key Start Before")->SetInt(temp); - keysStartAfter->GetValue().ToLong(&temp); - OPT_SET("Tool/Timing Post Processor/Threshold/Key Start After")->SetInt(temp); - keysEndBefore->GetValue().ToLong(&temp); - OPT_SET("Tool/Timing Post Processor/Threshold/Key End Before")->SetInt(temp); - keysEndAfter->GetValue().ToLong(&temp); - OPT_SET("Tool/Timing Post Processor/Threshold/Key End After")->SetInt(temp); - adjacentThres->GetValue().ToLong(&temp); - OPT_SET("Tool/Timing Post Processor/Threshold/Adjacent")->SetInt(temp); + OPT_SET("Audio/Lead/IN")->SetInt(leadIn); + OPT_SET("Audio/Lead/OUT")->SetInt(leadOut); + OPT_SET("Tool/Timing Post Processor/Threshold/Key Start Before")->SetInt(beforeStart); + OPT_SET("Tool/Timing Post Processor/Threshold/Key Start After")->SetInt(afterStart); + OPT_SET("Tool/Timing Post Processor/Threshold/Key End Before")->SetInt(beforeEnd); + OPT_SET("Tool/Timing Post Processor/Threshold/Key End After")->SetInt(afterEnd); + OPT_SET("Tool/Timing Post Processor/Threshold/Adjacent")->SetInt(adjDistance); OPT_SET("Tool/Timing Post Processor/Adjacent Bias")->SetDouble(adjacentBias->GetValue() / 100.0); OPT_SET("Tool/Timing Post Processor/Enable/Lead/IN")->SetBool(hasLeadIn->IsChecked()); OPT_SET("Tool/Timing Post Processor/Enable/Lead/OUT")->SetBool(hasLeadOut->IsChecked()); @@ -296,174 +277,137 @@ void DialogTimingProcessor::OnApply(wxCommandEvent &) { OPT_SET("Tool/Timing Post Processor/Enable/Adjacent")->SetBool(adjsEnable->IsChecked()); OPT_SET("Tool/Timing Post Processor/Only Selection")->SetBool(onlySelection->IsChecked()); - // Check if rows are valid - for (entryIter cur = c->ass->Line.begin(); cur != c->ass->Line.end(); ++cur) { - if (AssDialogue *tempDiag = dynamic_cast(*cur)) { - if (tempDiag->Start > tempDiag->End) { - wxMessageBox( - wxString::Format( - _("One of the lines in the file (%i) has negative duration. Aborting."), - std::distance(c->ass->Line.begin(), cur)), - _("Invalid script"), - wxICON_ERROR|wxOK); - EndModal(0); - return; - } - } - } - Process(); EndModal(0); } -int DialogTimingProcessor::GetClosestKeyFrame(int frame) { - std::vector::iterator pos = lower_bound(KeyFrames.begin(), KeyFrames.end(), frame); - if (distance(pos, KeyFrames.end()) < 2) return KeyFrames.back(); - return frame - *pos < *(pos + 1) - frame ? *pos : *(pos + 1); -} - static bool bad_line(std::set *styles, AssDialogue *d) { return !d || d->Comment || styles->find(d->Style) == styles->end(); } -void DialogTimingProcessor::SortDialogues() { +std::vector DialogTimingProcessor::SortDialogues() { std::set styles; for (size_t i = 0; i < StyleList->GetCount(); ++i) { - if (StyleList->IsChecked(i)) { + if (StyleList->IsChecked(i)) styles.insert(StyleList->GetString(i)); + } + + std::vector sorted; + sorted.reserve(c->ass->Line.size()); + + if (onlySelection->IsChecked()) { + SelectionController::Selection sel = c->selectionController->GetSelectedSet(); + remove_copy_if(sel.begin(), sel.end(), back_inserter(sorted), + bind(bad_line, &styles, _1)); + } + else { + transform(c->ass->Line.begin(), c->ass->Line.end(), back_inserter(sorted), cast()); + sorted.erase(remove_if(sorted.begin(), sorted.end(), bind(bad_line, &styles, _1)), sorted.end()); + } + + // Check if rows are valid + for (size_t i = 0; i < sorted.size(); ++i) { + if (sorted[i]->Start > sorted[i]->End) { + wxMessageBox( + wxString::Format(_("One of the lines in the file (%i) has negative duration. Aborting."), i), + _("Invalid script"), + wxICON_ERROR|wxOK); + sorted.clear(); + break; } } - Sorted.clear(); - Sorted.reserve(c->ass->Line.size()); - if (onlySelection->IsChecked()) { - SelectionController::Selection sel = c->selectionController->GetSelectedSet(); - remove_copy_if(sel.begin(), sel.end(), back_inserter(Sorted), - bind(bad_line, &styles, std::tr1::placeholders::_1)); - } - else { - std::vector tmp(c->ass->Line.size()); - transform(c->ass->Line.begin(), c->ass->Line.end(), back_inserter(tmp), cast()); - remove_copy_if(tmp.begin(), tmp.end(), back_inserter(Sorted), - bind(bad_line, &styles, std::tr1::placeholders::_1)); - } - sort(Sorted.begin(), Sorted.end(), AssFile::CompStart); + sort(sorted.begin(), sorted.end(), AssFile::CompStart); + return sorted; +} + +static int get_closest_kf(std::vector const& kf, int frame) { + std::vector::const_iterator pos = lower_bound(kf.begin(), kf.end(), frame); + // Return last keyframe if this is on or after the last one + if (distance(pos, kf.end()) < 2) return kf.back(); + // Check if this one or the one after is closer to the frame + return frame - *pos < *(pos + 1) - frame ? *pos : *(pos + 1); +} + +template +static int safe_time(Iter begin, Iter end, AssDialogue *comp, int initial, Field field, int const& (*cmp)(int const&, int const&)) { + // Compare to every previous line (yay for O(n^2)!) to see if it's OK to add lead-in + for (; begin != end; ++begin) { + // If the line doesn't already collide with this line, extend it only + // to the edge of the line + if (!comp->CollidesWith(*begin)) + initial = cmp(initial, (*begin)->*field); + } + return initial; } -/// @brief Actually process subtitles -/// void DialogTimingProcessor::Process() { - SortDialogues(); - int rows = Sorted.size(); + std::vector sorted = SortDialogues(); + if (sorted.empty()) return; // Options - long inVal = 0; - long outVal = 0; - leadIn->GetValue().ToLong(&inVal); - leadOut->GetValue().ToLong(&outVal); - bool addIn = hasLeadIn->IsChecked() && inVal; - bool addOut = hasLeadOut->IsChecked() && outVal; + bool addIn = hasLeadIn->IsChecked() && leadIn; + bool addOut = hasLeadOut->IsChecked() && leadOut; // Add lead-in/out if (addIn || addOut) { - for (int i=0;iStart = safe_time(sorted.begin() + i + 1, sorted.end(), cur, cur->Start - leadIn, &AssDialogue::End, &std::max); - // Compare to every previous line (yay for O(n^2)!) to see if it's OK to add lead-in - if (inVal) { - int startLead = cur->Start - inVal; - for (int j=0;jCollidesWith(comp)) continue; - - // Get comparison times - startLead = std::max(startLead, comp->End); - } - cur->Start = startLead; - } - - // Compare to every line to see how far can lead-out be extended - if (outVal) { - int endLead = cur->End + outVal; - for (int j=i+1;jCollidesWith(comp)) continue; - - // Get comparison times - endLead = std::min(endLead, comp->Start); - } - cur->End = endLead; - } + if (addOut) + cur->End = safe_time(sorted.rend() - i, sorted.rend(), cur, cur->End + leadOut, &AssDialogue::Start, &std::min); } } // Make adjacent if (adjsEnable->IsChecked()) { - AssDialogue *prev = Sorted.front(); - - long adjsThres = 0; - adjacentThres->GetValue().ToLong(&adjsThres); + double bias = adjacentBias->GetValue() / 100.0; - float bias = adjacentBias->GetValue() / 100.0; - - for (int i=1; i < rows;i++) { - AssDialogue *cur = Sorted[i]; + for (size_t i = 1; i < sorted.size(); ++i) { + AssDialogue *prev = sorted[i - 1]; + AssDialogue *cur = sorted[i]; // Check if they don't collide if (cur->CollidesWith(prev)) continue; // Compare distance int dist = cur->Start - prev->End; - if (dist > 0 && dist <= adjsThres) { - int setPos = prev->End + int(dist*bias); + if (dist > 0 && dist <= adjDistance) { + int setPos = prev->End + int(dist * bias); cur->Start = setPos; prev->End = setPos; } - - prev = cur; } } // Keyframe snapping if (keysEnable->IsChecked()) { - KeyFrames = c->videoController->GetKeyFrames(); + std::vector kf = c->videoController->GetKeyFrames(); if (c->videoController->IsLoaded()) - KeyFrames.push_back(c->videoController->GetLength() - 1); + kf.push_back(c->videoController->GetLength() - 1); - long beforeStart = 0; - long afterStart = 0; - long beforeEnd = 0; - long afterEnd = 0; - keysStartBefore->GetValue().ToLong(&beforeStart); - keysStartAfter->GetValue().ToLong(&afterStart); - keysEndBefore->GetValue().ToLong(&beforeEnd); - keysEndAfter->GetValue().ToLong(&afterEnd); - - for (int i=0;ivideoController->FrameAtTime(cur->Start,agi::vfr::START); - int endF = c->videoController->FrameAtTime(cur->End,agi::vfr::END); + int startF = c->videoController->FrameAtTime(cur->Start, agi::vfr::START); + int endF = c->videoController->FrameAtTime(cur->End, agi::vfr::END); // Get closest for start - int closest = GetClosestKeyFrame(startF); + int closest = get_closest_kf(kf, startF); if ((closest > startF && closest-startF <= beforeStart) || (closest < startF && startF-closest <= afterStart)) { - cur->Start = c->videoController->TimeAtFrame(closest,agi::vfr::START); + cur->Start = c->videoController->TimeAtFrame(closest, agi::vfr::START); } // Get closest for end - closest = GetClosestKeyFrame(endF)-1; + closest = get_closest_kf(kf, endF) - 1; if ((closest > endF && closest-endF <= beforeEnd) || (closest < endF && endF-closest <= afterEnd)) { - cur->End = c->videoController->TimeAtFrame(closest,agi::vfr::END); + cur->End = c->videoController->TimeAtFrame(closest, agi::vfr::END); } } } - // Update grid c->ass->Commit(_("timing processor"), AssFile::COMMIT_DIAG_TIME); } diff --git a/aegisub/src/dialog_timing_processor.h b/aegisub/src/dialog_timing_processor.h index d150fdd2c..9e9d31422 100644 --- a/aegisub/src/dialog_timing_processor.h +++ b/aegisub/src/dialog_timing_processor.h @@ -37,92 +37,54 @@ #ifndef AGI_PRE #include -#include -#include -#include #include -#include -#include -#include #endif namespace agi { struct Context; } class AssDialogue; +class wxButton; +class wxCheckBox; +class wxCheckListBox; +class wxSlider; -/// DOCME /// @class DialogTimingProcessor -/// @brief DOCME -/// -/// DOCME +/// @brief Automatic postprocessor for correcting common timing issues class DialogTimingProcessor : public wxDialog { - agi::Context *c; + agi::Context *c; ///< Project context - /// DOCME - wxStaticBoxSizer *KeyframesSizer; + int leadIn; ///< Lead-in to add in milliseconds + int leadOut; ///< Lead-out to add in milliseconds + int beforeStart; ///< Maximum time in milliseconds to move start time of line backwards to land on a keyframe + int afterStart; ///< Maximum time in milliseconds to move start time of line forwards to land on a keyframe + int beforeEnd; ///< Maximum time in milliseconds to move end time of line backwards to land on a keyframe + int afterEnd; ///< Maximum time in milliseconds to move end time of line forwards to land on a keyframe + int adjDistance; ///< Maximum time in milliseconds to snap adjacent lines to each other - /// DOCME - wxCheckBox *onlySelection; + wxCheckBox *onlySelection; ///< Only process selected lines of the selected styles + wxCheckBox *hasLeadIn; ///< Enable adding lead-in + wxCheckBox *hasLeadOut; ///< Enable adding lead-out + wxCheckBox *keysEnable; ///< Enable snapping to keyframes + wxCheckBox *adjsEnable; ///< Enable snapping adjacent lines to each other + wxSlider *adjacentBias; ///< Bias between shifting start and end times when snapping adjacent lines + wxCheckListBox *StyleList; ///< List of styles to process + wxButton *ApplyButton; ///< Button to apply the processing - /// DOCME - wxTextCtrl *leadIn; - - /// DOCME - wxTextCtrl *leadOut; - - /// DOCME - wxCheckBox *hasLeadIn; - - /// DOCME - wxCheckBox *hasLeadOut; - - /// DOCME - wxCheckBox *keysEnable; - - /// DOCME - wxTextCtrl *keysStartBefore; - - /// DOCME - wxTextCtrl *keysStartAfter; - - /// DOCME - wxTextCtrl *keysEndBefore; - - /// DOCME - wxTextCtrl *keysEndAfter; - - /// DOCME - wxCheckBox *adjsEnable; - - /// DOCME - wxTextCtrl *adjacentThres; - - /// DOCME - wxSlider *adjacentBias; - - /// DOCME - wxCheckListBox *StyleList; - - /// DOCME - wxButton *ApplyButton; - - /// DOCME - std::vector KeyFrames; - - void OnCheckBox(wxCommandEvent &event); - void OnSelectAll(wxCommandEvent &event); - void OnSelectNone(wxCommandEvent &event); void OnApply(wxCommandEvent &event); - void UpdateControls(); - void Process(); - int GetClosestKeyFrame(int frame); + /// Check or uncheck all styles + void CheckAll(bool check); - /// DOCME - std::vector Sorted; - void SortDialogues(); + /// Enable and disable text boxes based on which checkboxes are checked + void UpdateControls(); + + /// Process the file + void Process(); + + /// Get a list of dialogue lines in the file sorted by start time + std::vector SortDialogues(); public: + /// Constructor + /// @param c Project context DialogTimingProcessor(agi::Context *c); - - DECLARE_EVENT_TABLE() }; diff --git a/aegisub/src/validators.cpp b/aegisub/src/validators.cpp index 987f67b6e..4b3b0a85b 100644 --- a/aegisub/src/validators.cpp +++ b/aegisub/src/validators.cpp @@ -65,6 +65,13 @@ NumValidator::NumValidator(int val, bool issigned) { } +NumValidator::NumValidator(int64_t val, bool issigned) +: iValue((int)val) +, isFloat(false) +, isSigned(issigned) +{ +} + NumValidator::NumValidator(double val, bool issigned) : fValue(val) , isFloat(true) diff --git a/aegisub/src/validators.h b/aegisub/src/validators.h index 970b21b8b..b35df4b52 100644 --- a/aegisub/src/validators.h +++ b/aegisub/src/validators.h @@ -82,6 +82,11 @@ public: /// @param issigned Allow negative numbers? explicit NumValidator(int val, bool issigned=false); + /// Constructor + /// @param val Initial value to set the associated control to + /// @param issigned Allow negative numbers? + explicit NumValidator(int64_t val, bool issigned=false); + /// Constructor /// @param val Initial value to set the associated control to /// @param issigned Allow negative numbers?