// 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/ // // $Id$ /// @file subs_edit_box.cpp /// @brief Main subtitle editing area, including toolbars around the text control /// @ingroup main_ui #include "config.h" #ifndef AGI_PRE #include #include #include #include #include #include #include #include #include #include #endif #include "subs_edit_box.h" #include "ass_dialogue.h" #include "ass_file.h" #include "ass_override.h" #include "ass_style.h" #include "command/command.h" #include "dialog_colorpicker.h" #include "dialog_search_replace.h" #include "include/aegisub/context.h" #include "include/aegisub/hotkey.h" #include "libresrc/libresrc.h" #include "main.h" #include "placeholder_ctrl.h" #include "subs_edit_ctrl.h" #include "subs_grid.h" #include "timeedit_ctrl.h" #include "utils.h" #include "validators.h" #include "video_context.h" namespace { template struct field_setter : public std::binary_function { T AssDialogue::*field; field_setter(T AssDialogue::*field) : field(field) { } void operator()(AssDialogue* obj, T value) { obj->*field = value; } }; /// @brief Get the selection from a text edit /// @return Pair of selection start and end positions std::pair get_selection(SubsTextEditCtrl *TextEdit) { int start, end; TextEdit->GetSelection(&start, &end); int len = TextEdit->GetText().size(); return std::make_pair( mid(0, TextEdit->GetReverseUnicodePosition(start), len), mid(0, TextEdit->GetReverseUnicodePosition(end), len)); } /// @brief Get the value of a tag at a specified position in a line /// @param line Line to get the value from /// @param blockn Block number in the line /// @param initial Value from style to use if the tag does not exist /// @param tag Tag to get the value of /// @param alt Alternate name of the tag, if any template static T get_value(AssDialogue const& line, int blockn, T initial, wxString tag, wxString alt = wxString()) { for (int i = blockn; i >= 0; i--) { AssDialogueBlockOverride *ovr = dynamic_cast(line.Blocks[i]); if (!ovr) continue; for (int j = (int)ovr->Tags.size() - 1; j >= 0; j--) { if (ovr->Tags[j]->Name == tag || ovr->Tags[j]->Name == alt) { return ovr->Tags[j]->Params[0]->Get(initial); } } } return initial; } /// Get the block index in the text of the position int block_at_pos(wxString const& text, int pos) { int n = 0; int max = text.size() - 1; for (int i = 0; i <= pos && i <= max; ++i) { if (i > 0 && text[i] == '{') n++; if (text[i] == '}' && i != max && i != pos && i != pos -1 && (i+1 == max || text[i+1] != '{')) n++; } return n; } /// 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); } 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") , line(0) , splitLineMode(true) , controlState(true) , c(context) , commitId(-1) , undoTimer(GetEventHandler()) { using std::tr1::bind; // Top controls TopSizer = new wxBoxSizer(wxHORIZONTAL); CommentBox = new wxCheckBox(this,-1,_("&Comment")); CommentBox->SetToolTip(_("Comment this line out. Commented lines don't show up on screen.")); #ifdef __WXGTK__ // Only supported in wxgtk CommentBox->SetCanFocus(false); #endif TopSizer->Add(CommentBox, 0, wxRIGHT | wxALIGN_CENTER, 5); StyleBox = MakeComboBox("Default", wxCB_READONLY, &SubsEditBox::OnStyleChange, _("Style for this line")); ActorBox = 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_COMMAND_TEXT_UPDATED, &SubsEditBox::OnActorChange, this, ActorBox->GetId()); Bind(wxEVT_COMMAND_COMBOBOX_SELECTED, &SubsEditBox::OnActorChange, this, ActorBox->GetId()); TopSizer->Add(ActorBox, wxSizerFlags(2).Center().Border(wxRIGHT)); Effect = new Placeholder(this, _("Effect"), wxSize(80,-1), 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_COMMAND_TEXT_UPDATED, &SubsEditBox::OnEffectChange, this, Effect->GetId()); TopSizer->Add(Effect, 3, wxALIGN_CENTER, 5); // Middle controls MiddleSizer = 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")); MiddleSizer->Add(Layer, wxSizerFlags().Center()); MiddleSizer->AddSpacer(5); StartTime = MakeTimeCtrl(false, _("Start time"), &SubsEditBox::OnStartTimeChange); EndTime = MakeTimeCtrl(true, _("End time"), &SubsEditBox::OnEndTimeChange); MiddleSizer->AddSpacer(5); Duration = MakeTimeCtrl(false, _("Line duration"), &SubsEditBox::OnDurationChange); MiddleSizer->AddSpacer(5); MarginL = MakeMarginCtrl(_("Left Margin (0 = default)"), &SubsEditBox::OnMarginLChange); MarginR = MakeMarginCtrl(_("Right Margin (0 = default)"), &SubsEditBox::OnMarginRChange); MarginV = MakeMarginCtrl(_("Vertical Margin (0 = default)"), &SubsEditBox::OnMarginVChange); MiddleSizer->AddSpacer(5); // Middle-bottom controls MiddleBotSizer = new wxBoxSizer(wxHORIZONTAL); MakeButton(GETIMAGE(button_bold_16), _("Bold"), bind(&SubsEditBox::OnFlagButton, this, &AssStyle::bold, "\\b", _("toggle bold"))); MakeButton(GETIMAGE(button_italics_16), _("Italics"), bind(&SubsEditBox::OnFlagButton, this, &AssStyle::italic, "\\i", _("toggle italic"))); MakeButton(GETIMAGE(button_underline_16), _("Underline"), bind(&SubsEditBox::OnFlagButton, this, &AssStyle::underline, "\\u", _("toggle underline"))); MakeButton(GETIMAGE(button_strikeout_16), _("Strikeout"), bind(&SubsEditBox::OnFlagButton, this, &AssStyle::strikeout, "\\s", _("toggle strikeout"))); MakeButton(GETIMAGE(button_fontname_16), _("Font Face"), bind(&SubsEditBox::OnFontButton, this)); MiddleBotSizer->AddSpacer(5); MakeButton(GETIMAGE(button_color_one_16), _("Primary color"), bind(&SubsEditBox::OnColorButton, this, &AssStyle::primary, "\\c", "\\1c")); MakeButton(GETIMAGE(button_color_two_16), _("Secondary color"), bind(&SubsEditBox::OnColorButton, this, &AssStyle::secondary, "\\2c", "")); MakeButton(GETIMAGE(button_color_three_16), _("Outline color"), bind(&SubsEditBox::OnColorButton, this, &AssStyle::outline, "\\3c", "")); MakeButton(GETIMAGE(button_color_four_16), _("Shadow color"), bind(&SubsEditBox::OnColorButton, this, &AssStyle::shadow, "\\4c", "")); MiddleBotSizer->AddSpacer(5); MakeButton(GETIMAGE(button_audio_commit_16), _("Commits the text (Enter)"), bind(&cmd::call, "grid/line/next/create", c)); MiddleBotSizer->AddSpacer(10); ByTime = MakeRadio(_("T&ime"), true, _("Time by h:mm:ss.cs")); ByFrame = MakeRadio(_("F&rame"), false, _("Time by frame number")); ByFrame->Enable(false); // Text editor TextEdit = new SubsTextEditCtrl(this, wxSize(300,50), wxBORDER_SUNKEN, c); TextEdit->Bind(wxEVT_CHAR_HOOK, &SubsEditBox::OnKeyDown, this); Bind(wxEVT_CHAR_HOOK, &SubsEditBox::OnKeyDown, this); BottomSizer = new wxBoxSizer(wxHORIZONTAL); BottomSizer->Add(TextEdit,1,wxEXPAND,0); // Main sizer wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL); MainSizer->Add(TopSizer,0,wxEXPAND | wxALL,3); MainSizer->Add(MiddleSizer,0,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,3); MainSizer->Add(MiddleBotSizer,0,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,3); MainSizer->Add(BottomSizer,1,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,3); SetSizerAndFit(MainSizer); TextEdit->Bind(wxEVT_STC_MODIFIED, &SubsEditBox::OnChange, this); TextEdit->SetModEventMask(wxSTC_MOD_INSERTTEXT | wxSTC_MOD_DELETETEXT | wxSTC_STARTACTION); Bind(wxEVT_COMMAND_TEXT_UPDATED, &SubsEditBox::OnLayerEnter, this, Layer->GetId()); Bind(wxEVT_COMMAND_SPINCTRL_UPDATED, &SubsEditBox::OnLayerChange, this, Layer->GetId()); Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, &SubsEditBox::OnCommentChange, this, CommentBox->GetId()); Bind(wxEVT_SIZE, &SubsEditBox::OnSize, this); Bind(wxEVT_TIMER, &SubsEditBox::OnUndoTimer, this); wxSizeEvent evt; OnSize(evt); c->selectionController->AddSelectionListener(this); file_changed_slot = c->ass->AddCommitListener(&SubsEditBox::OnCommit, this); context->videoController->AddTimecodesListener(&SubsEditBox::UpdateFrameTiming, this); } SubsEditBox::~SubsEditBox() { c->selectionController->RemoveSelectionListener(this); } wxTextCtrl *SubsEditBox::MakeMarginCtrl(wxString const& tooltip, void (SubsEditBox::*handler)(wxCommandEvent&)) { wxTextCtrl *ctrl = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxSize(40,-1), wxTE_CENTRE | wxTE_PROCESS_ENTER, NumValidator()); ctrl->SetMaxLength(4); ctrl->SetToolTip(tooltip); Bind(wxEVT_COMMAND_TEXT_UPDATED, handler, this, ctrl->GetId()); MiddleSizer->Add(ctrl, wxSizerFlags().Center()); return ctrl; } TimeEdit *SubsEditBox::MakeTimeCtrl(bool end, wxString const& tooltip, void (SubsEditBox::*handler)(wxCommandEvent&)) { TimeEdit *ctrl = new TimeEdit(this, -1, c, "", wxSize(75,-1), end); ctrl->SetToolTip(tooltip); Bind(wxEVT_COMMAND_TEXT_UPDATED, handler, this, ctrl->GetId()); ctrl->Bind(wxEVT_CHAR_HOOK, time_edit_char_hook); MiddleSizer->Add(ctrl, wxSizerFlags().Center()); return ctrl; } template void SubsEditBox::MakeButton(wxBitmap const& icon, wxString const& tooltip, Handler const& handler) { wxBitmapButton *btn = new wxBitmapButton(this, -1, icon); btn->SetToolTip(tooltip); MiddleBotSizer->Add(btn, wxSizerFlags().Center().Expand()); Bind(wxEVT_COMMAND_BUTTON_CLICKED, handler, btn->GetId()); } 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); TopSizer->Add(ctrl, wxSizerFlags(2).Center().Border(wxRIGHT)); Bind(wxEVT_COMMAND_COMBOBOX_SELECTED, 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_COMMAND_RADIOBUTTON_SELECTED, &SubsEditBox::OnFrameTimeRadio, this, ctrl->GetId()); MiddleBotSizer->Add(ctrl, wxSizerFlags().Center().Expand().Border(wxRIGHT)); return ctrl; } void SubsEditBox::OnCommit(int type) { wxEventBlocker blocker(this); initialTimes.clear(); if (type == AssFile::COMMIT_NEW || type & AssFile::COMMIT_STYLES) { wxString style = StyleBox->GetValue(); StyleBox->Clear(); StyleBox->Append(c->ass->GetStyles()); StyleBox->Select(StyleBox->FindString(style)); } if (type == AssFile::COMMIT_NEW) { /// @todo maybe preserve selection over undo? PopulateActorList(); TextEdit->SetSelection(0,0); return; } else if (type & AssFile::COMMIT_STYLES) StyleBox->Select(StyleBox->FindString(line->Style)); if (!(type ^ AssFile::COMMIT_ORDER)) return; SetControlsState(!!line); if (!line) return; if (type & AssFile::COMMIT_DIAG_TIME) { StartTime->SetTime(line->Start); EndTime->SetTime(line->End); Duration->SetTime(line->End - line->Start); } if (type & AssFile::COMMIT_DIAG_TEXT) { TextEdit->SetTextTo(line->Text); } if (type & AssFile::COMMIT_DIAG_META) { Layer->SetValue(line->Layer); change_value(MarginL, line->GetMarginString(0,false)); change_value(MarginR, line->GetMarginString(1,false)); change_value(MarginV, line->GetMarginString(2,false)); Effect->ChangeValue(line->Effect); CommentBox->SetValue(line->Comment); StyleBox->Select(StyleBox->FindString(line->Style)); PopulateActorList(); ActorBox->ChangeValue(line->Actor); ActorBox->SetStringSelection(line->Actor); } } void SubsEditBox::PopulateActorList() { wxEventBlocker blocker(this); std::set actors; for (entryIter it = c->ass->Line.begin(); it != c->ass->Line.end(); ++it) { if (AssDialogue *diag = dynamic_cast(*it)) actors.insert(diag->Actor); } #ifdef __APPLE__ // OSX doesn't like combo boxes that are empty. actors.insert("Actor"); #endif actors.erase(""); wxArrayString arrstr; arrstr.reserve(actors.size()); copy(actors.begin(), actors.end(), std::back_inserter(arrstr)); ActorBox->Freeze(); long pos = ActorBox->GetInsertionPoint(); wxString value = ActorBox->GetValue(); ActorBox->Set(arrstr); ActorBox->ChangeValue(value); ActorBox->SetStringSelection(value); ActorBox->SetInsertionPoint(pos); ActorBox->Thaw(); } void SubsEditBox::OnActiveLineChanged(AssDialogue *new_line) { wxEventBlocker blocker(this); line = new_line; commitId = -1; OnCommit(AssFile::COMMIT_DIAG_FULL); /// @todo VideoContext should be doing this if (c->videoController->IsLoaded()) { bool sync; if (Search.HasFocus()) sync = OPT_GET("Tool/Search Replace/Video Update")->GetBool(); else sync = OPT_GET("Video/Subtitle Sync")->GetBool(); if (sync) { c->videoController->Stop(); c->videoController->JumpToTime(line->Start); } } } void SubsEditBox::OnSelectedSetChanged(const Selection &, const Selection &) { sel = c->selectionController->GetSelectedSet(); initialTimes.clear(); } void SubsEditBox::UpdateFrameTiming(agi::vfr::Framerate const& fps) { if (fps.IsLoaded()) { ByFrame->Enable(true); } else { ByFrame->Enable(false); ByTime->SetValue(true); StartTime->SetByFrame(false); EndTime->SetByFrame(false); Duration->SetByFrame(false); c->subsGrid->SetByFrame(false); } } void SubsEditBox::OnKeyDown(wxKeyEvent &event) { hotkey::check("Subtitle Edit Box", c, event); } void SubsEditBox::OnChange(wxStyledTextEvent &event) { if (line && TextEdit->GetText() != line->Text) { if (event.GetModificationType() & wxSTC_STARTACTION) commitId = -1; CommitText(_("modify text")); } } void SubsEditBox::OnUndoTimer(wxTimerEvent&) { commitId = -1; } template void SubsEditBox::SetSelectedRows(setter set, T value, wxString desc, int type, bool amend) { for_each(sel.begin(), sel.end(), bind(set, std::tr1::placeholders::_1, value)); file_changed_slot.Block(); commitId = c->ass->Commit(desc, type, (amend && desc == lastCommitType) ? commitId : -1, sel.size() == 1 ? *sel.begin() : 0); file_changed_slot.Unblock(); lastCommitType = desc; lastTimeCommitType = -1; initialTimes.clear(); undoTimer.Start(30000, wxTIMER_ONE_SHOT); } template void SubsEditBox::SetSelectedRows(T AssDialogue::*field, T value, wxString desc, int type, bool amend) { SetSelectedRows(field_setter(field), value, desc, type, amend); } void SubsEditBox::CommitText(wxString desc) { SetSelectedRows(&AssDialogue::Text, TextEdit->GetText(), desc, AssFile::COMMIT_DIAG_TEXT, true); } void SubsEditBox::CommitTimes(TimeField field) { for (Selection::iterator cur = sel.begin(); cur != sel.end(); ++cur) { AssDialogue *d = *cur; if (!initialTimes.count(d)) initialTimes[d] = std::make_pair(d->Start, d->End); switch (field) { case TIME_START: initialTimes[d].first = d->Start = StartTime->GetTime(); d->End = std::max(d->Start, initialTimes[d].second); break; case TIME_END: initialTimes[d].second = d->End = EndTime->GetTime(); d->Start = std::min(d->End, initialTimes[d].first); break; case TIME_DURATION: if (ByFrame->GetValue()) d->End = c->videoController->TimeAtFrame(c->videoController->FrameAtTime(d->Start, agi::vfr::START) + Duration->GetFrame(), agi::vfr::END); else d->End = d->Start + Duration->GetTime(); initialTimes[d].second = d->End; break; } } StartTime->SetTime(line->Start); EndTime->SetTime(line->End); if (ByFrame->GetValue()) Duration->SetFrame(EndTime->GetFrame() - StartTime->GetFrame() + 1); else Duration->SetTime(EndTime->GetTime() - StartTime->GetTime()); if (field != lastTimeCommitType) commitId = -1; lastTimeCommitType = field; file_changed_slot.Block(); commitId = c->ass->Commit(_("modify times"), AssFile::COMMIT_DIAG_TIME, commitId, sel.size() == 1 ? *sel.begin() : 0); file_changed_slot.Unblock(); } void SubsEditBox::OnSize(wxSizeEvent &evt) { int availableWidth = GetVirtualSize().GetWidth(); int midMin = MiddleSizer->GetMinSize().GetWidth(); int botMin = MiddleBotSizer->GetMinSize().GetWidth(); if (splitLineMode) { if (availableWidth > midMin + botMin) { GetSizer()->Detach(MiddleBotSizer); MiddleSizer->Add(MiddleBotSizer,0,wxALIGN_CENTER_VERTICAL); splitLineMode = false; } } else { if (availableWidth < midMin) { MiddleSizer->Detach(MiddleBotSizer); GetSizer()->Insert(2,MiddleBotSizer,0,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,3); splitLineMode = true; } } evt.Skip(); } void SubsEditBox::OnFrameTimeRadio(wxCommandEvent &event) { bool byFrame = ByFrame->GetValue(); StartTime->SetByFrame(byFrame); EndTime->SetByFrame(byFrame); Duration->SetByFrame(byFrame); c->subsGrid->SetByFrame(byFrame); event.Skip(); } void SubsEditBox::SetControlsState(bool state) { if (state == controlState) return; controlState = state; Enable(state); if (!state) { wxEventBlocker blocker(this); TextEdit->SetTextTo(""); } } void SubsEditBox::OnStyleChange(wxCommandEvent &) { SetSelectedRows(&AssDialogue::Style, StyleBox->GetValue(), _("style change"), AssFile::COMMIT_DIAG_META); } void SubsEditBox::OnActorChange(wxCommandEvent &evt) { bool amend = evt.GetEventType() == wxEVT_COMMAND_TEXT_UPDATED; SetSelectedRows(&AssDialogue::Actor, ActorBox->GetValue(), _("actor change"), AssFile::COMMIT_DIAG_META, amend); PopulateActorList(); } void SubsEditBox::OnLayerChange(wxSpinEvent &event) { OnLayerEnter(event); } void SubsEditBox::OnLayerEnter(wxCommandEvent &) { SetSelectedRows(&AssDialogue::Layer, Layer->GetValue(), _("layer change"), AssFile::COMMIT_DIAG_META); } void SubsEditBox::OnStartTimeChange(wxCommandEvent &) { CommitTimes(TIME_START); } void SubsEditBox::OnEndTimeChange(wxCommandEvent &) { CommitTimes(TIME_END); } void SubsEditBox::OnDurationChange(wxCommandEvent &) { CommitTimes(TIME_DURATION); } void SubsEditBox::OnMarginLChange(wxCommandEvent &) { SetSelectedRows(std::mem_fun(&AssDialogue::SetMarginString<0>), MarginL->GetValue(), _("MarginL change"), AssFile::COMMIT_DIAG_META); if (line) change_value(MarginL, line->GetMarginString(0, false)); } void SubsEditBox::OnMarginRChange(wxCommandEvent &) { SetSelectedRows(std::mem_fun(&AssDialogue::SetMarginString<1>), MarginR->GetValue(), _("MarginR change"), AssFile::COMMIT_DIAG_META); if (line) change_value(MarginR, line->GetMarginString(1, false)); } static void set_margin_v(AssDialogue* diag, wxString value) { diag->SetMarginString(value, 2); diag->SetMarginString(value, 3); } void SubsEditBox::OnMarginVChange(wxCommandEvent &) { SetSelectedRows(set_margin_v, MarginV->GetValue(), _("MarginV change"), AssFile::COMMIT_DIAG_META); if (line) change_value(MarginV, line->GetMarginString(2, false)); } void SubsEditBox::OnEffectChange(wxCommandEvent &) { SetSelectedRows(&AssDialogue::Effect, Effect->GetValue(), _("effect change"), AssFile::COMMIT_DIAG_META, true); } void SubsEditBox::OnCommentChange(wxCommandEvent &) { SetSelectedRows(&AssDialogue::Comment, CommentBox->GetValue(), _("comment change"), AssFile::COMMIT_DIAG_META); } void SubsEditBox::SetTag(wxString tag, wxString value, bool atEnd) { assert(line); if (line->Blocks.empty()) line->ParseASSTags(); std::pair sel = get_selection(TextEdit); int start = atEnd ? sel.second : sel.first; int blockn = block_at_pos(line->Text, start); AssDialogueBlockPlain *plain = 0; AssDialogueBlockOverride *ovr = 0; while (blockn >= 0) { AssDialogueBlock *block = line->Blocks[blockn]; if (dynamic_cast(block)) --blockn; else if ((plain = dynamic_cast(block))) { // Cursor is in a comment block, so try the previous block instead if (plain->GetText().StartsWith("{")) { --blockn; start = line->Text.rfind('{', start); } else break; } else { ovr = dynamic_cast(block); assert(ovr); break; } } // If we didn't hit a suitable block for inserting the override just put // it at the beginning of the line if (blockn < 0) start = 0; wxString insert = tag + value; int shift = insert.size(); if (plain || blockn < 0) { line->Text = line->Text.Left(start) + "{" + insert + "}" + line->Text.Mid(start); shift += 2; line->ParseASSTags(); } else if(ovr) { wxString alt; if (tag == "\\c") alt = "\\1c"; // Remove old of same bool found = false; for (size_t i = 0; i < ovr->Tags.size(); i++) { wxString name = ovr->Tags[i]->Name; if (tag == name || alt == name) { shift -= ((wxString)*ovr->Tags[i]).size(); if (found) { delete ovr->Tags[i]; ovr->Tags.erase(ovr->Tags.begin() + i); i--; } else { ovr->Tags[i]->Params[0]->Set(value); ovr->Tags[i]->Params[0]->omitted = false; found = true; } } } if (!found) { ovr->AddTag(insert); } line->UpdateText(); } else assert(false); TextEdit->SetTextTo(line->Text); if (!atEnd) TextEdit->SetSelectionU(sel.first+shift,sel.second+shift); TextEdit->SetFocus(); } void SubsEditBox::OnFlagButton(bool (AssStyle::*field), const char *tag, wxString const& undo_msg) { AssStyle *style = c->ass->GetStyle(line->Style); bool state = style ? style->*field : AssStyle().*field; line->ParseASSTags(); std::pair sel = get_selection(TextEdit); int blockn = block_at_pos(line->Text, sel.first); state = get_value(*line, blockn, state, tag); SetTag(tag, state ? "0" : "1"); if (sel.first != sel.second) SetTag(tag, state ? "1" : "0", true); line->ClearBlocks(); commitId = -1; CommitText(undo_msg); } void SubsEditBox::OnFontButton() { line->ParseASSTags(); int blockn = block_at_pos(line->Text, get_selection(TextEdit).first); AssStyle *style = c->ass->GetStyle(line->Style); AssStyle defStyle; if (!style) style = &defStyle; wxFont startfont( get_value(*line, blockn, (int)style->fontsize, "\\fs"), wxFONTFAMILY_DEFAULT, get_value(*line, blockn, style->italic, "\\i") ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL, get_value(*line, blockn, style->bold, "\\b") ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL, get_value(*line, blockn, style->underline, "\\u"), get_value(*line, blockn, style->font, "\\fn")); wxFont font = wxGetFontFromUser(this, startfont); if (!font.Ok() || font == startfont) { line->ClearBlocks(); return; } if (font.GetFaceName() != startfont.GetFaceName()) SetTag("\\fn", font.GetFaceName()); if (font.GetPointSize() != startfont.GetPointSize()) SetTag("\\fs", wxString::Format("%d", font.GetPointSize())); if (font.GetWeight() != startfont.GetWeight()) SetTag("\\b", wxString::Format("%d", font.GetWeight() == wxFONTWEIGHT_BOLD)); if (font.GetStyle() != startfont.GetStyle()) SetTag("\\i", wxString::Format("%d", font.GetStyle() == wxFONTSTYLE_ITALIC)); if (font.GetUnderlined() != startfont.GetUnderlined()) SetTag("\\i", wxString::Format("%d", font.GetUnderlined())); line->ClearBlocks(); commitId = -1; CommitText(_("set font")); } void SubsEditBox::OnColorButton(AssColor (AssStyle::*field), const char *tag, const char *alt) { AssStyle *style = c->ass->GetStyle(line->Style); wxColor color = (style ? style->*field : AssStyle().*field).GetWXColor(); colorTag = tag; commitId = -1; line->ParseASSTags(); std::pair sel = get_selection(TextEdit); int blockn = block_at_pos(line->Text, sel.first); color = get_value(*line, blockn, color, colorTag, alt); wxColor newColor = GetColorFromUser(c->parent, color, this); line->ClearBlocks(); CommitText(_("set color")); if (!newColor.IsOk()) { c->ass->Undo(); TextEdit->SetSelectionU(sel.first, sel.second); } } void SubsEditBox::SetColorCallback(wxColor newColor) { if (newColor.Ok()) { SetTag(colorTag, AssColor(newColor).GetASSFormatted(false)); CommitText(_("set color")); } }