1 // Copyright (c) 2013, 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.cpp
18 /// @brief Base class for visual typesetting functions
19 /// @ingroup visual_ts
20
21 #include "visual_tool.h"
22
23 #include "ass_dialogue.h"
24 #include "ass_file.h"
25 #include "ass_style.h"
26 #include "include/aegisub/context.h"
27 #include "selection_controller.h"
28 #include "video_controller.h"
29 #include "video_display.h"
30 #include "visual_tool_clip.h"
31 #include "visual_tool_drag.h"
32 #include "visual_tool_vector_clip.h"
33
34 #include <libaegisub/ass/time.h>
35 #include <libaegisub/format.h>
36 #include <libaegisub/of_type_adaptor.h>
37
38 #include <algorithm>
39
40 const wxColour VisualToolBase::colour[] = {
41 wxColour(106,32,19),
42 wxColour(255,169,40),
43 wxColour(255,253,185),
44 wxColour(187,0,0)
45 };
46
VisualToolBase(VideoDisplay * parent,agi::Context * context)47 VisualToolBase::VisualToolBase(VideoDisplay *parent, agi::Context *context)
48 : c(context)
49 , parent(parent)
50 , frame_number(c->videoController->GetFrameN())
51 , file_changed_connection(c->ass->AddCommitListener(&VisualToolBase::OnCommit, this))
52 {
53 int script_w, script_h;
54 c->ass->GetResolution(script_w, script_h);
55 script_res = Vector2D(script_w, script_h);
56 active_line = GetActiveDialogueLine();
57 connections.push_back(c->selectionController->AddActiveLineListener(&VisualToolBase::OnActiveLineChanged, this));
58 connections.push_back(c->videoController->AddSeekListener(&VisualToolBase::OnSeek, this));
59 parent->Bind(wxEVT_MOUSE_CAPTURE_LOST, &VisualToolBase::OnMouseCaptureLost, this);
60 }
61
OnCommit(int type)62 void VisualToolBase::OnCommit(int type) {
63 holding = false;
64 dragging = false;
65
66 if (type == AssFile::COMMIT_NEW || type & AssFile::COMMIT_SCRIPTINFO) {
67 int script_w, script_h;
68 c->ass->GetResolution(script_w, script_h);
69 script_res = Vector2D(script_w, script_h);
70 OnCoordinateSystemsChanged();
71 }
72
73 if (type & AssFile::COMMIT_DIAG_FULL || type & AssFile::COMMIT_DIAG_ADDREM) {
74 active_line = GetActiveDialogueLine();
75 OnFileChanged();
76 }
77 }
78
OnSeek(int new_frame)79 void VisualToolBase::OnSeek(int new_frame) {
80 if (frame_number == new_frame) return;
81
82 frame_number = new_frame;
83 OnFrameChanged();
84
85 AssDialogue *new_line = GetActiveDialogueLine();
86 if (new_line != active_line) {
87 dragging = false;
88 active_line = new_line;
89 OnLineChanged();
90 }
91 }
92
OnMouseCaptureLost(wxMouseCaptureLostEvent &)93 void VisualToolBase::OnMouseCaptureLost(wxMouseCaptureLostEvent &) {
94 holding = false;
95 dragging = false;
96 }
97
OnActiveLineChanged(AssDialogue * new_line)98 void VisualToolBase::OnActiveLineChanged(AssDialogue *new_line) {
99 if (!IsDisplayed(new_line))
100 new_line = nullptr;
101
102 holding = false;
103 dragging = false;
104 if (new_line != active_line) {
105 active_line = new_line;
106 OnLineChanged();
107 parent->Render();
108 }
109 }
110
IsDisplayed(AssDialogue * line) const111 bool VisualToolBase::IsDisplayed(AssDialogue *line) const {
112 int frame = c->videoController->GetFrameN();
113 return line
114 && !line->Comment
115 && c->videoController->FrameAtTime(line->Start, agi::vfr::START) <= frame
116 && c->videoController->FrameAtTime(line->End, agi::vfr::END) >= frame;
117 }
118
Commit(wxString message)119 void VisualToolBase::Commit(wxString message) {
120 file_changed_connection.Block();
121 if (message.empty())
122 message = _("visual typesetting");
123
124 commit_id = c->ass->Commit(message, AssFile::COMMIT_DIAG_TEXT, commit_id);
125 file_changed_connection.Unblock();
126 }
127
GetActiveDialogueLine()128 AssDialogue* VisualToolBase::GetActiveDialogueLine() {
129 AssDialogue *diag = c->selectionController->GetActiveLine();
130 if (IsDisplayed(diag))
131 return diag;
132 return nullptr;
133 }
134
SetDisplayArea(int x,int y,int w,int h)135 void VisualToolBase::SetDisplayArea(int x, int y, int w, int h) {
136 if (x == video_pos.X() && y == video_pos.Y() && w == video_res.X() && h == video_res.Y()) return;
137
138 video_pos = Vector2D(x, y);
139 video_res = Vector2D(w, h);
140
141 holding = false;
142 dragging = false;
143 if (parent->HasCapture())
144 parent->ReleaseMouse();
145 OnCoordinateSystemsChanged();
146 }
147
ToScriptCoords(Vector2D point) const148 Vector2D VisualToolBase::ToScriptCoords(Vector2D point) const {
149 return (point - video_pos) * script_res / video_res;
150 }
151
FromScriptCoords(Vector2D point) const152 Vector2D VisualToolBase::FromScriptCoords(Vector2D point) const {
153 return (point * video_res / script_res) + video_pos;
154 }
155
156 template<class FeatureType>
VisualTool(VideoDisplay * parent,agi::Context * context)157 VisualTool<FeatureType>::VisualTool(VideoDisplay *parent, agi::Context *context)
158 : VisualToolBase(parent, context)
159 {
160 }
161
162 template<class FeatureType>
OnMouseEvent(wxMouseEvent & event)163 void VisualTool<FeatureType>::OnMouseEvent(wxMouseEvent &event) {
164 bool left_click = event.LeftDown();
165 bool left_double = event.LeftDClick();
166 shift_down = event.ShiftDown();
167 ctrl_down = event.CmdDown();
168 alt_down = event.AltDown();
169
170 mouse_pos = event.GetPosition();
171
172 if (event.Leaving()) {
173 mouse_pos = Vector2D();
174 parent->Render();
175 return;
176 }
177
178 if (!dragging) {
179 int max_layer = INT_MIN;
180 active_feature = nullptr;
181 for (auto& feature : features) {
182 if (feature.IsMouseOver(mouse_pos) && feature.layer >= max_layer) {
183 active_feature = &feature;
184 max_layer = feature.layer;
185 }
186 }
187 }
188
189 if (dragging) {
190 // continue drag
191 if (event.LeftIsDown()) {
192 for (auto sel : sel_features)
193 sel->UpdateDrag(mouse_pos - drag_start, shift_down);
194 for (auto sel : sel_features)
195 UpdateDrag(sel);
196 Commit();
197 }
198 // end drag
199 else {
200 dragging = false;
201
202 // mouse didn't move, fiddle with selection
203 if (active_feature && !active_feature->HasMoved()) {
204 // Don't deselect stuff that was selected in this click's mousedown event
205 if (!sel_changed) {
206 if (ctrl_down)
207 RemoveSelection(active_feature);
208 else
209 SetSelection(active_feature, true);
210 }
211 }
212
213 active_feature = nullptr;
214 parent->ReleaseMouse();
215 parent->SetFocus();
216 }
217 }
218 else if (holding) {
219 if (!event.LeftIsDown()) {
220 holding = false;
221
222 parent->ReleaseMouse();
223 parent->SetFocus();
224 }
225
226 UpdateHold();
227 Commit();
228
229 }
230 else if (left_click) {
231 drag_start = mouse_pos;
232
233 // start drag
234 if (active_feature) {
235 if (!sel_features.count(active_feature)) {
236 sel_changed = true;
237 SetSelection(active_feature, !ctrl_down);
238 }
239 else
240 sel_changed = false;
241
242 if (active_feature->line)
243 c->selectionController->SetActiveLine(active_feature->line);
244
245 if (InitializeDrag(active_feature)) {
246 for (auto sel : sel_features) sel->StartDrag();
247 dragging = true;
248 parent->CaptureMouse();
249 }
250 }
251 // start hold
252 else {
253 if (!alt_down && features.size() > 1) {
254 sel_features.clear();
255 c->selectionController->SetSelectedSet({ c->selectionController->GetActiveLine() });
256 }
257 if (active_line && InitializeHold()) {
258 holding = true;
259 parent->CaptureMouse();
260 }
261 }
262 }
263
264 if (active_line && left_double)
265 OnDoubleClick();
266
267 parent->Render();
268
269 // Only coalesce the changes made in a single drag
270 if (!event.LeftIsDown())
271 commit_id = -1;
272 }
273
274 template<class FeatureType>
DrawAllFeatures()275 void VisualTool<FeatureType>::DrawAllFeatures() {
276 gl.SetLineColour(colour[0], 1.0f, 1);
277 for (auto& feature : features) {
278 int fill = 1;
279 if (&feature == active_feature)
280 fill = 2;
281 else if (sel_features.count(&feature))
282 fill = 3;
283 gl.SetFillColour(colour[fill], 0.3f);
284 feature.Draw(gl);
285 }
286 }
287
288 template<class FeatureType>
SetSelection(FeatureType * feat,bool clear)289 void VisualTool<FeatureType>::SetSelection(FeatureType *feat, bool clear) {
290 if (clear)
291 sel_features.clear();
292
293 if (sel_features.insert(feat).second && feat->line) {
294 Selection sel;
295 if (!clear)
296 sel = c->selectionController->GetSelectedSet();
297 if (sel.insert(feat->line).second)
298 c->selectionController->SetSelectedSet(std::move(sel));
299 }
300 }
301
302 template<class FeatureType>
RemoveSelection(FeatureType * feat)303 void VisualTool<FeatureType>::RemoveSelection(FeatureType *feat) {
304 if (!sel_features.erase(feat) || !feat->line) return;
305 for (auto sel : sel_features)
306 if (sel->line == feat->line) return;
307
308 auto sel = c->selectionController->GetSelectedSet();
309
310 // Don't deselect the only selected line
311 if (sel.size() <= 1) return;
312
313 sel.erase(feat->line);
314
315 // Set the active line to an arbitrary selected line if we just
316 // deselected the active line
317 AssDialogue *new_active = c->selectionController->GetActiveLine();
318 if (feat->line == new_active)
319 new_active = *sel.begin();
320
321 c->selectionController->SetSelectionAndActive(std::move(sel), new_active);
322 }
323
324 //////// PARSERS
325
326 typedef const std::vector<AssOverrideParameter> * param_vec;
327
328 // Find a tag's parameters in a line or return nullptr if it's not found
find_tag(std::vector<std::unique_ptr<AssDialogueBlock>> & blocks,std::string const & tag_name)329 static param_vec find_tag(std::vector<std::unique_ptr<AssDialogueBlock>>& blocks, std::string const& tag_name) {
330 for (auto ovr : blocks | agi::of_type<AssDialogueBlockOverride>()) {
331 for (auto const& tag : ovr->Tags) {
332 if (tag.Name == tag_name)
333 return &tag.Params;
334 }
335 }
336
337 return nullptr;
338 }
339
340 // Get a Vector2D from the given tag parameters, or Vector2D::Bad() if they are not valid
vec_or_bad(param_vec tag,size_t x_idx,size_t y_idx)341 static Vector2D vec_or_bad(param_vec tag, size_t x_idx, size_t y_idx) {
342 if (!tag ||
343 tag->size() <= x_idx || tag->size() <= y_idx ||
344 (*tag)[x_idx].omitted || (*tag)[y_idx].omitted)
345 {
346 return Vector2D();
347 }
348 return Vector2D((*tag)[x_idx].Get<float>(), (*tag)[y_idx].Get<float>());
349 }
350
GetLinePosition(AssDialogue * diag)351 Vector2D VisualToolBase::GetLinePosition(AssDialogue *diag) {
352 auto blocks = diag->ParseTags();
353
354 if (Vector2D ret = vec_or_bad(find_tag(blocks, "\\pos"), 0, 1)) return ret;
355 if (Vector2D ret = vec_or_bad(find_tag(blocks, "\\move"), 0, 1)) return ret;
356
357 // Get default position
358 auto margin = diag->Margin;
359 int align = 2;
360
361 if (AssStyle *style = c->ass->GetStyle(diag->Style)) {
362 align = style->alignment;
363 for (int i = 0; i < 3; i++) {
364 if (margin[i] == 0)
365 margin[i] = style->Margin[i];
366 }
367 }
368
369 param_vec align_tag;
370 int ovr_align = 0;
371 if ((align_tag = find_tag(blocks, "\\an")))
372 ovr_align = (*align_tag)[0].Get<int>(ovr_align);
373 else if ((align_tag = find_tag(blocks, "\\a")))
374 ovr_align = AssStyle::SsaToAss((*align_tag)[0].Get<int>(2));
375
376 if (ovr_align > 0 && ovr_align <= 9)
377 align = ovr_align;
378
379 // Alignment type
380 int hor = (align - 1) % 3;
381 int vert = (align - 1) / 3;
382
383 // Calculate positions
384 int x, y;
385 if (hor == 0)
386 x = margin[0];
387 else if (hor == 1)
388 x = (script_res.X() + margin[0] - margin[1]) / 2;
389 else
390 x = script_res.X() - margin[1];
391
392 if (vert == 0)
393 y = script_res.Y() - margin[2];
394 else if (vert == 1)
395 y = script_res.Y() / 2;
396 else
397 y = margin[2];
398
399 return Vector2D(x, y);
400 }
401
GetLineOrigin(AssDialogue * diag)402 Vector2D VisualToolBase::GetLineOrigin(AssDialogue *diag) {
403 auto blocks = diag->ParseTags();
404 return vec_or_bad(find_tag(blocks, "\\org"), 0, 1);
405 }
406
GetLineMove(AssDialogue * diag,Vector2D & p1,Vector2D & p2,int & t1,int & t2)407 bool VisualToolBase::GetLineMove(AssDialogue *diag, Vector2D &p1, Vector2D &p2, int &t1, int &t2) {
408 auto blocks = diag->ParseTags();
409
410 param_vec tag = find_tag(blocks, "\\move");
411 if (!tag)
412 return false;
413
414 p1 = vec_or_bad(tag, 0, 1);
415 p2 = vec_or_bad(tag, 2, 3);
416 // VSFilter actually defaults to -1, but it uses <= 0 to check for default and 0 seems less bug-prone
417 t1 = (*tag)[4].Get<int>(0);
418 t2 = (*tag)[5].Get<int>(0);
419
420 return p1 && p2;
421 }
422
GetLineRotation(AssDialogue * diag,float & rx,float & ry,float & rz)423 void VisualToolBase::GetLineRotation(AssDialogue *diag, float &rx, float &ry, float &rz) {
424 rx = ry = rz = 0.f;
425
426 if (AssStyle *style = c->ass->GetStyle(diag->Style))
427 rz = style->angle;
428
429 auto blocks = diag->ParseTags();
430
431 if (param_vec tag = find_tag(blocks, "\\frx"))
432 rx = tag->front().Get(rx);
433 if (param_vec tag = find_tag(blocks, "\\fry"))
434 ry = tag->front().Get(ry);
435 if (param_vec tag = find_tag(blocks, "\\frz"))
436 rz = tag->front().Get(rz);
437 else if ((tag = find_tag(blocks, "\\fr")))
438 rz = tag->front().Get(rz);
439 }
440
GetLineShear(AssDialogue * diag,float & fax,float & fay)441 void VisualToolBase::GetLineShear(AssDialogue *diag, float& fax, float& fay) {
442 fax = fay = 0.f;
443
444 auto blocks = diag->ParseTags();
445
446 if (param_vec tag = find_tag(blocks, "\\fax"))
447 fax = tag->front().Get(fax);
448 if (param_vec tag = find_tag(blocks, "\\fay"))
449 fay = tag->front().Get(fay);
450 }
451
GetLineScale(AssDialogue * diag,Vector2D & scale)452 void VisualToolBase::GetLineScale(AssDialogue *diag, Vector2D &scale) {
453 float x = 100.f, y = 100.f;
454
455 if (AssStyle *style = c->ass->GetStyle(diag->Style)) {
456 x = style->scalex;
457 y = style->scaley;
458 }
459
460 auto blocks = diag->ParseTags();
461
462 if (param_vec tag = find_tag(blocks, "\\fscx"))
463 x = tag->front().Get(x);
464 if (param_vec tag = find_tag(blocks, "\\fscy"))
465 y = tag->front().Get(y);
466
467 scale = Vector2D(x, y);
468 }
469
GetLineClip(AssDialogue * diag,Vector2D & p1,Vector2D & p2,bool & inverse)470 void VisualToolBase::GetLineClip(AssDialogue *diag, Vector2D &p1, Vector2D &p2, bool &inverse) {
471 inverse = false;
472
473 auto blocks = diag->ParseTags();
474 param_vec tag = find_tag(blocks, "\\iclip");
475 if (tag)
476 inverse = true;
477 else
478 tag = find_tag(blocks, "\\clip");
479
480 if (tag && tag->size() == 4) {
481 p1 = vec_or_bad(tag, 0, 1);
482 p2 = vec_or_bad(tag, 2, 3);
483 }
484 else {
485 p1 = Vector2D(0, 0);
486 p2 = script_res - 1;
487 }
488 }
489
GetLineVectorClip(AssDialogue * diag,int & scale,bool & inverse)490 std::string VisualToolBase::GetLineVectorClip(AssDialogue *diag, int &scale, bool &inverse) {
491 auto blocks = diag->ParseTags();
492
493 scale = 1;
494 inverse = false;
495
496 param_vec tag = find_tag(blocks, "\\iclip");
497 if (tag)
498 inverse = true;
499 else
500 tag = find_tag(blocks, "\\clip");
501
502 if (tag && tag->size() == 4) {
503 return agi::format("m %d %d l %d %d %d %d %d %d"
504 , (*tag)[0].Get<int>(), (*tag)[1].Get<int>()
505 , (*tag)[2].Get<int>(), (*tag)[1].Get<int>()
506 , (*tag)[2].Get<int>(), (*tag)[3].Get<int>()
507 , (*tag)[0].Get<int>(), (*tag)[3].Get<int>());
508 }
509 if (tag) {
510 scale = std::max((*tag)[0].Get(scale), 1);
511 return (*tag)[1].Get<std::string>("");
512 }
513
514 return "";
515 }
516
SetSelectedOverride(std::string const & tag,std::string const & value)517 void VisualToolBase::SetSelectedOverride(std::string const& tag, std::string const& value) {
518 for (auto line : c->selectionController->GetSelectedSet())
519 SetOverride(line, tag, value);
520 }
521
SetOverride(AssDialogue * line,std::string const & tag,std::string const & value)522 void VisualToolBase::SetOverride(AssDialogue* line, std::string const& tag, std::string const& value) {
523 if (!line) return;
524
525 std::string removeTag;
526 if (tag == "\\1c") removeTag = "\\c";
527 else if (tag == "\\frz") removeTag = "\\fr";
528 else if (tag == "\\pos") removeTag = "\\move";
529 else if (tag == "\\move") removeTag = "\\pos";
530 else if (tag == "\\clip") removeTag = "\\iclip";
531 else if (tag == "\\iclip") removeTag = "\\clip";
532
533 // Get block at start
534 auto blocks = line->ParseTags();
535 AssDialogueBlock *block = blocks.front().get();
536
537 if (AssDialogueBlockOverride *ovr = dynamic_cast<AssDialogueBlockOverride*>(block)) {
538 // Remove old of same
539 for (size_t i = 0; i < ovr->Tags.size(); i++) {
540 std::string const& name = ovr->Tags[i].Name;
541 if (tag == name || removeTag == name) {
542 ovr->Tags.erase(ovr->Tags.begin() + i);
543 i--;
544 }
545 }
546 ovr->AddTag(tag + value);
547
548 line->UpdateText(blocks);
549 }
550 else
551 line->Text = "{" + tag + value + "}" + line->Text.get();
552 }
553
554 // If only export worked
555 template class VisualTool<VisualDraggableFeature>;
556 template class VisualTool<ClipCorner>;
557 template class VisualTool<VisualToolDragDraggableFeature>;
558 template class VisualTool<VisualToolVectorClipDraggableFeature>;
559