// Copyright (c) 2007, Rodrigo Braz Monteiro // 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 visual_tool.cpp /// @brief Base class for visual typesetting functions /// @ingroup visual_ts #include "config.h" #ifndef AGI_PRE #include #include #endif #include "ass_dialogue.h" #include "ass_file.h" #include "ass_override.h" #include "ass_style.h" #include "ass_time.h" #include "include/aegisub/context.h" #include "main.h" #include "subs_grid.h" #include "utils.h" #include "video_context.h" #include "video_display.h" #include "video_provider_manager.h" #include "visual_feature.h" #include "visual_tool.h" #include "visual_tool_clip.h" #include "visual_tool_drag.h" #include "visual_tool_vector_clip.h" const wxColour IVisualTool::colour[4] = {wxColour(106,32,19), wxColour(255,169,40), wxColour(255,253,185), wxColour(187,0,0)}; template VisualTool::VisualTool(VideoDisplay *parent, agi::Context *context, VideoState const& video) : dragStartX(0) , dragStartY(0) , commitId(-1) , selChanged(false) , selectedFeatures(selFeatures) , c(context) , parent(parent) , holding(false) , dragging(false) , externalChange(true) , video(video) , leftClick(false) , leftDClick(false) , shiftDown(false) , ctrlDown(false) , altDown(false) { frameNumber = c->videoController->GetFrameN(); curDiag = GetActiveDialogueLine(); c->selectionController->AddSelectionListener(this); curFeature = features.begin(); } template VisualTool::~VisualTool() { c->selectionController->RemoveSelectionListener(this); } template void VisualTool::OnMouseEvent(wxMouseEvent &event) { bool needRender = false; if (event.Leaving()) { Update(); parent->Render(); return; } else if (event.Entering() && !OPT_GET("Tool/Visual/Always Show")->GetBool()) { needRender = true; } externalChange = false; leftClick = event.ButtonDown(wxMOUSE_BTN_LEFT); leftDClick = event.LeftDClick(); shiftDown = event.m_shiftDown; #ifdef __APPLE__ ctrlDown = event.m_metaDown; // Cmd key #else ctrlDown = event.m_controlDown; #endif altDown = event.m_altDown; if (!dragging) { feature_iterator oldHigh = curFeature; GetHighlightedFeature(); if (curFeature != oldHigh) needRender = true; } if (dragging) { // continue drag if (event.LeftIsDown()) { for (selection_iterator cur = selFeatures.begin(); cur != selFeatures.end(); ++cur) { (*cur)->x = (video.x - dragStartX + (*cur)->origX); (*cur)->y = (video.y - dragStartY + (*cur)->origY); if (shiftDown) { if (abs(video.x - dragStartX) > abs(video.y - dragStartY)) { (*cur)->y = (*cur)->origY; } else { (*cur)->x = (*cur)->origX; } } UpdateDrag(*cur); CommitDrag(*cur); } Commit(); needRender = true; } // end drag else { dragging = false; // mouse didn't move, fiddle with selection if (curFeature->x == curFeature->origX && curFeature->y == curFeature->origY) { // Don't deselect stuff that was selected in this click's mousedown event if (!selChanged) { if (ctrlDown) { // deselect this feature RemoveSelection(curFeature); } else { SetSelection(curFeature); } } } else { for (selection_iterator cur = selFeatures.begin(); cur != selFeatures.end(); ++cur) { CommitDrag(*cur); } Commit(); } curFeature = features.end(); parent->ReleaseMouse(); parent->SetFocus(); } } else if (holding) { // continue hold if (event.LeftIsDown()) { UpdateHold(); needRender = true; } // end hold else { holding = false; parent->ReleaseMouse(); parent->SetFocus(); } CommitHold(); Commit(); } else if (leftClick) { // start drag if (curFeature != features.end()) { if (selFeatures.find(curFeature) == selFeatures.end()) { selChanged = true; if (ctrlDown) { AddSelection(curFeature); } else { SetSelection(curFeature); } } else { selChanged = false; } if (curFeature->line) c->selectionController->SetActiveLine(curFeature->line); if (InitializeDrag(curFeature)) { dragStartX = video.x; dragStartY = video.y; for (selection_iterator cur = selFeatures.begin(); cur != selFeatures.end(); ++cur) { (*cur)->origX = (*cur)->x; (*cur)->origY = (*cur)->y; } dragging = true; parent->CaptureMouse(); } } // start hold else { if (!altDown) { ClearSelection(); Selection sel; sel.insert(c->selectionController->GetActiveLine()); c->selectionController->SetSelectedSet(sel); needRender = true; } if (curDiag && InitializeHold()) { holding = true; parent->CaptureMouse(); } } } if (Update() || needRender) parent->Render(); externalChange = true; if (!event.LeftIsDown()) { // Only coalesce the changes made in a single drag commitId = -1; } } template void VisualTool::Commit(wxString message) { externalChange = false; if (message.empty()) { message = _("visual typesetting"); } commitId = c->ass->Commit(message, AssFile::COMMIT_DIAG_TEXT, commitId); externalChange = true; } template AssDialogue* VisualTool::GetActiveDialogueLine() { AssDialogue *diag = c->selectionController->GetActiveLine(); if (diag && c->subsGrid->IsDisplayed(diag)) return diag; return NULL; } template void VisualTool::GetHighlightedFeature() { int highestLayerFound = INT_MIN; curFeature = features.end(); for (feature_iterator cur = features.begin(); cur != features.end(); ++cur) { if (cur->IsMouseOver(video.x, video.y) && cur->layer > highestLayerFound) { curFeature = cur; highestLayerFound = cur->layer; } } } template void VisualTool::DrawAllFeatures() { SetLineColour(colour[0],1.0f,2); for (feature_iterator cur = features.begin(); cur != features.end(); ++cur) { int fill; if (cur == curFeature) fill = 2; else if (selFeatures.find(cur) != selFeatures.end()) fill = 3; else fill = 1; SetFillColour(colour[fill],0.6f); cur->Draw(*this); } } template void VisualTool::Refresh() { if (externalChange) { curDiag = GetActiveDialogueLine(); curFeature = features.end(); OnFileChanged(); } } template void VisualTool::SetFrame(int newFrameNumber) { if (frameNumber == newFrameNumber) return; frameNumber = newFrameNumber; curFeature = features.end(); OnFrameChanged(); AssDialogue *newCurDiag = GetActiveDialogueLine(); if (newCurDiag != curDiag) { curDiag = newCurDiag; OnLineChanged(); } } template void VisualTool::OnActiveLineChanged(AssDialogue *new_line) { if (new_line && !c->subsGrid->IsDisplayed(new_line)) { new_line = NULL; } if (new_line != curDiag) { curDiag = new_line; OnLineChanged(); } } template void VisualTool::SetSelection(feature_iterator feat) { selFeatures.clear(); lineSelCount.clear(); selFeatures.insert(feat); AssDialogue *line = feat->line; if (line) { lineSelCount[line] = 1; Selection sel; sel.insert(line); c->selectionController->SetSelectedSet(sel); } } template void VisualTool::AddSelection(feature_iterator feat) { if (selFeatures.insert(feat).second && feat->line) { lineSelCount[feat->line] += 1; Selection sel = c->selectionController->GetSelectedSet(); if (sel.insert(feat->line).second) { c->selectionController->SetSelectedSet(sel); } } } template void VisualTool::RemoveSelection(feature_iterator feat) { if (selFeatures.erase(feat) > 0 && feat->line) { // Deselect a line only if all features for that line have been // deselected AssDialogue* line = feat->line; lineSelCount[line] -= 1; assert(lineSelCount[line] >= 0); if (lineSelCount[line] <= 0) { Selection sel = c->selectionController->GetSelectedSet(); // Don't deselect the only selected line if (sel.size() <= 1) return; sel.erase(line); // Set the active line to an arbitrary selected line if we just // deselected the active line if (line == c->selectionController->GetActiveLine()) { c->selectionController->SetActiveLine(*sel.begin()); } c->selectionController->SetSelectedSet(sel); } } } template void VisualTool::ClearSelection() { selFeatures.clear(); lineSelCount.clear(); } enum TagFoundType { TAG_NOT_FOUND = 0, PRIMARY_TAG_FOUND, ALT_TAG_FOUND }; /// @brief Get the first value set for a tag /// @param line Line to get the value from /// @param tag Tag to get the value of /// @param n Number of parameters passed /// @return Which tag (if any) was found template static TagFoundType get_value(const AssDialogue *line, wxString tag, size_t n, ...) { wxString alt; if (tag == "\\pos") alt = "\\move"; else if (tag == "\\an") alt = "\\a"; else if (tag == "\\clip") alt = "\\iclip"; for (size_t i = 0; i < line->Blocks.size(); i++) { const AssDialogueBlockOverride *ovr = dynamic_cast(line->Blocks[i]); if (!ovr) continue; for (size_t j=0; j < ovr->Tags.size(); j++) { const AssOverrideTag *cur = ovr->Tags[j]; if ((cur->Name == tag || cur->Name == alt) && cur->Params.size() >= n) { va_list argp; va_start(argp, n); for (size_t j = 0; j < n; j++) { T *val = va_arg(argp, T *); *val = cur->Params[j]->Get(*val); } va_end(argp); return cur->Name == alt ? ALT_TAG_FOUND : PRIMARY_TAG_FOUND; } } } return TAG_NOT_FOUND; } template void VisualTool::GetLinePosition(AssDialogue *diag,int &x, int &y) { int orgx,orgy; GetLinePosition(diag,x,y,orgx,orgy); } template void VisualTool::GetLinePosition(AssDialogue *diag,int &x, int &y, int &orgx, int &orgy) { int margin[4]; for (int i=0;i<4;i++) margin[i] = diag->Margin[i]; int align = 2; AssStyle *style = c->ass->GetStyle(diag->Style); if (style) { align = style->alignment; for (int i=0;i<4;i++) { if (margin[i] == 0) margin[i] = style->Margin[i]; } } int sw,sh; c->videoController->GetScriptSize(sw,sh); // Process margins margin[1] = sw - margin[1]; margin[3] = sh - margin[2]; // Overrides processing diag->ParseASSTags(); if (!get_value(diag, "\\pos", 2, &x, &y)) { if (get_value(diag, "\\an", 1, &align) == ALT_TAG_FOUND) { switch(align) { case 1: case 2: case 3: break; case 5: case 6: case 7: align += 2; break; case 9: case 10: case 11: align -= 5; break; default: align = 2; break; } } // Alignment type int hor = (align - 1) % 3; int vert = (align - 1) / 3; // Calculate positions if (hor == 0) x = margin[0]; else if (hor == 1) x = (margin[0] + margin[1])/2; else if (hor == 2) x = margin[1]; if (vert == 0) y = margin[3]; else if (vert == 1) y = (margin[2] + margin[3])/2; else if (vert == 2) y = margin[2]; } parent->FromScriptCoords(&x, &y); if (!get_value(diag, "\\org", 2, &orgx, &orgy)) { orgx = x; orgy = y; } else { parent->FromScriptCoords(&orgx, &orgy); } diag->ClearBlocks(); } template void VisualTool::GetLineMove(AssDialogue *diag,bool &hasMove,int &x1,int &y1,int &x2,int &y2,int &t1,int &t2) { diag->ParseASSTags(); hasMove = get_value(diag, "\\move", 6, &x1, &y1, &x2, &y2, &t1, &t2) || get_value(diag, "\\move", 4, &x1, &y1, &x2, &y2); if (hasMove) { parent->FromScriptCoords(&x1, &y1); parent->FromScriptCoords(&x2, &y2); } diag->ClearBlocks(); } template void VisualTool::GetLineRotation(AssDialogue *diag,float &rx,float &ry,float &rz) { rx = ry = rz = 0.f; AssStyle *style = c->ass->GetStyle(diag->Style); if (style) { rz = style->angle; } diag->ParseASSTags(); get_value(diag, "\\frx", 1, &rx); get_value(diag, "\\fry", 1, &ry); get_value(diag, "\\frz", 1, &rz); diag->ClearBlocks(); } template void VisualTool::GetLineScale(AssDialogue *diag,float &scalX,float &scalY) { scalX = scalY = 100.f; AssStyle *style = c->ass->GetStyle(diag->Style); if (style) { scalX = style->scalex; scalY = style->scaley; } diag->ParseASSTags(); get_value(diag, "\\fscx", 1, &scalX); get_value(diag, "\\fscy", 1, &scalY); diag->ClearBlocks(); } template void VisualTool::GetLineClip(AssDialogue *diag,int &x1,int &y1,int &x2,int &y2,bool &inverse) { x1 = y1 = 0; int sw,sh; c->videoController->GetScriptSize(sw,sh); x2 = sw-1; y2 = sh-1; inverse = false; diag->ParseASSTags(); inverse = get_value(diag, "\\clip", 4, &x1, &y1, &x2, &y2) == ALT_TAG_FOUND; diag->ClearBlocks(); parent->FromScriptCoords(&x1, &y1); parent->FromScriptCoords(&x2, &y2); } template wxString VisualTool::GetLineVectorClip(AssDialogue *diag,int &scale,bool &inverse) { scale = 1; inverse = false; diag->ParseASSTags(); int x1, y1, x2, y2; TagFoundType res = get_value(diag, "\\clip", 4, &x1, &y1, &x2, &y2); if (res) { inverse = res == ALT_TAG_FOUND; diag->ClearBlocks(); return wxString::Format("m %d %d l %d %d %d %d %d %d", x1, y1, x2, y1, x2, y2, x1, y2); } wxString result; wxString scaleStr; res = get_value(diag, "\\clip", 2, &scaleStr, &result); inverse = res == ALT_TAG_FOUND; if (!scaleStr.empty()) { long s; scaleStr.ToLong(&s); scale = s; } diag->ClearBlocks(); return result; } /// @brief Set override /// @param tag /// @param value template void VisualTool::SetOverride(AssDialogue* line, wxString tag, wxString value) { if (!line) return; wxString removeTag; if (tag == "\\1c") removeTag = "\\c"; else if (tag == "\\fr") removeTag = "\\frz"; else if (tag == "\\pos") removeTag = "\\move"; else if (tag == "\\move") removeTag = "\\pos"; else if (tag == "\\clip") removeTag = "\\iclip"; else if (tag == "\\iclip") removeTag = "\\clip"; wxString insert = tag + value; // Get block at start line->ParseASSTags(); AssDialogueBlock *block = line->Blocks.at(0); // Get current block as plain or override AssDialogueBlockPlain *plain = dynamic_cast(block); AssDialogueBlockOverride *ovr = dynamic_cast(block); assert(dynamic_cast(block) == NULL); if (plain) { line->Text = "{" + insert + "}" + line->Text; } else if (ovr) { // Remove old of same for (size_t i = 0; i < ovr->Tags.size(); i++) { wxString name = ovr->Tags[i]->Name; if (tag == name || removeTag == name) { delete ovr->Tags[i]; ovr->Tags.erase(ovr->Tags.begin() + i); i--; } } ovr->AddTag(insert); line->UpdateText(); } parent->SetFocus(); } // If only export worked template class VisualTool; template class VisualTool; template class VisualTool; template class VisualTool;