1 // Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
2 //
3 // Permission to use, copy, modify, and distribute this software for any
4 // purpose with or without fee is hereby granted, provided that the above
5 // copyright notice and this permission notice appear in all copies.
6 //
7 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 //
15 // Aegisub Project http://www.aegisub.org/
16 
17 /// @file visual_tool_drag.cpp
18 /// @brief Position all visible subtitles by dragging visual typesetting tool
19 /// @ingroup visual_ts
20 
21 #include "visual_tool_drag.h"
22 
23 #include "ass_dialogue.h"
24 #include "ass_file.h"
25 #include "include/aegisub/context.h"
26 #include "libresrc/libresrc.h"
27 #include "options.h"
28 #include "selection_controller.h"
29 #include "video_controller.h"
30 #include "video_display.h"
31 
32 #include <libaegisub/format.h>
33 #include <libaegisub/make_unique.h>
34 
35 #include <algorithm>
36 #include <boost/range/algorithm/binary_search.hpp>
37 
38 #include <wx/toolbar.h>
39 
40 static const DraggableFeatureType DRAG_ORIGIN = DRAG_BIG_TRIANGLE;
41 static const DraggableFeatureType DRAG_START = DRAG_BIG_SQUARE;
42 static const DraggableFeatureType DRAG_END = DRAG_BIG_CIRCLE;
43 
44 #define ICON(name) (OPT_GET("App/Toolbar Icon Size")->GetInt() == 16 ? GETIMAGE(name ## _16) : GETIMAGE(name ## _24))
45 
VisualToolDrag(VideoDisplay * parent,agi::Context * context)46 VisualToolDrag::VisualToolDrag(VideoDisplay *parent, agi::Context *context)
47 : VisualTool<VisualToolDragDraggableFeature>(parent, context)
48 {
49 	connections.push_back(c->selectionController->AddSelectionListener(&VisualToolDrag::OnSelectedSetChanged, this));
50 	auto const& sel_set = c->selectionController->GetSelectedSet();
51 	selection.insert(begin(selection), begin(sel_set), end(sel_set));
52 }
53 
SetToolbar(wxToolBar * tb)54 void VisualToolDrag::SetToolbar(wxToolBar *tb) {
55 	toolbar = tb;
56 	toolbar->AddTool(-1, _("Toggle between \\move and \\pos"), ICON(visual_move_conv_move));
57 	toolbar->Realize();
58 	toolbar->Show(true);
59 
60 	toolbar->Bind(wxEVT_TOOL, &VisualToolDrag::OnSubTool, this);
61 }
62 
UpdateToggleButtons()63 void VisualToolDrag::UpdateToggleButtons() {
64 	bool to_move = true;
65 	if (active_line) {
66 		Vector2D p1, p2;
67 		int t1, t2;
68 		to_move = !GetLineMove(active_line, p1, p2, t1, t2);
69 	}
70 
71 	if (to_move == button_is_move) return;
72 
73 	toolbar->SetToolNormalBitmap(toolbar->GetToolByPos(0)->GetId(),
74 		to_move ? ICON(visual_move_conv_move) : ICON(visual_move_conv_pos));
75 	button_is_move = to_move;
76 }
77 
OnSubTool(wxCommandEvent &)78 void VisualToolDrag::OnSubTool(wxCommandEvent &) {
79 	// Toggle \move <-> \pos
80 	VideoController *vc = c->videoController.get();
81 	for (auto line : selection) {
82 		Vector2D p1, p2;
83 		int t1, t2;
84 
85 		bool has_move = GetLineMove(line, p1, p2, t1, t2);
86 
87 		if (has_move)
88 			SetOverride(line, "\\pos", p1.PStr());
89 		else {
90 			p1 = GetLinePosition(line);
91 			// Round the start and end times to exact frames
92 			int start = vc->TimeAtFrame(vc->FrameAtTime(line->Start, agi::vfr::START)) - line->Start;
93 			int end = vc->TimeAtFrame(vc->FrameAtTime(line->Start, agi::vfr::END)) - line->Start;
94 			SetOverride(line, "\\move", agi::format("(%s,%s,%d,%d)", p1.Str(), p1.Str(), start, end));
95 		}
96 	}
97 
98 	Commit();
99 	OnFileChanged();
100 	UpdateToggleButtons();
101 }
102 
OnLineChanged()103 void VisualToolDrag::OnLineChanged() {
104 	UpdateToggleButtons();
105 }
106 
OnFileChanged()107 void VisualToolDrag::OnFileChanged() {
108 	/// @todo it should be possible to preserve the selection in some cases
109 	features.clear();
110 	sel_features.clear();
111 	primary = nullptr;
112 	active_feature = nullptr;
113 
114 	for (auto& diag : c->ass->Events) {
115 		if (IsDisplayed(&diag))
116 			MakeFeatures(&diag);
117 	}
118 
119 	UpdateToggleButtons();
120 }
121 
OnFrameChanged()122 void VisualToolDrag::OnFrameChanged() {
123 	if (primary && !IsDisplayed(primary->line))
124 		primary = nullptr;
125 
126 	auto feat = features.begin();
127 	auto end = features.end();
128 
129 	for (auto& diag : c->ass->Events) {
130 		if (IsDisplayed(&diag)) {
131 			// Features don't exist and should
132 			if (feat == end || feat->line != &diag)
133 				MakeFeatures(&diag, feat);
134 			// Move past already existing features for the line
135 			else
136 				while (feat != end && feat->line == &diag) ++feat;
137 		}
138 		else {
139 			// Remove all features for this line (if any)
140 			while (feat != end && feat->line == &diag) {
141 				if (&*feat == active_feature) active_feature = nullptr;
142 				feat->line = nullptr;
143 				RemoveSelection(&*feat);
144 				feat = features.erase(feat);
145 			}
146 		}
147 	}
148 }
149 
line_not_present(C const & set,T const & it)150 template<class C, class T> static bool line_not_present(C const& set, T const& it) {
151 	return std::none_of(set.begin(), set.end(), [&](typename C::value_type const& cmp) {
152 		return cmp->line == it->line;
153 	});
154 }
155 
OnSelectedSetChanged()156 void VisualToolDrag::OnSelectedSetChanged() {
157 	auto const& new_sel_set = c->selectionController->GetSelectedSet();
158 	std::vector<AssDialogue *> new_sel(begin(new_sel_set), end(new_sel_set));
159 
160 	bool any_changed = false;
161 	for (auto it = features.begin(); it != features.end(); ) {
162 		bool was_selected = boost::binary_search(selection, it->line);
163 		bool is_selected = boost::binary_search(new_sel, it->line);
164 		if (was_selected && !is_selected) {
165 			sel_features.erase(&*it++);
166 			any_changed = true;
167 		}
168 		else {
169 			if (is_selected && !was_selected && it->type == DRAG_START && line_not_present(sel_features, it)) {
170 				sel_features.insert(&*it);
171 				any_changed = true;
172 			}
173 			++it;
174 		}
175 	}
176 
177 	if (any_changed)
178 		parent->Render();
179 	selection = std::move(new_sel);
180 }
181 
Draw()182 void VisualToolDrag::Draw() {
183 	DrawAllFeatures();
184 
185 	// Draw connecting lines
186 	for (auto& feature : features) {
187 		if (feature.type == DRAG_START) continue;
188 
189 		Feature *p2 = &feature;
190 		Feature *p1 = feature.parent;
191 
192 		// Move end marker has an arrow; origin doesn't
193 		bool has_arrow = p2->type == DRAG_END;
194 		int arrow_len = has_arrow ? 10 : 0;
195 
196 		// Don't show the connecting line if the features are very close
197 		Vector2D direction = p2->pos - p1->pos;
198 		if (direction.SquareLen() < (20 + arrow_len) * (20 + arrow_len)) continue;
199 
200 		direction = direction.Unit();
201 		// Get the start and end points of the line
202 		Vector2D start = p1->pos + direction * 10;
203 		Vector2D end = p2->pos - direction * (10 + arrow_len);
204 
205 		if (has_arrow) {
206 			gl.SetLineColour(colour[3], 0.8f, 2);
207 
208 			// Arrow line
209 			gl.DrawLine(start, end);
210 
211 			// Arrow head
212 			Vector2D t_half_base_w = Vector2D(-direction.Y(), direction.X()) * 4;
213 			gl.DrawTriangle(end + direction * arrow_len, end + t_half_base_w, end - t_half_base_w);
214 		}
215 		// Draw dashed line
216 		else {
217 			gl.SetLineColour(colour[3], 0.5f, 2);
218 			gl.DrawDashedLine(start, end, 6);
219 		}
220 	}
221 }
222 
MakeFeatures(AssDialogue * diag)223 void VisualToolDrag::MakeFeatures(AssDialogue *diag) {
224 	MakeFeatures(diag, features.end());
225 }
226 
MakeFeatures(AssDialogue * diag,feature_list::iterator pos)227 void VisualToolDrag::MakeFeatures(AssDialogue *diag, feature_list::iterator pos) {
228 	Vector2D p1 = FromScriptCoords(GetLinePosition(diag));
229 
230 	// Create \pos feature
231 	auto feat = agi::make_unique<Feature>();
232 	auto parent = feat.get();
233 	feat->pos = p1;
234 	feat->type = DRAG_START;
235 	feat->line = diag;
236 
237 	if (boost::binary_search(selection, diag))
238 		sel_features.insert(feat.get());
239 	features.insert(pos, *feat.release());
240 
241 	Vector2D p2;
242 	int t1, t2;
243 
244 	// Create move destination feature
245 	if (GetLineMove(diag, p1, p2, t1, t2)) {
246 		feat = agi::make_unique<Feature>();
247 		feat->pos = FromScriptCoords(p2);
248 		feat->layer = 1;
249 		feat->type = DRAG_END;
250 		feat->time = t2;
251 		feat->line = diag;
252 		feat->parent = parent;
253 
254 		parent->time = t1;
255 		parent->parent = feat.get();
256 
257 		features.insert(pos, *feat.release());
258 	}
259 
260 	// Create org feature
261 	if (Vector2D org = GetLineOrigin(diag)) {
262 		feat = agi::make_unique<Feature>();
263 		feat->pos = FromScriptCoords(org);
264 		feat->layer = -1;
265 		feat->type = DRAG_ORIGIN;
266 		feat->time = 0;
267 		feat->line = diag;
268 		feat->parent = parent;
269 		features.insert(pos, *feat.release());
270 	}
271 }
272 
InitializeDrag(Feature * feature)273 bool VisualToolDrag::InitializeDrag(Feature *feature) {
274 	primary = feature;
275 
276 	// Set time of clicked feature to the current frame and shift all other
277 	// selected features by the same amount
278 	if (feature->type != DRAG_ORIGIN) {
279 		int time = c->videoController->TimeAtFrame(frame_number) - feature->line->Start;
280 		int change = time - feature->time;
281 
282 		for (auto feat : sel_features)
283 			feat->time += change;
284 	}
285 	return true;
286 }
287 
UpdateDrag(Feature * feature)288 void VisualToolDrag::UpdateDrag(Feature *feature) {
289 	if (feature->type == DRAG_ORIGIN) {
290 		SetOverride(feature->line, "\\org", ToScriptCoords(feature->pos).PStr());
291 		return;
292 	}
293 
294 	Feature *end_feature = feature->parent;
295 	if (feature->type == DRAG_END)
296 		std::swap(feature, end_feature);
297 
298 	if (!feature->parent)
299 		SetOverride(feature->line, "\\pos", ToScriptCoords(feature->pos).PStr());
300 	else
301 		SetOverride(feature->line, "\\move", agi::format("(%s,%s,%d,%d)"
302 			, ToScriptCoords(feature->pos).Str()
303 			, ToScriptCoords(end_feature->pos).Str()
304 			, feature->time , end_feature->time));
305 }
306 
OnDoubleClick()307 void VisualToolDrag::OnDoubleClick() {
308 	Vector2D d = ToScriptCoords(mouse_pos) - (primary ? ToScriptCoords(primary->pos) : GetLinePosition(active_line));
309 
310 	for (auto line : c->selectionController->GetSelectedSet()) {
311 		Vector2D p1, p2;
312 		int t1, t2;
313 		if (GetLineMove(line, p1, p2, t1, t2)) {
314 			if (t1 > 0 || t2 > 0)
315 				SetOverride(line, "\\move", agi::format("(%s,%s,%d,%d)", (p1 + d).Str(), (p2 + d).Str(), t1, t2));
316 			else
317 				SetOverride(line, "\\move", agi::format("(%s,%s)", (p1 + d).Str(), (p2 + d).Str()));
318 		}
319 		else
320 			SetOverride(line, "\\pos", (GetLinePosition(line) + d).PStr());
321 
322 		if (Vector2D org = GetLineOrigin(line))
323 			SetOverride(line, "\\org", (org + d).PStr());
324 	}
325 
326 	Commit(_("positioning"));
327 
328 	OnFileChanged();
329 }
330