Redesign how project metadata is stored in the file
Remove it from the script info section and put it in its own section that isn't tracked by undo and make it not stringly typed. Removes the need for the gross hack where changes are slipped in just before saving to circumvent the undo system, cuts down on the uses of string literals to identify fields, and probably improves performance a little.
This commit is contained in:
parent
19e8f19e52
commit
9c7119fdc2
21 changed files with 240 additions and 180 deletions
|
@ -84,6 +84,7 @@ void AssFile::swap(AssFile& from) throw() {
|
|||
Events.swap(from.Events);
|
||||
Attachments.swap(from.Attachments);
|
||||
Extradata.swap(from.Extradata);
|
||||
std::swap(Properties, from.Properties);
|
||||
std::swap(next_extradata_id, from.next_extradata_id);
|
||||
}
|
||||
|
||||
|
@ -121,22 +122,6 @@ int AssFile::GetScriptInfoAsInt(std::string const& key) const {
|
|||
return atoi(GetScriptInfo(key).c_str());
|
||||
}
|
||||
|
||||
std::string AssFile::GetUIState(std::string const& key) const {
|
||||
auto value = GetScriptInfo("Aegisub " + key);
|
||||
if (value.empty())
|
||||
value = GetScriptInfo(key);
|
||||
return value;
|
||||
}
|
||||
|
||||
int AssFile::GetUIStateAsInt(std::string const& key) const {
|
||||
return atoi(GetUIState(key).c_str());
|
||||
}
|
||||
|
||||
void AssFile::SaveUIState(std::string const& key, std::string const& value) {
|
||||
if (OPT_GET("App/Save UI State")->GetBool())
|
||||
SetScriptInfo("Aegisub " + key, value);
|
||||
}
|
||||
|
||||
void AssFile::SetScriptInfo(std::string const& key, std::string const& value) {
|
||||
for (auto it = Info.begin(); it != Info.end(); ++it) {
|
||||
if (boost::iequals(key, it->Key())) {
|
||||
|
@ -300,4 +285,3 @@ void AssFile::CleanExtradata() {
|
|||
Extradata.erase(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,11 +27,6 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
/// @file ass_file.h
|
||||
/// @see ass_file.cpp
|
||||
/// @ingroup subs_storage
|
||||
///
|
||||
|
||||
#include "ass_entry.h"
|
||||
|
||||
#include <libaegisub/fs_fwd.h>
|
||||
|
@ -58,6 +53,26 @@ struct AssFileCommit {
|
|||
AssDialogue *single_line;
|
||||
};
|
||||
|
||||
struct ProjectProperties {
|
||||
std::string automation_scripts;
|
||||
std::string export_filters;
|
||||
std::string export_encoding;
|
||||
std::string style_storage;
|
||||
std::string audio_file;
|
||||
std::string video_file;
|
||||
std::string timecodes_file;
|
||||
std::string keyframes_file;
|
||||
std::map<std::string, std::string> automation_settings;
|
||||
|
||||
// UI State
|
||||
double video_zoom = 0.;
|
||||
double ar_value = 0.;
|
||||
int scroll_position = 0;
|
||||
int active_row = 0;
|
||||
int ar_mode = 0;
|
||||
int video_position = 0;
|
||||
};
|
||||
|
||||
class AssFile {
|
||||
/// A set of changes has been committed to the file (AssFile::COMMITType)
|
||||
agi::signal::Signal<int, std::set<const AssDialogue*> const&> AnnounceCommit;
|
||||
|
@ -69,6 +84,7 @@ public:
|
|||
EntryList<AssDialogue> Events;
|
||||
std::vector<AssAttachment> Attachments;
|
||||
AegisubExtradataMap Extradata;
|
||||
ProjectProperties Properties;
|
||||
|
||||
uint32_t next_extradata_id = 0;
|
||||
|
||||
|
@ -104,9 +120,6 @@ public:
|
|||
std::string GetScriptInfo(std::string const& key) const;
|
||||
/// Set the value of a [Script Info] key. Adds it if it doesn't exist.
|
||||
void SetScriptInfo(std::string const& key, std::string const& value);
|
||||
std::string GetUIState(std::string const& key) const;
|
||||
int GetUIStateAsInt(std::string const& key) const;
|
||||
void SaveUIState(std::string const& key, std::string const& value);
|
||||
|
||||
/// @brief Add a new extradata entry
|
||||
/// @param key Class identifier/owner for the extradata
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include <libaegisub/ass/uuencode.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
#include <libaegisub/util.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
@ -31,9 +32,71 @@
|
|||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
class AssParser::HeaderToProperty {
|
||||
using field = boost::variant<
|
||||
std::string ProjectProperties::*,
|
||||
int ProjectProperties::*,
|
||||
double ProjectProperties::*
|
||||
>;
|
||||
std::unordered_map<std::string, field> fields;
|
||||
|
||||
public:
|
||||
HeaderToProperty()
|
||||
: fields({
|
||||
{"Automation Scripts", &ProjectProperties::automation_scripts},
|
||||
{"Export Filters", &ProjectProperties::export_filters},
|
||||
{"Export Encoding", &ProjectProperties::export_encoding},
|
||||
{"Last Style Storage", &ProjectProperties::style_storage},
|
||||
{"Audio URI", &ProjectProperties::audio_file},
|
||||
{"Audio File", &ProjectProperties::audio_file},
|
||||
{"Video File", &ProjectProperties::video_file},
|
||||
{"Timecodes File", &ProjectProperties::timecodes_file},
|
||||
{"Keyframes File", &ProjectProperties::keyframes_file},
|
||||
{"Video Zoom Percent", &ProjectProperties::video_zoom},
|
||||
{"Scroll Position", &ProjectProperties::scroll_position},
|
||||
{"Active Line", &ProjectProperties::active_row},
|
||||
{"Video Position", &ProjectProperties::video_position},
|
||||
{"Video AR Mode", &ProjectProperties::ar_mode},
|
||||
{"Video AR Value", &ProjectProperties::ar_value},
|
||||
{"Aegisub Video Zoom Percent", &ProjectProperties::video_zoom},
|
||||
{"Aegisub Scroll Position", &ProjectProperties::scroll_position},
|
||||
{"Aegisub Active Line", &ProjectProperties::active_row},
|
||||
{"Aegisub Video Position", &ProjectProperties::video_position}
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
bool ProcessProperty(AssFile *target, std::string const& key, std::string const& value) {
|
||||
auto it = fields.find(key);
|
||||
if (it != end(fields)) {
|
||||
using namespace agi::util;
|
||||
struct {
|
||||
using result_type = void;
|
||||
ProjectProperties &obj;
|
||||
std::string const& value;
|
||||
void operator()(std::string ProjectProperties::*f) const { obj.*f = value; }
|
||||
void operator()(int ProjectProperties::*f) const { try_parse(value, &(obj.*f)); }
|
||||
void operator()(double ProjectProperties::*f) const { try_parse(value, &(obj.*f)); }
|
||||
} visitor {target->Properties, value};
|
||||
boost::apply_visitor(visitor, it->second);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (boost::starts_with(key, "Automation Settings ")) {
|
||||
target->Properties.automation_settings[key.substr(strlen("Automation Settings"))] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
AssParser::AssParser(AssFile *target, int version)
|
||||
: target(target)
|
||||
: property_handler(new HeaderToProperty)
|
||||
, target(target)
|
||||
, version(version)
|
||||
, state(&AssParser::ParseScriptInfoLine)
|
||||
{
|
||||
|
@ -94,7 +157,23 @@ void AssParser::ParseScriptInfoLine(std::string const& data) {
|
|||
size_t pos = data.find(':');
|
||||
if (pos == data.npos) return;
|
||||
|
||||
target->Info.push_back(*new AssInfo(data.substr(0, pos), boost::trim_left_copy(data.substr(pos + 1))));
|
||||
auto key = data.substr(0, pos);
|
||||
auto value = data.substr(pos + 1);
|
||||
boost::trim_left(value);
|
||||
|
||||
if (!property_handler->ProcessProperty(target, key, value))
|
||||
target->Info.push_back(*new AssInfo(std::move(key), std::move(value)));
|
||||
}
|
||||
|
||||
void AssParser::ParseMetadataLine(std::string const& data) {
|
||||
size_t pos = data.find(':');
|
||||
if (pos == data.npos) return;
|
||||
|
||||
auto key = data.substr(0, pos);
|
||||
auto value = data.substr(pos + 1);
|
||||
boost::trim_left(value);
|
||||
|
||||
property_handler->ProcessProperty(target, key, value);
|
||||
}
|
||||
|
||||
void AssParser::ParseEventLine(std::string const& data) {
|
||||
|
@ -171,12 +250,14 @@ void AssParser::AddLine(std::string const& data) {
|
|||
state = &AssParser::ParseEventLine;
|
||||
else if (low == "[script info]")
|
||||
state = &AssParser::ParseScriptInfoLine;
|
||||
else if (low == "[aegisub project garbage]")
|
||||
state = &AssParser::ParseMetadataLine;
|
||||
else if (low == "[aegisub extradata]")
|
||||
state = &AssParser::ParseExtradataLine;
|
||||
else if (low == "[graphics]")
|
||||
state = &AssParser::ParseGraphicsLine;
|
||||
else if (low == "[fonts]")
|
||||
state = &AssParser::ParseFontLine;
|
||||
else if (low == "[aegisub extradata]")
|
||||
state = &AssParser::ParseExtradataLine;
|
||||
else
|
||||
state = &AssParser::UnknownLine;
|
||||
return;
|
||||
|
|
|
@ -20,6 +20,9 @@ class AssAttachment;
|
|||
class AssFile;
|
||||
|
||||
class AssParser {
|
||||
class HeaderToProperty;
|
||||
std::unique_ptr<HeaderToProperty> property_handler;
|
||||
|
||||
AssFile *target;
|
||||
int version;
|
||||
std::unique_ptr<AssAttachment> attach;
|
||||
|
@ -29,6 +32,7 @@ class AssParser {
|
|||
void ParseEventLine(std::string const& data);
|
||||
void ParseStyleLine(std::string const& data);
|
||||
void ParseScriptInfoLine(std::string const& data);
|
||||
void ParseMetadataLine(std::string const& data);
|
||||
void ParseFontLine(std::string const& data);
|
||||
void ParseGraphicsLine(std::string const& data);
|
||||
void ParseExtradataLine(std::string const &data);
|
||||
|
|
|
@ -182,14 +182,14 @@ namespace Automation4 {
|
|||
|
||||
std::string ExportFilter::GetScriptSettingsIdentifier()
|
||||
{
|
||||
return inline_string_encode("Automation Settings " + GetName());
|
||||
return inline_string_encode(GetName());
|
||||
}
|
||||
|
||||
wxWindow* ExportFilter::GetConfigDialogWindow(wxWindow *parent, agi::Context *c) {
|
||||
config_dialog = GenerateConfigDialog(parent, c);
|
||||
|
||||
if (config_dialog) {
|
||||
std::string val = c->ass->GetScriptInfo(GetScriptSettingsIdentifier());
|
||||
std::string const& val = c->ass->Properties.automation_settings[GetScriptSettingsIdentifier()];
|
||||
if (!val.empty())
|
||||
config_dialog->Unserialise(val);
|
||||
return config_dialog->CreateWindow(parent);
|
||||
|
@ -199,11 +199,8 @@ namespace Automation4 {
|
|||
}
|
||||
|
||||
void ExportFilter::LoadSettings(bool is_default, agi::Context *c) {
|
||||
if (config_dialog) {
|
||||
std::string val = config_dialog->Serialise();
|
||||
if (!val.empty())
|
||||
c->ass->SetScriptInfo(GetScriptSettingsIdentifier(), val);
|
||||
}
|
||||
if (config_dialog)
|
||||
c->ass->Properties.automation_settings[GetScriptSettingsIdentifier()] = config_dialog->Serialise();
|
||||
}
|
||||
|
||||
// ProgressSink
|
||||
|
@ -285,10 +282,6 @@ namespace Automation4 {
|
|||
}
|
||||
|
||||
// ScriptManager
|
||||
ScriptManager::~ScriptManager()
|
||||
{
|
||||
}
|
||||
|
||||
void ScriptManager::Add(std::unique_ptr<Script> script)
|
||||
{
|
||||
if (find(scripts.begin(), scripts.end(), script) == scripts.end())
|
||||
|
@ -373,18 +366,16 @@ namespace Automation4 {
|
|||
|
||||
LocalScriptManager::LocalScriptManager(agi::Context *c)
|
||||
: context(c)
|
||||
, connections(agi::signal::make_vector({
|
||||
c->subsController->AddFileSaveListener(&LocalScriptManager::OnSubtitlesSave, this),
|
||||
c->subsController->AddFileOpenListener(&LocalScriptManager::Reload, this),
|
||||
}))
|
||||
, file_open_connection(c->subsController->AddFileOpenListener(&LocalScriptManager::Reload, this))
|
||||
{
|
||||
AddScriptChangeListener(&LocalScriptManager::SaveLoadedList, this);
|
||||
}
|
||||
|
||||
void LocalScriptManager::Reload()
|
||||
{
|
||||
scripts.clear();
|
||||
|
||||
auto local_scripts = context->ass->GetScriptInfo("Automation Scripts");
|
||||
auto const& local_scripts = context->ass->Properties.automation_scripts;
|
||||
if (local_scripts.empty()) {
|
||||
ScriptsChanged();
|
||||
return;
|
||||
|
@ -421,7 +412,7 @@ namespace Automation4 {
|
|||
ScriptsChanged();
|
||||
}
|
||||
|
||||
void LocalScriptManager::OnSubtitlesSave()
|
||||
void LocalScriptManager::SaveLoadedList()
|
||||
{
|
||||
// Store Automation script data
|
||||
// Algorithm:
|
||||
|
@ -450,7 +441,7 @@ namespace Automation4 {
|
|||
|
||||
scripts_string += scriptfn;
|
||||
}
|
||||
context->ass->SetScriptInfo("Automation Scripts", scripts_string);
|
||||
context->ass->Properties.automation_scripts = std::move(scripts_string);
|
||||
}
|
||||
|
||||
// ScriptFactory
|
||||
|
|
|
@ -190,7 +190,7 @@ namespace Automation4 {
|
|||
|
||||
public:
|
||||
/// Deletes all scripts managed
|
||||
virtual ~ScriptManager();
|
||||
virtual ~ScriptManager() = default;
|
||||
/// Add a script to the manager.
|
||||
void Add(std::unique_ptr<Script> script);
|
||||
/// Remove a script from the manager, and delete the Script object.
|
||||
|
@ -215,9 +215,9 @@ namespace Automation4 {
|
|||
/// Manager for scripts specified by a subtitle file
|
||||
class LocalScriptManager final : public ScriptManager {
|
||||
agi::Context *context;
|
||||
std::vector<agi::signal::Connection> connections;
|
||||
agi::signal::Connection file_open_connection;
|
||||
|
||||
void OnSubtitlesSave();
|
||||
void SaveLoadedList();
|
||||
public:
|
||||
LocalScriptManager(agi::Context *context);
|
||||
void Reload() override;
|
||||
|
|
|
@ -127,7 +127,6 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
|
|||
connections = agi::signal::make_vector({
|
||||
context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this),
|
||||
context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this),
|
||||
context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this),
|
||||
|
||||
context->selectionController->AddActiveLineListener(&BaseGrid::OnActiveLineChanged, this),
|
||||
context->selectionController->AddSelectionListener([&]{ Refresh(false); }),
|
||||
|
@ -184,11 +183,7 @@ void BaseGrid::OnSubtitlesCommit(int type) {
|
|||
}
|
||||
|
||||
void BaseGrid::OnSubtitlesOpen() {
|
||||
ScrollTo(context->ass->GetUIStateAsInt("Scroll Position"));
|
||||
}
|
||||
|
||||
void BaseGrid::OnSubtitlesSave() {
|
||||
context->ass->SaveUIState("Scroll Position", std::to_string(yPos));
|
||||
ScrollTo(context->ass->Properties.scroll_position);
|
||||
}
|
||||
|
||||
void BaseGrid::OnShowColMenu(wxCommandEvent &event) {
|
||||
|
@ -444,7 +439,7 @@ void BaseGrid::OnSize(wxSizeEvent &) {
|
|||
void BaseGrid::OnScroll(wxScrollEvent &event) {
|
||||
int newPos = event.GetPosition();
|
||||
if (yPos != newPos) {
|
||||
yPos = newPos;
|
||||
context->ass->Properties.scroll_position = yPos = newPos;
|
||||
Refresh(false);
|
||||
}
|
||||
}
|
||||
|
@ -580,7 +575,7 @@ void BaseGrid::OnContextMenu(wxContextMenuEvent &evt) {
|
|||
void BaseGrid::ScrollTo(int y) {
|
||||
int nextY = mid(0, y, GetRows() - 1);
|
||||
if (yPos != nextY) {
|
||||
yPos = nextY;
|
||||
context->ass->Properties.scroll_position = yPos = nextY;
|
||||
scrollBar->SetThumbPosition(yPos);
|
||||
Refresh(false);
|
||||
}
|
||||
|
@ -605,7 +600,7 @@ void BaseGrid::AdjustScrollbar() {
|
|||
int drawPerScreen = clientSize.GetHeight() / lineHeight;
|
||||
int rows = GetRows();
|
||||
|
||||
yPos = mid(0, yPos, rows - 1);
|
||||
context->ass->Properties.scroll_position = yPos = mid(0, yPos, rows - 1);
|
||||
|
||||
scrollBar->SetScrollbar(yPos, drawPerScreen, rows + drawPerScreen - 1, drawPerScreen - 2, true);
|
||||
scrollBar->Thaw();
|
||||
|
|
|
@ -94,7 +94,6 @@ class BaseGrid final : public wxWindow {
|
|||
void OnSize(wxSizeEvent &event);
|
||||
void OnSubtitlesCommit(int type);
|
||||
void OnSubtitlesOpen();
|
||||
void OnSubtitlesSave();
|
||||
void OnActiveLineChanged(AssDialogue *);
|
||||
|
||||
void ScrollTo(int y);
|
||||
|
|
|
@ -87,7 +87,7 @@ DialogExport::DialogExport(agi::Context *c)
|
|||
filter_list->Bind(wxEVT_LISTBOX, &DialogExport::OnChange, this);
|
||||
|
||||
// Get selected filters
|
||||
std::string selected = c->ass->GetScriptInfo("Export filters");
|
||||
std::string const& selected = c->ass->Properties.export_filters;
|
||||
boost::char_separator<char> sep("|");
|
||||
for (auto const& token : boost::tokenizer<boost::char_separator<char>>(selected, sep)) {
|
||||
auto it = find(begin(filters), end(filters), token);
|
||||
|
@ -119,7 +119,7 @@ DialogExport::DialogExport(agi::Context *c)
|
|||
wxSizer *charset_list_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
charset_list_sizer->Add(charset_list_label, wxSizerFlags().Center().Border(wxRIGHT));
|
||||
charset_list_sizer->Add(charset_list, wxSizerFlags(1).Expand());
|
||||
if (!charset_list->SetStringSelection(to_wx(c->ass->GetScriptInfo("Export Encoding"))))
|
||||
if (!charset_list->SetStringSelection(to_wx(c->ass->Properties.export_encoding)))
|
||||
charset_list->SetStringSelection("Unicode (UTF-8)");
|
||||
|
||||
wxSizer *top_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Filters"));
|
||||
|
@ -148,15 +148,14 @@ DialogExport::DialogExport(agi::Context *c)
|
|||
}
|
||||
|
||||
DialogExport::~DialogExport() {
|
||||
std::string infoList;
|
||||
c->ass->Properties.export_filters.clear();
|
||||
for (size_t i = 0; i < filter_list->GetCount(); ++i) {
|
||||
if (filter_list->IsChecked(i)) {
|
||||
if (!infoList.empty())
|
||||
infoList += "|";
|
||||
infoList += from_wx(filter_list->GetString(i));
|
||||
if (!c->ass->Properties.export_filters.empty())
|
||||
c->ass->Properties.export_filters += "|";
|
||||
c->ass->Properties.export_filters += from_wx(filter_list->GetString(i));
|
||||
}
|
||||
}
|
||||
c->ass->SetScriptInfo("Export filters", infoList);
|
||||
}
|
||||
|
||||
void DialogExport::OnProcess(wxCommandEvent &) {
|
||||
|
@ -172,7 +171,7 @@ void DialogExport::OnProcess(wxCommandEvent &) {
|
|||
|
||||
try {
|
||||
wxBusyCursor busy;
|
||||
c->ass->SetScriptInfo("Export Encoding", from_wx(charset_list->GetStringSelection()));
|
||||
c->ass->Properties.export_encoding = from_wx(charset_list->GetStringSelection());
|
||||
exporter->Export(filename, from_wx(charset_list->GetStringSelection()), this);
|
||||
}
|
||||
catch (agi::UserCancelException const&) {
|
||||
|
|
|
@ -319,7 +319,7 @@ void DialogStyleManager::UpdateStorage() {
|
|||
|
||||
void DialogStyleManager::OnChangeCatalog() {
|
||||
std::string catalog(from_wx(CatalogList->GetStringSelection()));
|
||||
c->ass->SetScriptInfo("Last Style Storage", catalog);
|
||||
c->ass->Properties.style_storage = catalog;
|
||||
Store.LoadCatalog(catalog);
|
||||
UpdateStorage();
|
||||
}
|
||||
|
@ -341,15 +341,12 @@ void DialogStyleManager::LoadCatalog() {
|
|||
}
|
||||
|
||||
// Set to default if available
|
||||
std::string pickStyle = c->ass->GetScriptInfo("Last Style Storage");
|
||||
std::string pickStyle = c->ass->Properties.style_storage;
|
||||
if (pickStyle.empty())
|
||||
pickStyle = "Default";
|
||||
|
||||
int opt = CatalogList->FindString(to_wx(pickStyle), false);
|
||||
if (opt != wxNOT_FOUND)
|
||||
CatalogList->SetSelection(opt);
|
||||
else
|
||||
CatalogList->SetSelection(0);
|
||||
CatalogList->SetSelection(opt == wxNOT_FOUND ? 0 : opt);
|
||||
|
||||
OnChangeCatalog();
|
||||
}
|
||||
|
|
|
@ -55,20 +55,15 @@ Project::Project(agi::Context *c) : context(c) {
|
|||
OPT_SUB("Subtitle/Provider", &Project::ReloadVideo, this);
|
||||
OPT_SUB("Video/Force BT.601", &Project::ReloadVideo, this);
|
||||
OPT_SUB("Video/Provider", &Project::ReloadVideo, this);
|
||||
c->subsController->AddFileSaveListener(&Project::OnSubtitlesSave, this);
|
||||
}
|
||||
|
||||
Project::~Project() { }
|
||||
|
||||
void Project::OnSubtitlesSave() {
|
||||
context->ass->SetScriptInfo("Audio File",
|
||||
config::path->MakeRelative(audio_file, "?script").generic_string());
|
||||
context->ass->SetScriptInfo("Video File",
|
||||
config::path->MakeRelative(video_file, "?script").generic_string());
|
||||
context->ass->SetScriptInfo("VFR File",
|
||||
config::path->MakeRelative(timecodes_file, "?script").generic_string());
|
||||
context->ass->SetScriptInfo("Keyframes File",
|
||||
config::path->MakeRelative(keyframes_file, "?script").generic_string());
|
||||
void Project::UpdateRelativePaths() {
|
||||
context->ass->Properties.audio_file = config::path->MakeRelative(audio_file, "?script").generic_string();
|
||||
context->ass->Properties.video_file = config::path->MakeRelative(video_file, "?script").generic_string();
|
||||
context->ass->Properties.timecodes_file = config::path->MakeRelative(timecodes_file, "?script").generic_string();
|
||||
context->ass->Properties.keyframes_file = config::path->MakeRelative(keyframes_file, "?script").generic_string();
|
||||
}
|
||||
|
||||
void Project::ReloadAudio() {
|
||||
|
@ -89,6 +84,15 @@ void Project::ShowError(std::string const& message) {
|
|||
ShowError(to_wx(message));
|
||||
}
|
||||
|
||||
void Project::SetPath(agi::fs::path& var, const char *token, const char *mru, agi::fs::path const& value) {
|
||||
var = value;
|
||||
if (*token)
|
||||
config::path->SetToken(token, value);
|
||||
if (*mru)
|
||||
config::mru->Add(mru, value);
|
||||
UpdateRelativePaths();
|
||||
}
|
||||
|
||||
void Project::DoLoadSubtitles(agi::fs::path const& path, std::string encoding) {
|
||||
try {
|
||||
if (encoding.empty())
|
||||
|
@ -141,10 +145,10 @@ void Project::LoadUnloadFiles() {
|
|||
auto load_linked = OPT_GET("App/Auto/Load Linked Files")->GetInt();
|
||||
if (!load_linked) return;
|
||||
|
||||
auto audio = config::path->MakeAbsolute(context->ass->GetScriptInfo("Audio File"), "?script");
|
||||
auto video = config::path->MakeAbsolute(context->ass->GetScriptInfo("Video File"), "?script");
|
||||
auto timecodes = config::path->MakeAbsolute(context->ass->GetScriptInfo("VFR File"), "?script");
|
||||
auto keyframes = config::path->MakeAbsolute(context->ass->GetScriptInfo("Keyframes File"), "?script");
|
||||
auto audio = config::path->MakeAbsolute(context->ass->Properties.audio_file, "?script");
|
||||
auto video = config::path->MakeAbsolute(context->ass->Properties.video_file, "?script");
|
||||
auto timecodes = config::path->MakeAbsolute(context->ass->Properties.timecodes_file, "?script");
|
||||
auto keyframes = config::path->MakeAbsolute(context->ass->Properties.keyframes_file, "?script");
|
||||
|
||||
if (video == video_file && audio == audio_file && keyframes == keyframes_file && timecodes == timecodes_file)
|
||||
return;
|
||||
|
@ -160,23 +164,14 @@ void Project::LoadUnloadFiles() {
|
|||
CloseVideo();
|
||||
else if ((loaded_video = DoLoadVideo(video))) {
|
||||
auto vc = context->videoController.get();
|
||||
vc->JumpToFrame(context->ass->GetUIStateAsInt("Video Position"));
|
||||
vc->JumpToFrame(context->ass->Properties.video_position);
|
||||
|
||||
std::string arString = context->ass->GetUIState("Video Aspect Ratio");
|
||||
if (boost::starts_with(arString, "c")) {
|
||||
double ar = 0.;
|
||||
agi::util::try_parse(arString.substr(1), &ar);
|
||||
vc->SetAspectRatio(ar);
|
||||
}
|
||||
else {
|
||||
int ar = 0;
|
||||
if (agi::util::try_parse(arString, &ar) && ar >= 0 && ar < 4)
|
||||
vc->SetAspectRatio((AspectRatio)ar);
|
||||
}
|
||||
|
||||
double videoZoom = 0.;
|
||||
if (agi::util::try_parse(context->ass->GetUIState("Video Zoom Percent"), &videoZoom))
|
||||
context->videoDisplay->SetZoom(videoZoom);
|
||||
auto ar_mode = static_cast<AspectRatio>(context->ass->Properties.ar_mode);
|
||||
if (ar_mode == AspectRatio::Custom)
|
||||
vc->SetAspectRatio(context->ass->Properties.ar_value);
|
||||
else
|
||||
vc->SetAspectRatio(ar_mode);
|
||||
context->videoDisplay->SetZoom(context->ass->Properties.video_zoom);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,9 +220,7 @@ void Project::DoLoadAudio(agi::fs::path const& path, bool quiet) {
|
|||
return ShowError(e.GetChainedMessage());
|
||||
}
|
||||
|
||||
audio_file = path;
|
||||
config::path->SetToken("?audio", path);
|
||||
config::mru->Add("Audio", path);
|
||||
SetPath(audio_file, "?audio", "Audio", path);
|
||||
AnnounceAudioProviderModified(audio_provider.get());
|
||||
}
|
||||
|
||||
|
@ -238,8 +231,7 @@ void Project::LoadAudio(agi::fs::path const& path) {
|
|||
void Project::CloseAudio() {
|
||||
AnnounceAudioProviderModified(nullptr);
|
||||
audio_provider.reset();
|
||||
audio_file.clear();
|
||||
config::path->SetToken("?audio", "");
|
||||
SetPath(audio_file, "?audio", "", "");
|
||||
}
|
||||
|
||||
bool Project::DoLoadVideo(agi::fs::path const& path) {
|
||||
|
@ -266,12 +258,10 @@ bool Project::DoLoadVideo(agi::fs::path const& path) {
|
|||
|
||||
timecodes = video_provider->GetFPS();
|
||||
keyframes = video_provider->GetKeyFrames();
|
||||
|
||||
timecodes_file.clear();
|
||||
keyframes_file.clear();
|
||||
|
||||
video_file = path;
|
||||
config::mru->Add("Video", path);
|
||||
config::path->SetToken("?video", path);
|
||||
SetPath(video_file, "?video", "Video", path);
|
||||
|
||||
std::string warning = video_provider->GetWarning();
|
||||
if (!warning.empty())
|
||||
|
@ -296,15 +286,13 @@ void Project::LoadVideo(agi::fs::path const& path) {
|
|||
void Project::CloseVideo() {
|
||||
AnnounceVideoProviderModified(nullptr);
|
||||
video_provider.reset();
|
||||
video_file.clear();
|
||||
config::path->SetToken("?video", "");
|
||||
SetPath(video_file, "?video", "", "");
|
||||
video_has_subtitles = false;
|
||||
}
|
||||
|
||||
void Project::DoLoadTimecodes(agi::fs::path const& path) {
|
||||
timecodes = agi::vfr::Framerate(path);
|
||||
timecodes_file = path;
|
||||
config::mru->Add("Timecodes", path);
|
||||
SetPath(timecodes_file, "", "Timecodes", path);
|
||||
AnnounceTimecodesModified(timecodes);
|
||||
}
|
||||
|
||||
|
@ -324,14 +312,13 @@ void Project::LoadTimecodes(agi::fs::path const& path) {
|
|||
|
||||
void Project::CloseTimecodes() {
|
||||
timecodes = video_provider ? video_provider->GetFPS() : agi::vfr::Framerate{};
|
||||
timecodes_file.clear();
|
||||
SetPath(timecodes_file, "", "", "");
|
||||
AnnounceTimecodesModified(timecodes);
|
||||
}
|
||||
|
||||
void Project::DoLoadKeyframes(agi::fs::path const& path) {
|
||||
keyframes = agi::keyframe::Load(path);
|
||||
keyframes_file = path;
|
||||
config::mru->Add("Keyframes", path);
|
||||
SetPath(keyframes_file, "", "Keyframes", path);
|
||||
AnnounceKeyframesModified(keyframes);
|
||||
}
|
||||
|
||||
|
@ -351,7 +338,7 @@ void Project::LoadKeyframes(agi::fs::path const& path) {
|
|||
|
||||
void Project::CloseKeyframes() {
|
||||
keyframes = video_provider ? video_provider->GetKeyFrames() : std::vector<int>{};
|
||||
keyframes_file.clear();
|
||||
SetPath(keyframes_file, "", "", "");
|
||||
AnnounceKeyframesModified(keyframes);
|
||||
}
|
||||
|
||||
|
@ -431,8 +418,12 @@ void Project::LoadList(std::vector<agi::fs::path> const& files) {
|
|||
|
||||
if (!subs.empty())
|
||||
DoLoadSubtitles(subs);
|
||||
if (!video.empty())
|
||||
if (!video.empty()) {
|
||||
auto rel_audio_file = context->ass->Properties.audio_file;
|
||||
DoLoadVideo(video);
|
||||
if (!rel_audio_file.empty() && context->ass->Properties.audio_file.empty())
|
||||
context->ass->Properties.audio_file.swap(rel_audio_file);
|
||||
}
|
||||
if (!audio.empty())
|
||||
DoLoadAudio(audio, false);
|
||||
else if (OPT_GET("Video/Open Audio")->GetBool() && audio_file != video_file)
|
||||
|
|
|
@ -29,7 +29,6 @@ class wxString;
|
|||
namespace agi { struct Context; }
|
||||
|
||||
class Project {
|
||||
// Things owned by this
|
||||
std::unique_ptr<AudioProvider> audio_provider;
|
||||
std::unique_ptr<AsyncVideoProvider> video_provider;
|
||||
agi::vfr::Framerate timecodes;
|
||||
|
@ -46,10 +45,7 @@ class Project {
|
|||
agi::signal::Signal<std::vector<int> const&> AnnounceKeyframesModified;
|
||||
|
||||
bool video_has_subtitles = false;
|
||||
|
||||
DialogProgress *progress = nullptr;
|
||||
|
||||
// Things not
|
||||
agi::Context *context = nullptr;
|
||||
|
||||
void ShowError(wxString const& message);
|
||||
|
@ -62,11 +58,12 @@ class Project {
|
|||
void DoLoadKeyframes(agi::fs::path const& path);
|
||||
|
||||
void LoadUnloadFiles();
|
||||
|
||||
void OnSubtitlesSave();
|
||||
void UpdateRelativePaths();
|
||||
void ReloadAudio();
|
||||
void ReloadVideo();
|
||||
|
||||
void SetPath(agi::fs::path& var, const char *token, const char *mru, agi::fs::path const& value);
|
||||
|
||||
public:
|
||||
Project(agi::Context *context);
|
||||
~Project();
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
SelectionController::SelectionController(agi::Context *c)
|
||||
: context(c)
|
||||
, open_connection(c->subsController->AddFileOpenListener(&SelectionController::OnSubtitlesOpen, this))
|
||||
, save_connection(c->subsController->AddFileSaveListener(&SelectionController::OnSubtitlesSave, this))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -35,7 +34,7 @@ void SelectionController::OnSubtitlesOpen() {
|
|||
selection.clear();
|
||||
active_line = nullptr;
|
||||
if (!context->ass->Events.empty()) {
|
||||
int row = mid<int>(0, context->ass->GetUIStateAsInt("Active Line"), context->ass->Events.size() - 1);
|
||||
int row = mid<int>(0, context->ass->Properties.active_row, context->ass->Events.size() - 1);
|
||||
active_line = &*std::next(context->ass->Events.begin(), row);
|
||||
selection.insert(active_line);
|
||||
}
|
||||
|
@ -43,11 +42,6 @@ void SelectionController::OnSubtitlesOpen() {
|
|||
AnnounceActiveLineChanged(active_line);
|
||||
}
|
||||
|
||||
void SelectionController::OnSubtitlesSave() {
|
||||
if (active_line)
|
||||
context->ass->SaveUIState("Active Line", std::to_string(active_line->Row));
|
||||
}
|
||||
|
||||
void SelectionController::SetSelectedSet(Selection new_selection) {
|
||||
selection = std::move(new_selection);
|
||||
AnnounceSelectedSetChanged();
|
||||
|
@ -56,6 +50,8 @@ void SelectionController::SetSelectedSet(Selection new_selection) {
|
|||
void SelectionController::SetActiveLine(AssDialogue *new_line) {
|
||||
if (new_line != active_line) {
|
||||
active_line = new_line;
|
||||
if (active_line)
|
||||
context->ass->Properties.active_row = active_line->Row;
|
||||
AnnounceActiveLineChanged(new_line);
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +60,8 @@ void SelectionController::SetSelectionAndActive(Selection new_selection, AssDial
|
|||
bool active_line_changed = new_line != active_line;
|
||||
selection = std::move(new_selection);
|
||||
active_line = new_line;
|
||||
if (active_line)
|
||||
context->ass->Properties.active_row = active_line->Row;
|
||||
|
||||
AnnounceSelectedSetChanged();
|
||||
if (active_line_changed)
|
||||
|
|
|
@ -47,7 +47,6 @@ class SelectionController {
|
|||
AssDialogue *active_line = nullptr; ///< The currently active line or 0 if none
|
||||
|
||||
agi::signal::Connection open_connection;
|
||||
agi::signal::Connection save_connection;
|
||||
|
||||
void OnSubtitlesOpen();
|
||||
void OnSubtitlesSave();
|
||||
|
|
|
@ -220,11 +220,9 @@ void SubsController::Save(agi::fs::path const& filename, std::string const& enco
|
|||
this->filename = filename;
|
||||
config::path->SetToken("?script", filename.parent_path());
|
||||
|
||||
FileSave();
|
||||
|
||||
context->ass->CleanExtradata();
|
||||
|
||||
writer->WriteFile(context->ass.get(), filename, 0, encoding);
|
||||
FileSave();
|
||||
}
|
||||
catch (...) {
|
||||
autosaved_commit_id = old_autosaved_commit_id;
|
||||
|
|
|
@ -52,11 +52,12 @@ class SubsController {
|
|||
|
||||
/// A new file has been opened (filename)
|
||||
agi::signal::Signal<agi::fs::path> FileOpen;
|
||||
/// The file is about to be saved
|
||||
/// This signal is intended for adding metadata such as video filename,
|
||||
/// frame number, etc. Ideally this would all be done immediately rather
|
||||
/// than waiting for a save, but that causes (more) issues with undo
|
||||
/// The file has been saved
|
||||
agi::signal::Signal<> FileSave;
|
||||
/// The file is about to be saved
|
||||
/// This signal is intended for adding metadata which is awkward or
|
||||
/// expensive to always keep up to date
|
||||
agi::signal::Signal<> UpdateProperties;
|
||||
|
||||
/// The filename of the currently open file, if any
|
||||
agi::fs::path filename;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "ass_file.h"
|
||||
#include "ass_style.h"
|
||||
#include "ass_parser.h"
|
||||
#include "options.h"
|
||||
#include "string_codec.h"
|
||||
#include "text_file_reader.h"
|
||||
#include "text_file_writer.h"
|
||||
|
@ -93,6 +94,41 @@ struct Writer {
|
|||
}
|
||||
}
|
||||
|
||||
void Write(ProjectProperties const& properties) {
|
||||
file.WriteLineToFile("");
|
||||
file.WriteLineToFile("[Aegisub Project Garbage]");
|
||||
|
||||
WriteIfNotEmpty("Automation Scripts: ", properties.automation_scripts);
|
||||
WriteIfNotEmpty("Export Filters: ", properties.export_filters);
|
||||
WriteIfNotEmpty("Export Encoding: ", properties.export_encoding);
|
||||
WriteIfNotEmpty("Last Style Storage: ", properties.style_storage);
|
||||
WriteIfNotEmpty("Audio File: ", properties.audio_file);
|
||||
WriteIfNotEmpty("Video File: ", properties.video_file);
|
||||
WriteIfNotEmpty("Timecodes File: ", properties.timecodes_file);
|
||||
WriteIfNotEmpty("Keyframes File: ", properties.keyframes_file);
|
||||
|
||||
WriteIfNotZero("Video AR Mode: ", properties.ar_mode);
|
||||
WriteIfNotZero("Video AR Value: ", properties.ar_value);
|
||||
|
||||
if (OPT_GET("App/Save UI State")->GetBool()) {
|
||||
WriteIfNotZero("Video Zoom Percent: ", properties.video_zoom);
|
||||
WriteIfNotZero("Scroll Position: ", properties.scroll_position);
|
||||
WriteIfNotZero("Active Line: ", properties.active_row);
|
||||
WriteIfNotZero("Video Position: ", properties.video_position);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteIfNotEmpty(const char *key, std::string const& value) {
|
||||
if (!value.empty())
|
||||
file.WriteLineToFile(key + value);
|
||||
}
|
||||
|
||||
template<typename Number>
|
||||
void WriteIfNotZero(const char *key, Number n) {
|
||||
if (n != Number{})
|
||||
file.WriteLineToFile(key + std::to_string(n));
|
||||
}
|
||||
|
||||
void WriteExtradata(AegisubExtradataMap const& extradata) {
|
||||
if (extradata.size() == 0)
|
||||
return;
|
||||
|
@ -125,6 +161,7 @@ struct Writer {
|
|||
void AssSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, agi::vfr::Framerate const& fps, std::string const& encoding) const {
|
||||
Writer writer(filename, encoding);
|
||||
writer.Write(src->Info);
|
||||
writer.Write(src->Properties);
|
||||
writer.Write(src->Styles);
|
||||
writer.Write(src->Attachments);
|
||||
writer.Write(src->Events);
|
||||
|
|
|
@ -61,7 +61,6 @@ VideoController::VideoController(agi::Context *c)
|
|||
context->ass->AddCommitListener(&VideoController::OnSubtitlesCommit, this),
|
||||
context->project->AddVideoProviderListener(&VideoController::OnNewVideoProvider, this),
|
||||
context->selectionController->AddActiveLineListener(&VideoController::OnActiveLineChanged, this),
|
||||
context->subsController->AddFileSaveListener(&VideoController::OnSubtitlesSave, this),
|
||||
}))
|
||||
{
|
||||
Bind(EVT_VIDEO_ERROR, &VideoController::OnVideoError, this);
|
||||
|
@ -76,6 +75,9 @@ void VideoController::OnNewVideoProvider(AsyncVideoProvider *new_provider) {
|
|||
provider = new_provider;
|
||||
if (!provider) {
|
||||
color_matrix.clear();
|
||||
context->ass->Properties.ar_mode = 0;
|
||||
context->ass->Properties.ar_value = 0.0;
|
||||
context->ass->Properties.video_position = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -98,33 +100,12 @@ void VideoController::OnSubtitlesCommit(int type, std::set<const AssDialogue *>
|
|||
}
|
||||
}
|
||||
|
||||
if (changed.empty() || no_amend)
|
||||
if (changed.empty())
|
||||
provider->LoadSubtitles(context->ass.get());
|
||||
else
|
||||
provider->UpdateSubtitles(context->ass.get(), changed);
|
||||
if (!IsPlaying())
|
||||
provider->GetFrame(frame_n, TimeAtFrame(frame_n));
|
||||
|
||||
no_amend = false;
|
||||
}
|
||||
|
||||
void VideoController::OnSubtitlesSave() {
|
||||
no_amend = true;
|
||||
|
||||
if (!provider) {
|
||||
context->ass->SaveUIState("Video Aspect Ratio", "");
|
||||
context->ass->SaveUIState("Video Position", "");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string ar;
|
||||
if (ar_type == AspectRatio::Custom)
|
||||
ar = "c" + std::to_string(ar_value);
|
||||
else
|
||||
ar = std::to_string((int)ar_type);
|
||||
|
||||
context->ass->SaveUIState("Video Aspect Ratio", ar);
|
||||
context->ass->SaveUIState("Video Position", std::to_string(frame_n));
|
||||
}
|
||||
|
||||
void VideoController::OnActiveLineChanged(AssDialogue *line) {
|
||||
|
@ -133,6 +114,7 @@ void VideoController::OnActiveLineChanged(AssDialogue *line) {
|
|||
}
|
||||
|
||||
void VideoController::RequestFrame() {
|
||||
context->ass->Properties.video_position = frame_n;
|
||||
provider->RequestFrame(frame_n, TimeAtFrame(frame_n));
|
||||
}
|
||||
|
||||
|
@ -243,12 +225,16 @@ double VideoController::GetARFromType(AspectRatio type) const {
|
|||
void VideoController::SetAspectRatio(double value) {
|
||||
ar_type = AspectRatio::Custom;
|
||||
ar_value = mid(.5, value, 5.);
|
||||
context->ass->Properties.ar_mode = (int)ar_type;
|
||||
context->ass->Properties.ar_value = ar_value;
|
||||
ARChange(ar_type, ar_value);
|
||||
}
|
||||
|
||||
void VideoController::SetAspectRatio(AspectRatio type) {
|
||||
ar_value = mid(.5, GetARFromType(type), 5.);
|
||||
ar_type = type;
|
||||
context->ass->Properties.ar_mode = (int)ar_type;
|
||||
context->ass->Properties.ar_value = ar_value;
|
||||
ARChange(ar_type, ar_value);
|
||||
}
|
||||
|
||||
|
|
|
@ -101,19 +101,12 @@ class VideoController final : public wxEvtHandler {
|
|||
|
||||
std::vector<agi::signal::Connection> connections;
|
||||
|
||||
/// Amending the frame source's copy of the subtitle file requires that it
|
||||
/// be kept in perfect sync. Saving the file can add lines to the file
|
||||
/// without a commit, breaking this sync, so force a non-amend after each
|
||||
/// save.
|
||||
bool no_amend = false;
|
||||
|
||||
void OnPlayTimer(wxTimerEvent &event);
|
||||
|
||||
void OnVideoError(VideoProviderErrorEvent const& err);
|
||||
void OnSubtitlesError(SubtitlesProviderErrorEvent const& err);
|
||||
|
||||
void OnSubtitlesCommit(int type, std::set<const AssDialogue *> const& changed);
|
||||
void OnSubtitlesSave();
|
||||
void OnNewVideoProvider(AsyncVideoProvider *provider);
|
||||
void OnActiveLineChanged(AssDialogue *line);
|
||||
|
||||
|
|
|
@ -109,7 +109,6 @@ VideoDisplay::VideoDisplay(wxToolBar *toolbar, bool freeSize, wxComboBox *zoomBo
|
|||
connections = agi::signal::make_vector({
|
||||
con->project->AddVideoProviderListener(&VideoDisplay::UpdateSize, this),
|
||||
con->videoController->AddARChangeListener(&VideoDisplay::UpdateSize, this),
|
||||
con->subsController->AddFileSaveListener(&VideoDisplay::OnSubtitlesSave, this),
|
||||
});
|
||||
|
||||
Bind(wxEVT_PAINT, std::bind(&VideoDisplay::Render, this));
|
||||
|
@ -345,6 +344,7 @@ void VideoDisplay::OnSizeEvent(wxSizeEvent &event) {
|
|||
PositionVideo();
|
||||
zoomValue = double(viewport_height) / con->project->VideoProvider()->GetHeight();
|
||||
zoomBox->ChangeValue(wxString::Format("%g%%", zoomValue * 100.));
|
||||
con->ass->Properties.video_zoom = zoomValue;
|
||||
}
|
||||
else {
|
||||
PositionVideo();
|
||||
|
@ -385,11 +385,13 @@ void VideoDisplay::OnKeyDown(wxKeyEvent &event) {
|
|||
}
|
||||
|
||||
void VideoDisplay::SetZoom(double value) {
|
||||
if (value == 0) return;
|
||||
zoomValue = std::max(value, .125);
|
||||
size_t selIndex = zoomValue / .125 - 1;
|
||||
if (selIndex < zoomBox->GetCount())
|
||||
zoomBox->SetSelection(selIndex);
|
||||
zoomBox->ChangeValue(wxString::Format("%g%%", zoomValue * 100.));
|
||||
con->ass->Properties.video_zoom = zoomValue;
|
||||
UpdateSize();
|
||||
}
|
||||
|
||||
|
@ -397,6 +399,7 @@ void VideoDisplay::SetZoomFromBox(wxCommandEvent &) {
|
|||
int sel = zoomBox->GetSelection();
|
||||
if (sel != wxNOT_FOUND) {
|
||||
zoomValue = (sel + 1) * .125;
|
||||
con->ass->Properties.video_zoom = zoomValue;
|
||||
UpdateSize();
|
||||
}
|
||||
}
|
||||
|
@ -444,7 +447,3 @@ void VideoDisplay::Unload() {
|
|||
tool.reset();
|
||||
pending_frame.reset();
|
||||
}
|
||||
|
||||
void VideoDisplay::OnSubtitlesSave() {
|
||||
con->ass->SaveUIState("Video Zoom Percent", std::to_string(zoomValue));
|
||||
}
|
||||
|
|
|
@ -141,8 +141,6 @@ class VideoDisplay final : public wxGLCanvas {
|
|||
void OnSizeEvent(wxSizeEvent &event);
|
||||
void OnContextMenu(wxContextMenuEvent&);
|
||||
|
||||
void OnSubtitlesSave();
|
||||
|
||||
public:
|
||||
/// @brief Constructor
|
||||
VideoDisplay(
|
||||
|
|
Loading…
Reference in a new issue