Aegisub/aegisub/src/visual_tool_drag.cpp
Thomas Goyne be77dc8307 Mostly rewrite the visual tools and related classes
Convert all coordinates within the visual tools to Vector2D, which has
been significantly extended. Eliminates a lot of issues with accumulated
rounding errors and simplifies a lot of code.

Modernize the visual tools' interactions with the rest of Aegisub by
connecting to signals directly rather than routing everything through
the video display and converting the main visual tool mode toolbar to
the command system.

Extract all references to OpenGL from the visual tools and move them to
OpenGLWrapper as a first step towards making it possible to implement an
alternative video renderer. In the process, eliminate all uses of OpenGL
immediate mode.

Fix a bunch of minor issues and general instability.

Originally committed to SVN as r5823.
2011-11-06 17:18:20 +00:00

311 lines
8.9 KiB
C++

// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
//
// $Id$
/// @file visual_tool_drag.cpp
/// @brief Position all visible subtitles by dragging visual typesetting tool
/// @ingroup visual_ts
#include "config.h"
#include "visual_tool_drag.h"
#ifndef AGI_PRE
#include <wx/bmpbuttn.h>
#include <wx/toolbar.h>
#endif
#include "ass_dialogue.h"
#include "ass_file.h"
#include "include/aegisub/context.h"
#include "libresrc/libresrc.h"
#include "utils.h"
#include "video_context.h"
static const DraggableFeatureType DRAG_ORIGIN = DRAG_BIG_TRIANGLE;
static const DraggableFeatureType DRAG_START = DRAG_BIG_SQUARE;
static const DraggableFeatureType DRAG_END = DRAG_BIG_CIRCLE;
VisualToolDrag::VisualToolDrag(VideoDisplay *parent, agi::Context *context)
: VisualTool<VisualToolDragDraggableFeature>(parent, context)
, primary(0)
, button_is_move(true)
{
c->selectionController->GetSelectedSet(selection);
}
void VisualToolDrag::SetToolbar(wxToolBar *tb) {
toolbar = tb;
toolbar->AddTool(-1, _("Toggle between \\move and \\pos"), GETIMAGE(visual_move_conv_move_24));
toolbar->Realize();
toolbar->Show(true);
toolbar->Bind(wxEVT_COMMAND_TOOL_CLICKED, &VisualToolDrag::OnSubTool, this);
}
void VisualToolDrag::UpdateToggleButtons() {
bool to_move = true;
if (active_line) {
Vector2D p1, p2;
int t1, t2;
to_move = !GetLineMove(active_line, p1, p2, t1, t2);
}
if (to_move == button_is_move) return;
toolbar->SetToolNormalBitmap(toolbar->GetToolByPos(0)->GetId(),
to_move ? GETIMAGE(visual_move_conv_move_24) : GETIMAGE(visual_move_conv_pos_24));
button_is_move = to_move;
}
void VisualToolDrag::OnSubTool(wxCommandEvent &) {
// Toggle \move <-> \pos
VideoContext *vc = c->videoController;
for (Selection::const_iterator cur = selection.begin(); cur != selection.end(); ++cur) {
AssDialogue *line = *cur;
Vector2D p1, p2;
int t1, t2;
bool has_move = GetLineMove(line, p1, p2, t1, t2);
if (has_move)
SetOverride(line, "\\pos", p1.PStr());
else {
p1 = GetLinePosition(line);
// Round the start and end times to exact frames
int start = vc->TimeAtFrame(vc->FrameAtTime(line->Start.GetMS(), agi::vfr::START)) - line->Start.GetMS();
int end = vc->TimeAtFrame(vc->FrameAtTime(line->Start.GetMS(), agi::vfr::END)) - line->Start.GetMS();
SetOverride(line, "\\move", wxString::Format("(%s,%s,%d,%d)", p1.Str(), p1.Str(), start, end));
}
}
Commit();
OnFileChanged();
UpdateToggleButtons();
}
void VisualToolDrag::OnLineChanged() {
UpdateToggleButtons();
}
void VisualToolDrag::OnFileChanged() {
/// @todo it should be possible to preserve the selection in some cases
features.clear();
sel_features.clear();
primary = 0;
active_feature = features.end();
for (entryIter it = c->ass->Line.begin(); it != c->ass->Line.end(); ++it) {
AssDialogue *diag = dynamic_cast<AssDialogue*>(*it);
if (diag && IsDisplayed(diag))
MakeFeatures(diag);
}
UpdateToggleButtons();
}
void VisualToolDrag::OnFrameChanged() {
if (primary && !IsDisplayed(primary->line))
primary = 0;
feature_iterator feat = features.begin();
feature_iterator end = features.end();
for (entryIter it = c->ass->Line.begin(); it != c->ass->Line.end(); ++it) {
AssDialogue *diag = dynamic_cast<AssDialogue*>(*it);
if (!diag) continue;
if (IsDisplayed(diag)) {
// Features don't exist and should
if (feat == end || feat->line != diag)
MakeFeatures(diag, feat);
// Move past already existing features for the line
else
while (feat != end && feat->line == diag) ++feat;
}
else {
// Remove all features for this line (if any)
while (feat != end && feat->line == diag) {
feat->line = 0;
RemoveSelection(feat);
feat = features.erase(feat);
}
}
}
active_feature = features.end();
}
void VisualToolDrag::OnSelectedSetChanged(const Selection &added, const Selection &removed) {
c->selectionController->GetSelectedSet(selection);
for (feature_iterator it = features.begin(); it != features.end(); ++it) {
if (removed.count(it->line))
sel_features.erase(it);
else if (added.count(it->line) && it->type == DRAG_START)
sel_features.insert(it);
}
}
void VisualToolDrag::Draw() {
DrawAllFeatures();
// Draw connecting lines
for (feature_iterator cur = features.begin(); cur != features.end(); ++cur) {
if (cur->type == DRAG_START) continue;
feature_iterator p2 = cur;
feature_iterator p1 = cur->parent;
// Move end marker has an arrow; origin doesn't
bool has_arrow = p2->type == DRAG_END;
int arrow_len = has_arrow ? 10 : 0;
// Don't show the connecting line if the features are very close
Vector2D direction = p2->pos - p1->pos;
if (direction.SquareLen() < (20 + arrow_len) * (20 + arrow_len)) continue;
direction = direction.Unit();
// Get the start and end points of the line
Vector2D start = p1->pos + direction * 10;
Vector2D end = p2->pos - direction * (10 + arrow_len);
if (has_arrow) {
gl.SetLineColour(colour[3], 0.8f, 2);
// Arrow line
gl.DrawLine(start, end);
// Arrow head
Vector2D t_half_base_w = Vector2D(-direction.Y(), direction.X()) * 4;
gl.DrawTriangle(end + direction * arrow_len, end + t_half_base_w, end - t_half_base_w);
}
// Draw dashed line
else {
gl.SetLineColour(colour[3], 0.5f, 2);
gl.DrawDashedLine(start, end, 6);
}
}
}
void VisualToolDrag::MakeFeatures(AssDialogue *diag) {
MakeFeatures(diag, features.end());
}
void VisualToolDrag::MakeFeatures(AssDialogue *diag, feature_iterator pos) {
Vector2D p1 = FromScriptCoords(GetLinePosition(diag));
// Create \pos feature
Feature feat;
feat.pos = p1;
feat.layer = 0;
feat.type = DRAG_START;
feat.time = 0;
feat.line = diag;
feat.parent = features.end();
features.insert(pos, feat);
feature_iterator cur = pos; --cur;
feat.parent = cur;
if (selection.count(diag))
sel_features.insert(cur);
Vector2D p2;
int t1, t2;
// Create move destination feature
if (GetLineMove(diag, p1, p2, t1, t2)) {
feat.pos = FromScriptCoords(p2);
feat.layer = 1;
feat.type = DRAG_END;
feat.parent->time = t1;
feat.time = t2;
feat.line = diag;
features.insert(pos, feat);
feat.parent->parent = --pos; ++pos;
}
// Create org feature
if (Vector2D org = GetLineOrigin(diag)) {
feat.pos = FromScriptCoords(org);
feat.layer = -1;
feat.type = DRAG_ORIGIN;
feat.time = 0;
feat.line = diag;
features.insert(pos, feat);
}
}
bool VisualToolDrag::InitializeDrag(feature_iterator feature) {
primary = &*feature;
// Set time of clicked feature to the current frame and shift all other
// selected features by the same amount
if (feature->type != DRAG_ORIGIN) {
int time = c->videoController->TimeAtFrame(frame_number) - feature->line->Start.GetMS();
int change = time - feature->time;
for (sel_iterator cur = sel_features.begin(); cur != sel_features.end(); ++cur) {
(*cur)->time += change;
}
}
return true;
}
void VisualToolDrag::UpdateDrag(feature_iterator feature) {
if (feature->type == DRAG_ORIGIN) {
SetOverride(feature->line, "\\org", ToScriptCoords(feature->pos).PStr());
return;
}
feature_iterator end_feature = feature->parent;
if (feature->type == DRAG_END)
std::swap(feature, end_feature);
if (feature->parent == features.end())
SetOverride(feature->line, "\\pos", ToScriptCoords(feature->pos).PStr());
else
SetOverride(feature->line, "\\move",
wxString::Format("(%s,%s,%d,%d)",
ToScriptCoords(feature->pos).Str(),
ToScriptCoords(end_feature->pos).Str(),
feature->time, end_feature->time));
}
void VisualToolDrag::OnDoubleClick() {
Vector2D d = ToScriptCoords(mouse_pos) - (primary ? ToScriptCoords(primary->pos) : GetLinePosition(active_line));
Selection sel = c->selectionController->GetSelectedSet();
for (Selection::const_iterator it = sel.begin(); it != sel.end(); ++it) {
Vector2D p1, p2;
int t1, t2;
if (GetLineMove(*it, p1, p2, t1, t2)) {
if (t1 > 0 || t2 > 0)
SetOverride(*it, "\\move", wxString::Format("(%s,%s,%d,%d)", (p1 + d).Str(), (p2 + d).Str(), t1, t2));
else
SetOverride(*it, "\\move", wxString::Format("(%s,%s)", (p1 + d).Str(), (p2 + d).Str()));
}
else
SetOverride(*it, "\\pos", (GetLinePosition(*it) + d).PStr());
if (Vector2D org = GetLineOrigin(*it))
SetOverride(*it, "\\org", (org + d).PStr());
}
Commit(_("positioning"));
OnFileChanged();
}