// Copyright (c) 2005-2010, Niels Martin Hansen // Copyright (c) 2005-2010, Rodrigo Braz Monteiro // Copyright (c) 2010, Amar Takhar // 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 edit.cpp /// @brief edit/ commands. /// @ingroup command /// #include "../config.h" #ifndef AGI_PRE #include #include #endif #include "command.h" #include "../ass_dialogue.h" #include "../ass_file.h" #include "../ass_karaoke.h" #include "../dialog_search_replace.h" #include "../include/aegisub/context.h" #include "../subs_edit_ctrl.h" #include "../subs_grid.h" #include "../video_context.h" namespace { using cmd::Command; /// @defgroup cmd-edit Editing commands. /// @{ struct validate_sel_nonempty : public Command { CMD_TYPE(COMMAND_VALIDATE) bool Validate(const agi::Context *c) { return c->selectionController->GetSelectedSet().size() > 0; } }; struct validate_sel_multiple : public Command { CMD_TYPE(COMMAND_VALIDATE) bool Validate(const agi::Context *c) { return c->selectionController->GetSelectedSet().size() > 1; } }; /// Find and replace words in subtitles. struct edit_find_replace : public Command { CMD_NAME("edit/find_replace") STR_MENU("Find and R&eplace...") STR_DISP("Find and Replace") STR_HELP("Find and replace words in subtitles") void operator()(agi::Context *c) { c->videoController->Stop(); Search.OpenDialog(true); } }; /// Copy subtitles. struct edit_line_copy : public validate_sel_nonempty { CMD_NAME("edit/line/copy") STR_MENU("&Copy Lines") STR_DISP("Copy Lines") STR_HELP("Copy subtitles") void operator()(agi::Context *c) { // Ideally we'd let the control's keydown handler run and only deal // with the events not processed by it, but that doesn't seem to be // possible with how wx implements key event handling - the native // platform processing is evoked only if the wx event is unprocessed, // and there's no way to do something if the native platform code leaves // it unprocessed if (wxTextEntryBase *ctrl = dynamic_cast(c->parent->FindFocus())) ctrl->Copy(); else c->subsGrid->CopyLines(c->subsGrid->GetSelection()); } }; /// Cut subtitles. struct edit_line_cut: public validate_sel_nonempty { CMD_NAME("edit/line/cut") STR_MENU("Cu&t Lines") STR_DISP("Cut Lines") STR_HELP("Cut subtitles") void operator()(agi::Context *c) { if (wxTextEntryBase *ctrl = dynamic_cast(c->parent->FindFocus())) ctrl->Cut(); else c->subsGrid->CutLines(c->subsGrid->GetSelection()); } }; /// Delete currently selected lines. struct edit_line_delete : public validate_sel_nonempty { CMD_NAME("edit/line/delete") STR_MENU("De&lete Lines") STR_DISP("Delete Lines") STR_HELP("Delete currently selected lines") void operator()(agi::Context *c) { c->subsGrid->DeleteLines(c->subsGrid->GetSelection()); } }; struct in_selection : public std::unary_function { SubtitleSelectionController::Selection const& sel; in_selection(SubtitleSelectionController::Selection const& sel) : sel(sel) { } bool operator()(AssEntry *e) const { AssDialogue *d = dynamic_cast(e); return d && sel.count(d); } }; static void duplicate_lines(agi::Context *c, bool shift) { in_selection sel(c->selectionController->GetSelectedSet()); SubtitleSelectionController::Selection new_sel; AssDialogue *new_active = 0; std::list::iterator start = c->ass->Line.begin(); std::list::iterator end = c->ass->Line.end(); while (start != end) { // Find the first line in the selection start = find_if(start, end, sel); if (start == end) break; // And the last line in this contiguous selection std::list::iterator insert_pos = find_if(start, end, std::not1(sel)); std::list::iterator last = insert_pos; --last; // Duplicate each of the selected lines, inserting them in a block // after the selected block do { AssDialogue *new_diag = static_cast((*start)->Clone()); c->ass->Line.insert(insert_pos, new_diag); new_sel.insert(new_diag); if (!new_active) new_active = new_diag; if (shift) { int pos = c->videoController->FrameAtTime(new_diag->End, agi::vfr::END) + 1; new_diag->Start = c->videoController->TimeAtFrame(pos, agi::vfr::START); new_diag->End = c->videoController->TimeAtFrame(pos, agi::vfr::END); } } while (start++ != last); // Skip over the lines we just made start = insert_pos; } if (new_sel.empty()) return; c->ass->Commit(_("duplicate lines"), AssFile::COMMIT_DIAG_ADDREM); c->selectionController->SetSelectionAndActive(new_sel, new_active); } /// Duplicate the selected lines. struct edit_line_duplicate : public validate_sel_nonempty { CMD_NAME("edit/line/duplicate") STR_MENU("&Duplicate Lines") STR_DISP("Duplicate Lines") STR_HELP("Duplicate the selected lines") void operator()(agi::Context *c) { duplicate_lines(c, false); } }; /// Duplicate lines and shift by one frame. struct edit_line_duplicate_shift : public Command { CMD_NAME("edit/line/duplicate/shift") STR_MENU("D&uplicate and Shift by 1 Frame") STR_DISP("Duplicate and Shift by 1 Frame") STR_HELP("Duplicate lines and shift by one frame") CMD_TYPE(COMMAND_VALIDATE) bool Validate(const agi::Context *c) { return !c->selectionController->GetSelectedSet().empty() && c->videoController->IsLoaded(); } void operator()(agi::Context *c) { duplicate_lines(c, true); } }; static void combine_lines(agi::Context *c, void (*combiner)(AssDialogue *, AssDialogue *), wxString const& message) { SubtitleSelection sel = c->selectionController->GetSelectedSet(); AssDialogue *first = 0; entryIter out = c->ass->Line.begin(); for (entryIter it = c->ass->Line.begin(); it != c->ass->Line.end(); ++it) { AssDialogue *diag = dynamic_cast(*it); if (!diag || !sel.count(diag)) { *out++ = *it; continue; } if (!first) { first = diag; *out++ = *it; continue; } combiner(first, diag); first->End = std::max(first->End, diag->End); delete diag; } c->ass->Line.erase(out, c->ass->Line.end()); sel.clear(); sel.insert(first); c->selectionController->SetSelectionAndActive(sel, first); c->ass->Commit(message, AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_DIAG_FULL); } static void combine_karaoke(AssDialogue *first, AssDialogue *second) { first->Text += wxString::Format("{\\k%d}%s", (second->Start - first->End) / 10, second->Text); } static void combine_concat(AssDialogue *first, AssDialogue *second) { first->Text += " " + second->Text; } static void combine_drop(AssDialogue *, AssDialogue *) { } /// Joins selected lines in a single one, as karaoke. struct edit_line_join_as_karaoke : public validate_sel_multiple { CMD_NAME("edit/line/join/as_karaoke") STR_MENU("As &Karaoke") STR_DISP("As Karaoke") STR_HELP("Joins selected lines in a single one, as karaoke") void operator()(agi::Context *c) { combine_lines(c, combine_karaoke, _("join as karaoke")); } }; /// Joins selected lines in a single one, concatenating text together. struct edit_line_join_concatenate : public validate_sel_multiple { CMD_NAME("edit/line/join/concatenate") STR_MENU("&Concatenate") STR_DISP("Concatenate") STR_HELP("Joins selected lines in a single one, concatenating text together") void operator()(agi::Context *c) { combine_lines(c, combine_concat, _("join lines")); } }; /// Joins selected lines in a single one, keeping text of first and discarding remaining. struct edit_line_join_keep_first : public validate_sel_multiple { CMD_NAME("edit/line/join/keep_first") STR_MENU("Keep &First") STR_DISP("Keep First") STR_HELP("Joins selected lines in a single one, keeping text of first and discarding remaining") void operator()(agi::Context *c) { combine_lines(c, combine_drop, _("join lines")); } }; /// Paste subtitles. struct edit_line_paste : public Command { CMD_NAME("edit/line/paste") STR_MENU("&Paste Lines") STR_DISP("Paste Lines") STR_HELP("Paste subtitles") CMD_TYPE(COMMAND_VALIDATE) bool Validate(const agi::Context *) { if (wxTheClipboard->Open()) { bool can_paste = wxTheClipboard->IsSupported(wxDF_TEXT); wxTheClipboard->Close(); return can_paste; } return false; } void operator()(agi::Context *c) { if (wxTextEntryBase *ctrl = dynamic_cast(c->parent->FindFocus())) ctrl->Paste(); else c->subsGrid->PasteLines(c->subsGrid->GetFirstSelRow()); } }; /// Paste subtitles over others. struct edit_line_paste_over : public Command { CMD_NAME("edit/line/paste/over") STR_MENU("Paste Lines &Over...") STR_DISP("Paste Lines Over") STR_HELP("Paste subtitles over others") CMD_TYPE(COMMAND_VALIDATE) bool Validate(const agi::Context *c) { if (wxTheClipboard->Open()) { bool can_paste = wxTheClipboard->IsSupported(wxDF_TEXT); wxTheClipboard->Close(); return can_paste && c->selectionController->GetSelectedSet().size(); } return false; } void operator()(agi::Context *c) { c->subsGrid->PasteLines(c->subsGrid->GetFirstSelRow(),true); } }; /// Recombine subtitles when they have been split and merged. struct edit_line_recombine : public validate_sel_multiple { CMD_NAME("edit/line/recombine") STR_MENU("Recom&bine Lines") STR_DISP("Recombine Lines") STR_HELP("Recombine subtitles when they have been split and merged") void operator()(agi::Context *c) { c->subsGrid->RecombineLines(); } }; /// Uses karaoke timing to split line into multiple smaller lines. struct edit_line_split_by_karaoke : public validate_sel_nonempty { CMD_NAME("edit/line/split/by_karaoke") STR_MENU("Split Lines (by karaoke)") STR_DISP("Split Lines (by karaoke)") STR_HELP("Uses karaoke timing to split line into multiple smaller lines") void operator()(agi::Context *c) { AssKaraoke::SplitLines(c->selectionController->GetSelectedSet(), c); } }; /// Redoes last action. struct edit_redo : public Command { CMD_NAME("edit/redo") STR_HELP("Redoes last action") CMD_TYPE(COMMAND_VALIDATE | COMMAND_DYNAMIC_NAME) wxString StrMenu(const agi::Context *c) const { return c->ass->IsRedoStackEmpty() ? _("Nothing to &redo") : wxString::Format(_("&Redo %s"), c->ass->GetRedoDescription()); } wxString StrDisplay(const agi::Context *c) const { return c->ass->IsRedoStackEmpty() ? _("Nothing to redo") : wxString::Format(_("Redo %s"), c->ass->GetRedoDescription()); } bool Validate(const agi::Context *c) { return !c->ass->IsRedoStackEmpty(); } void operator()(agi::Context *c) { c->ass->Redo(); } }; /// Undoes last action. struct edit_undo : public Command { CMD_NAME("edit/undo") STR_HELP("Undoes last action") CMD_TYPE(COMMAND_VALIDATE | COMMAND_DYNAMIC_NAME) wxString StrMenu(const agi::Context *c) const { return c->ass->IsUndoStackEmpty() ? _("Nothing to &undo") : wxString::Format(_("&Undo %s"), c->ass->GetUndoDescription()); } wxString StrDisplay(const agi::Context *c) const { return c->ass->IsUndoStackEmpty() ? _("Nothing to undo") : wxString::Format(_("Undo %s"), c->ass->GetUndoDescription()); } bool Validate(const agi::Context *c) { return !c->ass->IsUndoStackEmpty(); } void operator()(agi::Context *c) { c->ass->Undo(); } }; } /// @} namespace cmd { void init_edit() { reg(new edit_find_replace); reg(new edit_line_copy); reg(new edit_line_cut); reg(new edit_line_delete); reg(new edit_line_duplicate); reg(new edit_line_duplicate_shift); reg(new edit_line_join_as_karaoke); reg(new edit_line_join_concatenate); reg(new edit_line_join_keep_first); reg(new edit_line_paste); reg(new edit_line_paste_over); reg(new edit_line_recombine); reg(new edit_line_split_by_karaoke); reg(new edit_redo); reg(new edit_undo); } }