// 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 #ifdef __APPLE__ #include #else #include #endif #include "ass_dialogue.h" #include "ass_file.h" #include "ass_override.h" #include "ass_style.h" #include "ass_time.h" #include "export_visible_lines.h" #include "main.h" #include "options.h" #include "subs_edit_box.h" #include "subs_grid.h" #include "utils.h" #include "vfr.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, VideoState const& video) : realtime(OPT_GET("Video/Visual Realtime")) , dragStartX(0) , dragStartY(0) , selChanged(false) , parent(parent) , holding(false) , curDiag(NULL) , dragging(false) , externalChange(true) , curFeature(NULL) , dragListOK(false) , frame_n(0) , video(video) , leftClick(false) , leftDClick(false) , shiftDown(false) , ctrlDown(false) , altDown(false) { if (VideoContext::Get()->IsLoaded()) { frame_n = VideoContext::Get()->GetFrameN(); VideoContext::Get()->grid->RegisterSelectionChange(this); } PopulateFeatureList(); } template VisualTool::~VisualTool() { VideoContext::Get()->grid->RegisterSelectionChange(NULL); } template void VisualTool::OnMouseEvent (wxMouseEvent &event) { bool realTime = realtime->GetBool(); bool needRender = false; if (event.Leaving()) { Update(); parent->Render(); return; } 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 (!dragListOK) { PopulateFeatureList(); dragListOK = true; } if (!dragging) { unsigned oldHigh = curFeatureI; GetHighlightedFeature(); if (curFeatureI != oldHigh) needRender = true; } if (dragging) { // continue drag if (event.LeftIsDown()) { for (selection_iterator cur = selFeatures.begin(); cur != selFeatures.end(); ++cur) { features[*cur].x = (video.x - dragStartX + features[*cur].origX); features[*cur].y = (video.y - dragStartY + features[*cur].origY); if (shiftDown) { if (abs(video.x - dragStartX) > abs(video.y - dragStartY)) { features[*cur].y = features[*cur].origY; } else { features[*cur].x = features[*cur].origX; } } UpdateDrag(&features[*cur]); if (realTime) { CommitDrag(&features[*cur]); } } if (realTime) { Commit(); } else { needRender = true; } } // end drag else { if (realTime) AssLimitToVisibleFilter::SetFrame(-1); 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(curFeatureI); } else { // deselect everything else ClearSelection(); AddSelection(curFeatureI); } SetEditbox(); } } else { for (selection_iterator cur = selFeatures.begin(); cur != selFeatures.end(); ++cur) { CommitDrag(&features[*cur]); } Commit(true); } curFeature = NULL; parent->ReleaseMouse(); parent->SetFocus(); } } else if (holding) { // continue hold if (event.LeftIsDown()) { UpdateHold(); if (realTime) { CommitHold(); Commit(); } else { needRender = true; } } // end hold else { if (realTime) AssLimitToVisibleFilter::SetFrame(-1); holding = false; CommitHold(); Commit(true); curDiag = NULL; parent->ReleaseMouse(); parent->SetFocus(); } } else if (leftClick) { // start drag if (curFeature) { if (InitializeDrag(curFeature)) { if (selFeatures.find(curFeatureI) == selFeatures.end()) { selChanged = true; if (!ctrlDown) { ClearSelection(); } AddSelection(curFeatureI); } else { selChanged = false; } SetEditbox(curFeature->lineN); dragStartX = video.x; dragStartY = video.y; for (selection_iterator cur = selFeatures.begin(); cur != selFeatures.end(); ++cur) { features[*cur].origX = features[*cur].x; features[*cur].origY = features[*cur].y; } dragging = true; parent->CaptureMouse(); if (realTime) AssLimitToVisibleFilter::SetFrame(frame_n); } } // start hold else { if (!altDown) { ClearSelection(); SetEditbox(); needRender = true; } curDiag = GetActiveDialogueLine(); if (curDiag && InitializeHold()) { holding = true; parent->CaptureMouse(); if (realTime) AssLimitToVisibleFilter::SetFrame(frame_n); } } } if (Update() || needRender) parent->Render(); externalChange = true; } template void VisualTool::Commit(bool full, wxString message) { SubtitlesGrid *grid = VideoContext::Get()->grid; if (full) { if (message.empty()) { message = _("visual typesetting"); } grid->ass->FlagAsModified(message); } grid->CommitChanges(false,!full); if (full) grid->editBox->Update(false, true, false); } template AssDialogue* VisualTool::GetActiveDialogueLine() { SubtitlesGrid *grid = VideoContext::Get()->grid; AssDialogue *diag = grid->GetDialogue(grid->editBox->linen); if (grid->IsDisplayed(diag)) return diag; return NULL; } template void VisualTool::GetHighlightedFeature() { int highestLayerFound = INT_MIN; curFeature = NULL; curFeatureI = -1; unsigned i = 0; for (feature_iterator cur = features.begin(); cur != features.end(); ++cur, ++i) { if (cur->IsMouseOver(video.x, video.y) && cur->layer > highestLayerFound) { curFeature = &*cur; curFeatureI = i; highestLayerFound = cur->layer; } } } template void VisualTool::DrawAllFeatures() { if (!dragListOK) { PopulateFeatureList(); dragListOK = true; } SetLineColour(colour[0],1.0f,2); for (unsigned i = 0; i < features.size(); ++i) { int fill; if (&features[i] == curFeature) fill = 2; else if (selFeatures.find(i) != selFeatures.end()) fill = 3; else fill = 1; SetFillColour(colour[fill],0.6f); features[i].Draw(*this); } } template void VisualTool::Refresh() { frame_n = VideoContext::Get()->GetFrameN(); if (externalChange) { dragListOK = false; DoRefresh(); } } template void VisualTool::AddSelection(unsigned i) { assert(i < features.size()); if (selFeatures.insert(i).second && features[i].line) { lineSelCount[features[i].lineN] += 1; SubtitlesGrid *grid = VideoContext::Get()->grid; grid->SelectRow(features[i].lineN, true); } } template void VisualTool::RemoveSelection(unsigned i) { assert(i < features.size()); if (selFeatures.erase(i) > 0 && features[i].line) { // Deselect a line only if all features for that line have been // deselected int lineN = features[i].lineN; lineSelCount[lineN] -= 1; assert(lineSelCount[lineN] >= 0); if (lineSelCount[lineN] <= 0) { SubtitlesGrid *grid = VideoContext::Get()->grid; grid->SelectRow(lineN, true, false); // We may have just deselected the active line, so make sure the // edit box is set to something sane SetEditbox(); } } } template wxArrayInt VisualTool::GetSelection() { return VideoContext::Get()->grid->GetSelection(); } template void VisualTool::ClearSelection(bool hard) { if (hard) { VideoContext::Get()->grid->SelectRow(0, false, false); } selFeatures.clear(); lineSelCount.clear(); } template void VisualTool::SetEditbox(int lineN) { VideoContext* con = VideoContext::Get(); if (lineN > -1) { con->grid->editBox->SetToLine(lineN); con->grid->SelectRow(lineN, true); } else { wxArrayInt sel = GetSelection(); // If there is a selection and the edit box's line is in it, do nothing // Otherwise set the edit box if there is a selection or the selection // to the edit box if there is no selection if (sel.empty()) { con->grid->SelectRow(con->grid->editBox->linen, true); return; } else if (!std::binary_search(sel.begin(), sel.end(), con->grid->editBox->linen)) { con->grid->editBox->SetToLine(sel[0]); } } } /// @brief Get position of line /// @param diag /// @param x /// @param y template void VisualTool::GetLinePosition(AssDialogue *diag,int &x, int &y) { int orgx,orgy; GetLinePosition(diag,x,y,orgx,orgy); } /// @brief DOCME /// @param diag /// @param x /// @param y /// @param orgx /// @param orgy template void VisualTool::GetLinePosition(AssDialogue *diag,int &x, int &y, int &orgx, int &orgy) { if (!diag) { x = INT_MIN; y = INT_MIN; orgx = INT_MIN; orgy = INT_MIN; return; } int margin[4]; for (int i=0;i<4;i++) margin[i] = diag->Margin[i]; int align = 2; AssStyle *style = VideoContext::Get()->grid->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; VideoContext::Get()->GetScriptSize(sw,sh); // Process margins margin[3] = margin[2]; margin[1] = sw - margin[1]; margin[3] = sh - margin[3]; // Position bool posSet = false; bool orgSet = false; // Overrides processing diag->ParseASSTags(); AssDialogueBlockOverride *override; AssOverrideTag *tag; size_t blockn = diag->Blocks.size(); for (size_t i=0;i(diag->Blocks.at(i)); if (override) { for (size_t j=0;jTags.size();j++) { tag = override->Tags.at(j); // Position if ((tag->Name == L"\\pos" || tag->Name == L"\\move") && tag->Params.size() >= 2) { if (!posSet) { x = tag->Params[0]->Get(); y = tag->Params[1]->Get(); posSet = true; } } // Alignment else if ((tag->Name == L"\\an" || tag->Name == L"\\a") && tag->Params.size() >= 1) { align = tag->Params[0]->Get(); if (tag->Name == L"\\a") { 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; } } } // Origin else if (!orgSet && tag->Name == L"\\org" && tag->Params.size() >= 2) { orgx = tag->Params[0]->Get(); orgy = tag->Params[1]->Get(); parent->FromScriptCoords(&orgx, &orgy); orgSet = true; } } } } diag->ClearBlocks(); if (!posSet) { // 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); // No origin? if (!orgSet) { orgx = x; orgy = y; } } /// @brief Get the destination of move, if any /// @param diag /// @param hasMove /// @param x1 /// @param y1 /// @param x2 /// @param y2 /// @param t1 /// @param t2 template void VisualTool::GetLineMove(AssDialogue *diag,bool &hasMove,int &x1,int &y1,int &x2,int &y2,int &t1,int &t2) { // Parse tags hasMove = false; diag->ParseASSTags(); AssDialogueBlockOverride *override; AssOverrideTag *tag; size_t blockn = diag->Blocks.size(); // For each block for (size_t i=0;i(diag->Blocks.at(i)); if (override) { for (size_t j=0;jTags.size();j++) { tag = override->Tags.at(j); // Position if (tag->Name == L"\\move" && tag->Params.size() >= 4) { hasMove = true; x1 = tag->Params[0]->Get(); y1 = tag->Params[1]->Get(); x2 = tag->Params[2]->Get(); y2 = tag->Params[3]->Get(); parent->FromScriptCoords(&x1, &y1); parent->FromScriptCoords(&x2, &y2); if (tag->Params.size() >= 6 && !tag->Params[4]->ommited && !tag->Params[5]->ommited) { t1 = tag->Params[4]->Get(); t2 = tag->Params[5]->Get(); } return; } } } } diag->ClearBlocks(); } /// @brief Get line's rotation /// @param diag /// @param rx /// @param ry /// @param rz template void VisualTool::GetLineRotation(AssDialogue *diag,float &rx,float &ry,float &rz) { // Default values rx = ry = rz = 0.0f; // No dialogue if (!diag) return; AssStyle *style = VideoContext::Get()->grid->ass->GetStyle(diag->Style); if (style) { rz = style->angle; } // Prepare overrides diag->ParseASSTags(); AssDialogueBlockOverride *override; AssOverrideTag *tag; size_t blockn = diag->Blocks.size(); if (blockn == 0) { diag->ClearBlocks(); return; } // Process override override = dynamic_cast(diag->Blocks.at(0)); if (override) { for (size_t j=0;jTags.size();j++) { tag = override->Tags.at(j); if (tag->Name == L"\\frx" && tag->Params.size() == 1) { rx = tag->Params[0]->Get(); } if (tag->Name == L"\\fry" && tag->Params.size() == 1) { ry = tag->Params[0]->Get(); } if ((tag->Name == L"\\frz" || tag->Name == L"\fr") && tag->Params.size() == 1) { rz = tag->Params[0]->Get(); } } } diag->ClearBlocks(); } /// @brief Get line's scale /// @param diag /// @param scalX /// @param scalY template void VisualTool::GetLineScale(AssDialogue *diag,float &scalX,float &scalY) { // Default values scalX = scalY = 100.0f; // Prepare overrides diag->ParseASSTags(); AssDialogueBlockOverride *override; AssOverrideTag *tag; size_t blockn = diag->Blocks.size(); if (blockn == 0) { diag->ClearBlocks(); return; } // Process override override = dynamic_cast(diag->Blocks.at(0)); if (override) { for (size_t j=0;jTags.size();j++) { tag = override->Tags.at(j); if (tag->Name == L"\\fscx" && tag->Params.size() == 1) { scalX = tag->Params[0]->Get(); } if (tag->Name == L"\\fscy" && tag->Params.size() == 1) { scalY = tag->Params[0]->Get(); } } } diag->ClearBlocks(); } /// @brief Get line's clip /// @param diag /// @param x1 /// @param y1 /// @param x2 /// @param y2 /// @param inverse template void VisualTool::GetLineClip(AssDialogue *diag,int &x1,int &y1,int &x2,int &y2,bool &inverse) { // Default values x1 = y1 = 0; int sw,sh; VideoContext::Get()->GetScriptSize(sw,sh); x2 = sw-1; y2 = sh-1; inverse = false; // Prepare overrides diag->ParseASSTags(); AssDialogueBlockOverride *override; AssOverrideTag *tag; size_t blockn = diag->Blocks.size(); if (blockn == 0) { diag->ClearBlocks(); return; } // Process override override = dynamic_cast(diag->Blocks.at(0)); if (override) { for (size_t j=0;jTags.size();j++) { tag = override->Tags.at(j); if (tag->Name == L"\\clip" && tag->Params.size() == 4) { x1 = tag->Params[0]->Get(); y1 = tag->Params[1]->Get(); x2 = tag->Params[2]->Get(); y2 = tag->Params[3]->Get(); inverse = false; } else if (tag->Name == L"\\iclip" && tag->Params.size() == 4) { x1 = tag->Params[0]->Get(); y1 = tag->Params[1]->Get(); x2 = tag->Params[2]->Get(); y2 = tag->Params[3]->Get(); inverse = true; } } } diag->ClearBlocks(); parent->FromScriptCoords(&x1, &y1); parent->FromScriptCoords(&x2, &y2); } /// @brief Get line vector clip, if it exists /// @param diag /// @param scale /// @param inverse template wxString VisualTool::GetLineVectorClip(AssDialogue *diag,int &scale,bool &inverse) { // Prepare overrides wxString result; scale = 1; inverse = false; diag->ParseASSTags(); AssDialogueBlockOverride *override; AssOverrideTag *tag; size_t blockn = diag->Blocks.size(); if (blockn == 0) { diag->ClearBlocks(); return result; } // Process override override = dynamic_cast(diag->Blocks.at(0)); if (override) { for (size_t j=0;jTags.size();j++) { tag = override->Tags.at(j); if (tag->Name == L"\\clip" || tag->Name == L"\\iclip") { if (tag->Params.size() == 1) { result = tag->Params[0]->Get(); } else if (tag->Params.size() == 2) { scale = tag->Params[0]->Get(); result = tag->Params[1]->Get(); } else if (tag->Params.size() == 4) { int x1 = tag->Params[0]->Get(), y1 = tag->Params[1]->Get(), x2 = tag->Params[2]->Get(), y2 = tag->Params[3]->Get(); result = wxString::Format(L"m %d %d l %d %d %d %d %d %d", x1, y1, x2, y1, x2, y2, x1, y2); } inverse = tag->Name == L"\\iclip"; } } } 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 == L"\\1c") removeTag = L"\\c"; else if (tag == L"\\fr") removeTag = L"\\frz"; else if (tag == L"\\pos") removeTag = L"\\move"; else if (tag == L"\\move") removeTag = L"\\pos"; else if (tag == L"\\clip") removeTag = L"\\iclip"; else if (tag == L"\\iclip") removeTag = L"\\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 = L"{" + insert + L"}" + 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;