Aegisub/src/audio_timing_dialogue.cpp
Thomas Goyne 2eb211f5b6 Fix issues with showing the previous inactive line
The "previous" line for the first line was actually the last line of the
file, and the previous line for the second line was ignored entirely.

Closes #1719.
2014-03-21 16:41:22 -07:00

870 lines
27 KiB
C++

// Copyright (c) 2010, Niels Martin Hansen
// 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/
/// @file audio_timing_dialogue.cpp
/// @brief Default timing mode for dialogue subtitles
/// @ingroup audio_ui
#include "config.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_time.h"
#include "audio_renderer.h"
#include "audio_timing.h"
#include "command/command.h"
#include "include/aegisub/context.h"
#include "options.h"
#include "pen.h"
#include "selection_controller.h"
#include "utils.h"
#include <boost/range/algorithm.hpp>
#include <cstdint>
#include <wx/pen.h>
class TimeableLine;
/// @class DialogueTimingMarker
/// @brief AudioMarker implementation for AudioTimingControllerDialogue
///
/// Audio marker intended to live in pairs of two, taking styles depending
/// on which marker in the pair is to the left and which is to the right.
class DialogueTimingMarker final : public AudioMarker {
/// Current ms position of this marker
int position;
/// Draw style for the marker
const Pen *style;
/// Feet style for the marker
FeetStyle feet;
/// Rendering style of the owning line, needed for sorting
AudioRenderingStyle type;
/// The line which owns this marker
TimeableLine *line;
public:
int GetPosition() const override { return position; }
wxPen GetStyle() const override { return *style; }
FeetStyle GetFeet() const override { return feet; }
bool CanSnap() const override { return true; }
/// Move the marker to a new position
/// @param new_position The position to move the marker to, in milliseconds
///
/// This notifies the owning line of the change, so that it can ensure that
/// this marker has the appropriate rendering style.
void SetPosition(int new_position);
/// Constructor
/// @param position Initial position of this marker
/// @param style Rendering style of this marker
/// @param feet Foot style of this marker
/// @param type Type of this marker, used only for sorting
/// @param line Line which this is a marker for
DialogueTimingMarker(int position, const Pen *style, FeetStyle feet, AudioRenderingStyle type, TimeableLine *line)
: position(position)
, style(style)
, feet(feet)
, type(type)
, line(line)
{
}
DialogueTimingMarker(DialogueTimingMarker const& other, TimeableLine *line)
: position(other.position)
, style(other.style)
, feet(other.feet)
, type(other.type)
, line(line)
{
}
/// Get the line which this is a marker for
TimeableLine *GetLine() const { return line; }
/// Implicit decay to the position of the marker
operator int() const { return position; }
/// Comparison operator
///
/// Compares first on position, then on audio rendering style so that the
/// markers for the active line end up after those for the inactive lines.
bool operator<(DialogueTimingMarker const& other) const
{
if (position < other.position) return true;
if (position > other.position) return false;
return type < other.type;
}
/// Swap the rendering style of this marker with that of the passed marker
void SwapStyles(DialogueTimingMarker &other)
{
std::swap(style, other.style);
std::swap(feet, other.feet);
}
};
/// A comparison predicate for pointers to dialogue markers and millisecond positions
struct marker_ptr_cmp
{
bool operator()(const DialogueTimingMarker *lft, const DialogueTimingMarker *rgt) const
{
return *lft < *rgt;
}
bool operator()(const DialogueTimingMarker *lft, int rgt) const
{
return *lft < rgt;
}
bool operator()(int lft, const DialogueTimingMarker *rgt) const
{
return lft < *rgt;
}
};
/// @class TimeableLine
/// @brief A single dialogue line which can be timed via AudioTimingControllerDialogue
///
/// This class provides markers and styling ranges for a single dialogue line,
/// both active and inactive. In addition, it can apply changes made via those
/// markers to the tracked dialogue line.
class TimeableLine {
/// The current tracked dialogue line
AssDialogue *line = nullptr;
/// The rendering style of this line
AudioRenderingStyle style;
/// One of the markers. Initially the left marker, but the user may change this.
DialogueTimingMarker marker1;
/// One of the markers. Initially the right marker, but the user may change this.
DialogueTimingMarker marker2;
/// Pointer to whichever marker happens to be on the left
DialogueTimingMarker *left_marker;
/// Pointer to whichever marker happens to be on the right
DialogueTimingMarker *right_marker;
public:
/// Constructor
/// @param style Rendering style to use for this line's time range
/// @param style_left The rendering style for the start marker
/// @param style_right The rendering style for the end marker
TimeableLine(AudioRenderingStyle style, const Pen *style_left, const Pen *style_right)
: style(style)
, marker1(0, style_left, AudioMarker::Feet_Right, style, this)
, marker2(0, style_right, AudioMarker::Feet_Left, style, this)
, left_marker(&marker1)
, right_marker(&marker2)
{
}
/// Explicit copy constructor needed due to that the markers have a pointer to this
TimeableLine(TimeableLine const& other)
: line(other.line)
, style(other.style)
, marker1(*other.left_marker, this)
, marker2(*other.right_marker, this)
, left_marker(&marker1)
, right_marker(&marker2)
{
}
/// Get the tracked dialogue line
AssDialogue *GetLine() const { return line; }
/// Get the time range for this line
operator TimeRange() const { return TimeRange(*left_marker, *right_marker); }
/// Add this line's style to the style ranges
void GetStyleRange(AudioRenderingStyleRanges *ranges) const
{
ranges->AddRange(*left_marker, *right_marker, style);
}
/// Get this line's markers
/// @param c Vector to add the markers to
void GetMarkers(std::vector<DialogueTimingMarker*> *c) const
{
c->push_back(left_marker);
c->push_back(right_marker);
}
/// Get the leftmost of the markers
DialogueTimingMarker *GetLeftMarker() { return left_marker; }
/// Get the rightmost of the markers
DialogueTimingMarker *GetRightMarker() { return right_marker; }
/// Does this line have a marker in the given range?
bool ContainsMarker(TimeRange const& range) const
{
return range.contains(marker1) || range.contains(marker2);
}
/// Check if the markers have the correct styles, and correct them if needed
void CheckMarkers()
{
if (*right_marker < *left_marker)
{
marker1.SwapStyles(marker2);
std::swap(left_marker, right_marker);
}
}
/// Apply any changes made here to the tracked dialogue line
void Apply()
{
if (line)
{
line->Start = left_marker->GetPosition();
line->End = right_marker->GetPosition();
}
}
/// Set the dialogue line which this is tracking and reset the markers to
/// the line's time range
/// @return Were the markers actually set to the line's time?
bool SetLine(AssDialogue *new_line)
{
if (!line || new_line->End > 0)
{
line = new_line;
marker1.SetPosition(new_line->Start);
marker2.SetPosition(new_line->End);
return true;
}
else
{
line = new_line;
return false;
}
}
};
void DialogueTimingMarker::SetPosition(int new_position) {
position = new_position;
line->CheckMarkers();
}
/// @class AudioTimingControllerDialogue
/// @brief Default timing mode for dialogue subtitles
///
/// Displays a start and end marker for an active subtitle line, and possibly
/// some of the inactive lines. The markers for the active line can be dragged,
/// updating the audio selection and the start/end time of that line. In
/// addition, any markers for inactive lines that start/end at the same time
/// as the active line starts/ends can optionally be dragged along with the
/// active line's markers, updating those lines as well.
class AudioTimingControllerDialogue final : public AudioTimingController {
/// The rendering style for the active line's start marker
Pen style_left;
/// The rendering style for the active line's end marker
Pen style_right;
/// The rendering style for the start and end markers of inactive lines
Pen style_inactive;
/// The currently active line
TimeableLine active_line;
/// Inactive lines which are currently modifiable
std::list<TimeableLine> inactive_lines;
/// Selected lines which are currently modifiable
std::list<TimeableLine> selected_lines;
/// All audio markers for active and inactive lines, sorted by position
std::vector<DialogueTimingMarker*> markers;
/// Marker provider for video keyframes
AudioMarkerProviderKeyframes keyframes_provider;
/// Marker provider for video playback position
VideoPositionMarkerProvider video_position_provider;
/// Marker provider for seconds lines
SecondsMarkerProvider seconds_provider;
/// The set of lines which have been modified and need to have their
/// changes applied on commit
std::set<TimeableLine*> modified_lines;
/// Commit id for coalescing purposes when in auto commit mode
int commit_id =-1;
/// The owning project context
agi::Context *context;
/// Autocommit option
const agi::OptionValue *auto_commit;
const agi::OptionValue *inactive_line_mode;
const agi::OptionValue *inactive_line_comments;
const agi::OptionValue *drag_timing;
agi::signal::Connection commit_connection;
agi::signal::Connection audio_open_connection;
agi::signal::Connection inactive_line_mode_connection;
agi::signal::Connection inactive_line_comment_connection;
agi::signal::Connection active_line_connection;
agi::signal::Connection selection_connection;
/// Update the audio controller's selection
void UpdateSelection();
/// Regenerate the list of timeable inactive lines
void RegenerateInactiveLines();
/// Regenerate the list of timeable selected lines
void RegenerateSelectedLines();
/// Add a line to the list of timeable inactive lines
void AddInactiveLine(SubtitleSelection const& sel, AssDialogue *diag);
/// Regenerate the list of active and inactive line markers
void RegenerateMarkers();
/// Get the start markers for the active line and all selected lines
std::vector<AudioMarker*> GetLeftMarkers();
/// Get the end markers for the active line and all selected lines
std::vector<AudioMarker*> GetRightMarkers();
/// @brief Set the position of markers and announce the change to the world
/// @param upd_markers Markers to move
/// @param ms New position of the markers
void SetMarkers(std::vector<AudioMarker*> const& upd_markers, int ms);
/// Snap a position to a nearby marker, if any
/// @param position Position to snap
/// @param snap_range Maximum distance to snap in milliseconds
/// @param exclude Markers which should be excluded from the potential snaps
int SnapPosition(int position, int snap_range, std::vector<AudioMarker*> const& exclude) const;
/// Commit all pending changes to the file
/// @param user_triggered Is this a user-initiated commit or an autocommit
void DoCommit(bool user_triggered);
void OnActiveLineChanged();
void OnSelectedSetChanged();
// AssFile events
void OnFileChanged(int type);
public:
// AudioMarkerProvider interface
void GetMarkers(const TimeRange &range, AudioMarkerVector &out_markers) const override;
// AudioTimingController interface
wxString GetWarningMessage() const override;
TimeRange GetIdealVisibleTimeRange() const override;
TimeRange GetPrimaryPlaybackRange() const override;
TimeRange GetActiveLineRange() const override;
void GetRenderingStyles(AudioRenderingStyleRanges &ranges) const override;
void GetLabels(TimeRange const& range, std::vector<AudioLabel> &out) const override { }
void Next(NextMode mode) override;
void Prev() override;
void Commit() override;
void Revert() override;
void AddLeadIn() override;
void AddLeadOut() override;
void ModifyLength(int delta, bool shift_following) override;
void ModifyStart(int delta) override;
bool IsNearbyMarker(int ms, int sensitivity) const override;
std::vector<AudioMarker*> OnLeftClick(int ms, bool ctrl_down, int sensitivity, int snap_range) override;
std::vector<AudioMarker*> OnRightClick(int ms, bool, int sensitivity, int snap_range) override;
void OnMarkerDrag(std::vector<AudioMarker*> const& markers, int new_position, int snap_range) override;
/// Constructor
/// @param c Project context
AudioTimingControllerDialogue(agi::Context *c);
};
AudioTimingController *CreateDialogueTimingController(agi::Context *c)
{
return new AudioTimingControllerDialogue(c);
}
AudioTimingControllerDialogue::AudioTimingControllerDialogue(agi::Context *c)
: style_left("Colour/Audio Display/Line boundary Start", "Audio/Line Boundaries Thickness")
, style_right("Colour/Audio Display/Line boundary End", "Audio/Line Boundaries Thickness")
, style_inactive("Colour/Audio Display/Line Boundary Inactive Line", "Audio/Line Boundaries Thickness")
, active_line(AudioStyle_Primary, &style_left, &style_right)
, keyframes_provider(c, "Audio/Display/Draw/Keyframes in Dialogue Mode")
, video_position_provider(c)
, context(c)
, auto_commit(OPT_GET("Audio/Auto/Commit"))
, inactive_line_mode(OPT_GET("Audio/Inactive Lines Display Mode"))
, inactive_line_comments(OPT_GET("Audio/Display/Draw/Inactive Comments"))
, drag_timing(OPT_GET("Audio/Drag Timing"))
, commit_connection(c->ass->AddCommitListener(&AudioTimingControllerDialogue::OnFileChanged, this))
, inactive_line_mode_connection(OPT_SUB("Audio/Inactive Lines Display Mode", &AudioTimingControllerDialogue::RegenerateInactiveLines, this))
, inactive_line_comment_connection(OPT_SUB("Audio/Display/Draw/Inactive Comments", &AudioTimingControllerDialogue::RegenerateInactiveLines, this))
, active_line_connection(c->selectionController->AddActiveLineListener(&AudioTimingControllerDialogue::OnActiveLineChanged, this))
, selection_connection(c->selectionController->AddSelectionListener(&AudioTimingControllerDialogue::OnSelectedSetChanged, this))
{
keyframes_provider.AddMarkerMovedListener(std::bind(std::ref(AnnounceMarkerMoved)));
video_position_provider.AddMarkerMovedListener(std::bind(std::ref(AnnounceMarkerMoved)));
seconds_provider.AddMarkerMovedListener(std::bind(std::ref(AnnounceMarkerMoved)));
Revert();
}
void AudioTimingControllerDialogue::GetMarkers(const TimeRange &range, AudioMarkerVector &out_markers) const
{
// The order matters here; later markers are painted on top of earlier
// markers, so the markers that we want to end up on top need to appear last
seconds_provider.GetMarkers(range, out_markers);
// Copy inactive line markers in the range
copy(
boost::lower_bound(markers, range.begin(), marker_ptr_cmp()),
boost::upper_bound(markers, range.end(), marker_ptr_cmp()),
back_inserter(out_markers));
keyframes_provider.GetMarkers(range, out_markers);
video_position_provider.GetMarkers(range, out_markers);
}
void AudioTimingControllerDialogue::OnActiveLineChanged()
{
Revert();
}
void AudioTimingControllerDialogue::OnSelectedSetChanged()
{
RegenerateSelectedLines();
RegenerateInactiveLines();
}
void AudioTimingControllerDialogue::OnFileChanged(int type) {
if (type & AssFile::COMMIT_DIAG_TIME)
Revert();
else if (type & AssFile::COMMIT_DIAG_ADDREM)
RegenerateInactiveLines();
}
wxString AudioTimingControllerDialogue::GetWarningMessage() const
{
// We have no warning messages currently, maybe add the old "Modified" message back later?
return wxString();
}
TimeRange AudioTimingControllerDialogue::GetIdealVisibleTimeRange() const
{
return GetPrimaryPlaybackRange();
}
TimeRange AudioTimingControllerDialogue::GetPrimaryPlaybackRange() const
{
return active_line;
}
TimeRange AudioTimingControllerDialogue::GetActiveLineRange() const
{
return active_line;
}
void AudioTimingControllerDialogue::GetRenderingStyles(AudioRenderingStyleRanges &ranges) const
{
active_line.GetStyleRange(&ranges);
for (auto const& line : selected_lines)
line.GetStyleRange(&ranges);
for (auto const& line : inactive_lines)
line.GetStyleRange(&ranges);
}
void AudioTimingControllerDialogue::Next(NextMode mode)
{
if (mode == TIMING_UNIT)
{
context->selectionController->NextLine();
return;
}
int new_end_ms = *active_line.GetRightMarker();
cmd::call("grid/line/next/create", context);
if (mode == LINE_RESET_DEFAULT || active_line.GetLine()->End == 0) {
const int default_duration = OPT_GET("Timing/Default Duration")->GetInt();
// Setting right first here so that they don't get switched and the
// same marker gets set twice
active_line.GetRightMarker()->SetPosition(new_end_ms + default_duration);
active_line.GetLeftMarker()->SetPosition(new_end_ms);
boost::sort(markers, marker_ptr_cmp());
modified_lines.insert(&active_line);
UpdateSelection();
}
}
void AudioTimingControllerDialogue::Prev()
{
context->selectionController->PrevLine();
}
void AudioTimingControllerDialogue::Commit()
{
DoCommit(true);
}
void AudioTimingControllerDialogue::DoCommit(bool user_triggered)
{
// Store back new times
if (modified_lines.size())
{
for (auto line : modified_lines)
line->Apply();
commit_connection.Block();
if (user_triggered)
{
context->ass->Commit(_("timing"), AssFile::COMMIT_DIAG_TIME);
commit_id = -1; // never coalesce with a manually triggered commit
}
else
{
AssDialogue *amend = modified_lines.size() == 1 ? (*modified_lines.begin())->GetLine() : nullptr;
commit_id = context->ass->Commit(_("timing"), AssFile::COMMIT_DIAG_TIME, commit_id, amend);
}
commit_connection.Unblock();
modified_lines.clear();
}
}
void AudioTimingControllerDialogue::Revert()
{
commit_id = -1;
if (AssDialogue *line = context->selectionController->GetActiveLine())
{
modified_lines.clear();
if (active_line.SetLine(line))
{
AnnounceUpdatedPrimaryRange();
if (inactive_line_mode->GetInt() == 0)
AnnounceUpdatedStyleRanges();
}
else
{
modified_lines.insert(&active_line);
}
}
RegenerateInactiveLines();
RegenerateSelectedLines();
}
void AudioTimingControllerDialogue::AddLeadIn()
{
DialogueTimingMarker *m = active_line.GetLeftMarker();
SetMarkers({ m }, *m - OPT_GET("Audio/Lead/IN")->GetInt());
}
void AudioTimingControllerDialogue::AddLeadOut()
{
DialogueTimingMarker *m = active_line.GetRightMarker();
SetMarkers({ m }, *m + OPT_GET("Audio/Lead/OUT")->GetInt());
}
void AudioTimingControllerDialogue::ModifyLength(int delta, bool) {
DialogueTimingMarker *m = active_line.GetRightMarker();
SetMarkers({ m },
std::max<int>(*m + delta * 10, *active_line.GetLeftMarker()));
}
void AudioTimingControllerDialogue::ModifyStart(int delta) {
DialogueTimingMarker *m = active_line.GetLeftMarker();
SetMarkers({ m },
std::min<int>(*m + delta * 10, *active_line.GetRightMarker()));
}
bool AudioTimingControllerDialogue::IsNearbyMarker(int ms, int sensitivity) const
{
assert(sensitivity >= 0);
return active_line.ContainsMarker(TimeRange(ms-sensitivity, ms+sensitivity));
}
std::vector<AudioMarker*> AudioTimingControllerDialogue::OnLeftClick(int ms, bool ctrl_down, int sensitivity, int snap_range)
{
assert(sensitivity >= 0);
assert(snap_range >= 0);
std::vector<AudioMarker*> ret;
DialogueTimingMarker *left = active_line.GetLeftMarker();
DialogueTimingMarker *right = active_line.GetRightMarker();
int dist_l = tabs(*left - ms);
int dist_r = tabs(*right - ms);
if (dist_l > sensitivity && dist_r > sensitivity)
{
// Clicked far from either marker:
// Insta-set the left marker to the clicked position and return the
// right as the dragged one, such that if the user does start dragging,
// he will create a new selection from scratch
std::vector<AudioMarker*> jump = GetLeftMarkers();
ret = drag_timing->GetBool() ? GetRightMarkers() : jump;
// Get ret before setting as setting may swap left/right
SetMarkers(jump, SnapPosition(ms, snap_range, jump));
return ret;
}
DialogueTimingMarker *clicked = dist_l <= dist_r ? left : right;
if (ctrl_down)
{
// The use of GetPosition here is important, as otherwise it'll start
// after lines ending at the same time as the active line begins
auto it = boost::lower_bound(markers, clicked->GetPosition(), marker_ptr_cmp());
for(; it != markers.end() && !(*clicked < **it); ++it)
ret.push_back(*it);
}
else
ret.push_back(clicked);
// Left-click within drag range should still move the left marker to the
// clicked position, but not the right marker
if (clicked == left)
SetMarkers(ret, SnapPosition(ms, snap_range, ret));
return ret;
}
std::vector<AudioMarker*> AudioTimingControllerDialogue::OnRightClick(int ms, bool, int sensitivity, int snap_range)
{
std::vector<AudioMarker*> ret = GetRightMarkers();
SetMarkers(ret, SnapPosition(ms, snap_range, ret));
return ret;
}
void AudioTimingControllerDialogue::OnMarkerDrag(std::vector<AudioMarker*> const& markers, int new_position, int snap_range)
{
SetMarkers(markers, SnapPosition(new_position, snap_range, markers));
}
void AudioTimingControllerDialogue::UpdateSelection()
{
AnnounceUpdatedPrimaryRange();
AnnounceUpdatedStyleRanges();
}
void AudioTimingControllerDialogue::SetMarkers(std::vector<AudioMarker*> const& upd_markers, int ms)
{
// Since we're moving markers, the sorted list of markers will need to be
// resorted. To avoid resorting the entire thing, find the subrange that
// is effected.
int min_ms = ms;
int max_ms = ms;
for (AudioMarker *upd_marker : upd_markers)
{
DialogueTimingMarker *marker = static_cast<DialogueTimingMarker*>(upd_marker);
min_ms = std::min<int>(*marker, min_ms);
max_ms = std::max<int>(*marker, max_ms);
}
auto begin = boost::lower_bound(markers, min_ms, marker_ptr_cmp());
auto end = upper_bound(begin, markers.end(), max_ms, marker_ptr_cmp());
// Update the markers
for (AudioMarker *upd_marker : upd_markers)
{
DialogueTimingMarker *marker = static_cast<DialogueTimingMarker*>(upd_marker);
marker->SetPosition(ms);
modified_lines.insert(marker->GetLine());
}
// Resort the range
sort(begin, end, marker_ptr_cmp());
if (auto_commit->GetBool()) DoCommit(false);
UpdateSelection();
AnnounceMarkerMoved();
}
static bool noncomment_dialogue(AssDialogue const& diag)
{
return !diag.Comment;
}
void AudioTimingControllerDialogue::RegenerateInactiveLines()
{
auto predicate = inactive_line_comments->GetBool() ? [](AssDialogue const&) { return true; } : noncomment_dialogue;
bool was_empty = inactive_lines.empty();
inactive_lines.clear();
SubtitleSelection const& sel = context->selectionController->GetSelectedSet();
switch (int mode = inactive_line_mode->GetInt())
{
case 1: // Previous line only
case 2: // Previous and next lines
if (AssDialogue *line = context->selectionController->GetActiveLine())
{
auto current_line = context->ass->Events.iterator_to(*line);
if (current_line == context->ass->Events.end())
break;
if (current_line != context->ass->Events.begin())
{
auto prev = current_line;
while (--prev != context->ass->Events.begin() && !predicate(*prev)) ;
if (predicate(*prev))
AddInactiveLine(sel, &*prev);
}
if (mode == 2)
{
auto next = std::find_if(++current_line, context->ass->Events.end(), predicate);
if (next != context->ass->Events.end())
AddInactiveLine(sel, &*next);
}
}
break;
case 3: // All inactive lines
{
AssDialogue *active_line = context->selectionController->GetActiveLine();
for (auto& line : context->ass->Events)
{
if (&line != active_line && predicate(line))
AddInactiveLine(sel, &line);
}
break;
}
default:
if (was_empty)
{
RegenerateMarkers();
return;
}
}
AnnounceUpdatedStyleRanges();
RegenerateMarkers();
}
void AudioTimingControllerDialogue::AddInactiveLine(SubtitleSelection const& sel, AssDialogue *diag)
{
if (sel.count(diag)) return;
inactive_lines.emplace_back(AudioStyle_Inactive, &style_inactive, &style_inactive);
inactive_lines.back().SetLine(diag);
}
void AudioTimingControllerDialogue::RegenerateSelectedLines()
{
bool was_empty = selected_lines.empty();
selected_lines.clear();
AssDialogue *active = context->selectionController->GetActiveLine();
for (auto line : context->selectionController->GetSelectedSet())
{
if (line == active) continue;
selected_lines.emplace_back(AudioStyle_Selected, &style_inactive, &style_inactive);
selected_lines.back().SetLine(line);
}
if (!selected_lines.empty() || !was_empty)
{
AnnounceUpdatedStyleRanges();
RegenerateMarkers();
}
}
void AudioTimingControllerDialogue::RegenerateMarkers()
{
markers.clear();
active_line.GetMarkers(&markers);
for (auto const& line : selected_lines)
line.GetMarkers(&markers);
for (auto const& line : inactive_lines)
line.GetMarkers(&markers);
AnnounceMarkerMoved();
}
std::vector<AudioMarker*> AudioTimingControllerDialogue::GetLeftMarkers()
{
std::vector<AudioMarker*> ret;
ret.reserve(selected_lines.size() + 1);
ret.push_back(active_line.GetLeftMarker());
for (auto& line : selected_lines)
ret.push_back(line.GetLeftMarker());
return ret;
}
std::vector<AudioMarker*> AudioTimingControllerDialogue::GetRightMarkers()
{
std::vector<AudioMarker*> ret;
ret.reserve(selected_lines.size() + 1);
ret.push_back(active_line.GetRightMarker());
for (auto& line : selected_lines)
ret.push_back(line.GetRightMarker());
return ret;
}
int AudioTimingControllerDialogue::SnapPosition(int position, int snap_range, std::vector<AudioMarker*> const& exclude) const
{
if (position < 0)
position = 0;
if (snap_range <= 0)
return position;
TimeRange snap_time_range(position - snap_range, position + snap_range);
const AudioMarker *snap_marker = nullptr;
AudioMarkerVector potential_snaps;
GetMarkers(snap_time_range, potential_snaps);
for (auto marker : potential_snaps)
{
if (marker->CanSnap() && boost::find(exclude, marker) == exclude.end())
{
if (!snap_marker)
snap_marker = marker;
else if (tabs(marker->GetPosition() - position) < tabs(snap_marker->GetPosition() - position))
snap_marker = marker;
}
}
if (snap_marker)
return snap_marker->GetPosition();
return position;
}