2013-01-26 02:57:46 +01:00
|
|
|
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
|
|
|
|
//
|
|
|
|
// Permission to use, copy, modify, and distribute this software for any
|
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
// copyright notice and this permission notice appear in all copies.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
//
|
|
|
|
// Aegisub Project http://www.aegisub.org/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include "subs_controller.h"
|
|
|
|
|
2014-03-04 17:32:29 +01:00
|
|
|
#include "ass_attachment.h"
|
2013-01-26 02:57:46 +01:00
|
|
|
#include "ass_dialogue.h"
|
|
|
|
#include "ass_file.h"
|
2014-03-04 17:32:29 +01:00
|
|
|
#include "ass_info.h"
|
2013-01-26 02:57:46 +01:00
|
|
|
#include "ass_style.h"
|
2014-03-05 02:36:02 +01:00
|
|
|
#include "base_grid.h"
|
2013-02-01 16:28:57 +01:00
|
|
|
#include "charset_detect.h"
|
2013-01-26 02:57:46 +01:00
|
|
|
#include "compat.h"
|
|
|
|
#include "command/command.h"
|
|
|
|
#include "include/aegisub/context.h"
|
|
|
|
#include "options.h"
|
2014-03-05 02:36:02 +01:00
|
|
|
#include "selection_controller.h"
|
2013-01-26 02:57:46 +01:00
|
|
|
#include "subtitle_format.h"
|
|
|
|
#include "text_file_reader.h"
|
2013-01-30 15:44:51 +01:00
|
|
|
#include "utils.h"
|
2013-01-26 02:57:46 +01:00
|
|
|
#include "video_context.h"
|
|
|
|
|
|
|
|
#include <libaegisub/fs.h>
|
2013-01-30 04:35:37 +01:00
|
|
|
#include <libaegisub/path.h>
|
2013-01-26 02:57:46 +01:00
|
|
|
#include <libaegisub/util.h>
|
|
|
|
|
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
#include <boost/format.hpp>
|
|
|
|
#include <wx/msgdlg.h>
|
|
|
|
|
2013-01-30 15:40:02 +01:00
|
|
|
namespace {
|
|
|
|
void autosave_timer_changed(wxTimer *timer) {
|
|
|
|
int freq = OPT_GET("App/Auto/Save Every Seconds")->GetInt();
|
|
|
|
if (freq > 0 && OPT_GET("App/Auto/Save")->GetBool())
|
|
|
|
timer->Start(freq * 1000);
|
|
|
|
else
|
|
|
|
timer->Stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-26 02:57:46 +01:00
|
|
|
struct SubsController::UndoInfo {
|
2014-03-04 17:32:29 +01:00
|
|
|
std::vector<std::pair<std::string, std::string>> script_info;
|
|
|
|
std::vector<AssStyle> styles;
|
|
|
|
std::vector<AssDialogueBase> events;
|
|
|
|
std::vector<AssAttachment> graphics;
|
|
|
|
std::vector<AssAttachment> fonts;
|
|
|
|
|
2014-03-05 02:36:02 +01:00
|
|
|
mutable std::vector<int> selection;
|
|
|
|
int active_line_id = 0;
|
|
|
|
|
2013-01-26 02:57:46 +01:00
|
|
|
wxString undo_description;
|
|
|
|
int commit_id;
|
2014-03-05 02:36:02 +01:00
|
|
|
|
|
|
|
UndoInfo(const agi::Context *c, wxString const& d, int commit_id)
|
|
|
|
: undo_description(d), commit_id(commit_id)
|
2014-03-04 17:32:29 +01:00
|
|
|
{
|
|
|
|
size_t info_count = 0, style_count = 0, event_count = 0, font_count = 0, graphics_count = 0;
|
2014-03-05 02:36:02 +01:00
|
|
|
for (auto const& line : c->ass->Line) {
|
2014-03-04 17:32:29 +01:00
|
|
|
switch (line.Group()) {
|
2014-03-05 02:36:02 +01:00
|
|
|
case AssEntryGroup::DIALOGUE: ++event_count; break;
|
|
|
|
case AssEntryGroup::INFO: ++info_count; break;
|
|
|
|
case AssEntryGroup::STYLE: ++style_count; break;
|
|
|
|
case AssEntryGroup::FONT: ++font_count; break;
|
|
|
|
case AssEntryGroup::GRAPHIC: ++graphics_count; break;
|
2014-03-04 17:32:29 +01:00
|
|
|
default: assert(false); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
script_info.reserve(info_count);
|
|
|
|
styles.reserve(style_count);
|
|
|
|
events.reserve(event_count);
|
|
|
|
|
2014-03-05 02:36:02 +01:00
|
|
|
for (auto const& line : c->ass->Line) {
|
2014-03-04 17:32:29 +01:00
|
|
|
switch (line.Group()) {
|
|
|
|
case AssEntryGroup::DIALOGUE:
|
|
|
|
events.push_back(static_cast<AssDialogue const&>(line));
|
|
|
|
break;
|
|
|
|
case AssEntryGroup::INFO: {
|
|
|
|
auto info = static_cast<const AssInfo *>(&line);
|
|
|
|
script_info.emplace_back(info->Key(), info->Value());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case AssEntryGroup::STYLE:
|
|
|
|
styles.push_back(static_cast<AssStyle const&>(line));
|
|
|
|
break;
|
|
|
|
case AssEntryGroup::FONT:
|
|
|
|
fonts.push_back(static_cast<AssAttachment const&>(line));
|
|
|
|
break;
|
|
|
|
case AssEntryGroup::GRAPHIC:
|
|
|
|
graphics.push_back(static_cast<AssAttachment const&>(line));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-03-05 02:36:02 +01:00
|
|
|
|
|
|
|
UpdateActiveLine(c);
|
|
|
|
UpdateSelection(c);
|
2014-03-04 17:32:29 +01:00
|
|
|
}
|
|
|
|
|
2014-03-05 02:36:02 +01:00
|
|
|
void Apply(agi::Context *c) const {
|
|
|
|
// Keep old lines alive until after the commit is complete
|
|
|
|
AssFile old;
|
|
|
|
old.swap(*c->ass);
|
|
|
|
|
|
|
|
sort(begin(selection), end(selection));
|
|
|
|
|
|
|
|
AssDialogue *active_line = nullptr;
|
|
|
|
SubtitleSelection new_sel;
|
|
|
|
|
2014-03-04 17:32:29 +01:00
|
|
|
for (auto const& info : script_info)
|
2014-03-05 02:36:02 +01:00
|
|
|
c->ass->Line.push_back(*new AssInfo(info.first, info.second));
|
2014-03-04 17:32:29 +01:00
|
|
|
for (auto const& style : styles)
|
2014-03-05 02:36:02 +01:00
|
|
|
c->ass->Line.push_back(*new AssStyle(style));
|
|
|
|
for (auto const& event : events) {
|
|
|
|
auto copy = new AssDialogue(event);
|
|
|
|
c->ass->Line.push_back(*copy);
|
|
|
|
if (copy->Id == active_line_id)
|
|
|
|
active_line = copy;
|
|
|
|
if (binary_search(begin(selection), end(selection), copy->Id))
|
|
|
|
new_sel.insert(copy);
|
|
|
|
}
|
2014-03-04 17:32:29 +01:00
|
|
|
for (auto const& attachment : graphics)
|
2014-03-05 02:36:02 +01:00
|
|
|
c->ass->Line.push_back(*new AssAttachment(attachment));
|
2014-03-04 17:32:29 +01:00
|
|
|
for (auto const& attachment : fonts)
|
2014-03-05 02:36:02 +01:00
|
|
|
c->ass->Line.push_back(*new AssAttachment(attachment));
|
|
|
|
|
|
|
|
c->subsGrid->BeginBatch();
|
|
|
|
c->selectionController->SetSelectedSet({ });
|
|
|
|
c->ass->Commit("", AssFile::COMMIT_NEW);
|
|
|
|
c->selectionController->SetSelectionAndActive(new_sel, active_line);
|
|
|
|
c->subsGrid->EndBatch();
|
|
|
|
}
|
|
|
|
|
|
|
|
void UpdateActiveLine(const agi::Context *c) {
|
|
|
|
auto line = c->selectionController->GetActiveLine();
|
|
|
|
if (line)
|
|
|
|
active_line_id = line->Id;
|
|
|
|
}
|
|
|
|
|
|
|
|
void UpdateSelection(const agi::Context *c) {
|
|
|
|
auto const& sel = c->selectionController->GetSelectedSet();
|
|
|
|
selection.clear();
|
|
|
|
selection.reserve(sel.size());
|
|
|
|
for (const auto diag : sel)
|
|
|
|
selection.push_back(diag->Id);
|
2014-03-04 17:32:29 +01:00
|
|
|
}
|
2013-01-26 02:57:46 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
SubsController::SubsController(agi::Context *context)
|
|
|
|
: context(context)
|
|
|
|
, undo_connection(context->ass->AddUndoManager(&SubsController::OnCommit, this))
|
|
|
|
{
|
2013-01-30 15:40:02 +01:00
|
|
|
autosave_timer_changed(&autosave_timer);
|
|
|
|
OPT_SUB("App/Auto/Save", autosave_timer_changed, &autosave_timer);
|
|
|
|
OPT_SUB("App/Auto/Save Every Seconds", autosave_timer_changed, &autosave_timer);
|
|
|
|
|
|
|
|
autosave_timer.Bind(wxEVT_TIMER, [=](wxTimerEvent&) {
|
|
|
|
try {
|
|
|
|
auto fn = AutoSave();
|
|
|
|
if (!fn.empty())
|
2013-01-30 15:44:51 +01:00
|
|
|
StatusTimeout(wxString::Format(_("File backup saved as \"%s\"."), fn.wstring()));
|
2013-01-30 15:40:02 +01:00
|
|
|
}
|
|
|
|
catch (const agi::Exception& err) {
|
2013-01-30 15:44:51 +01:00
|
|
|
StatusTimeout(to_wx("Exception when attempting to autosave file: " + err.GetMessage()));
|
2013-01-30 15:40:02 +01:00
|
|
|
}
|
|
|
|
catch (...) {
|
2013-01-30 15:44:51 +01:00
|
|
|
StatusTimeout("Unhandled exception when attempting to autosave file.");
|
2013-01-30 15:40:02 +01:00
|
|
|
}
|
|
|
|
});
|
2013-01-26 02:57:46 +01:00
|
|
|
}
|
|
|
|
|
2014-03-05 02:36:02 +01:00
|
|
|
void SubsController::SetSelectionController(SelectionController<AssDialogue *> *selection_controller) {
|
|
|
|
active_line_connection = context->selectionController->AddActiveLineListener(&SubsController::OnActiveLineChanged, this);
|
|
|
|
selection_connection = context->selectionController->AddSelectionListener(&SubsController::OnSelectionChanged, this);
|
|
|
|
}
|
|
|
|
|
2013-02-01 16:28:57 +01:00
|
|
|
void SubsController::Load(agi::fs::path const& filename, std::string charset) {
|
2013-02-05 04:26:54 +01:00
|
|
|
try {
|
2013-02-01 16:28:57 +01:00
|
|
|
try {
|
2013-11-10 00:56:09 +01:00
|
|
|
if (charset.empty())
|
|
|
|
charset = CharSetDetect::GetEncoding(filename);
|
2013-02-01 16:28:57 +01:00
|
|
|
}
|
2013-11-10 00:56:09 +01:00
|
|
|
catch (agi::UserCancelException const&) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure that file isn't actually a timecode file
|
|
|
|
if (charset != "binary") {
|
|
|
|
try {
|
|
|
|
TextFileReader testSubs(filename, charset);
|
|
|
|
std::string cur = testSubs.ReadLineFromFile();
|
|
|
|
if (boost::starts_with(cur, "# timecode")) {
|
|
|
|
context->videoController->LoadTimecodes(filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
// if trying to load the file as timecodes fails it's fairly
|
|
|
|
// safe to assume that it is in fact not a timecode file
|
|
|
|
}
|
2013-01-26 02:57:46 +01:00
|
|
|
}
|
|
|
|
|
2013-11-10 00:56:09 +01:00
|
|
|
const SubtitleFormat *reader = SubtitleFormat::GetReader(filename, charset);
|
2013-01-26 02:57:46 +01:00
|
|
|
|
|
|
|
AssFile temp;
|
|
|
|
reader->ReadFile(&temp, filename, charset);
|
|
|
|
|
|
|
|
bool found_style = false;
|
|
|
|
bool found_dialogue = false;
|
|
|
|
|
|
|
|
// Check if the file has at least one style and at least one dialogue line
|
|
|
|
for (auto const& line : temp.Line) {
|
|
|
|
AssEntryGroup type = line.Group();
|
2013-06-13 00:54:19 +02:00
|
|
|
if (type == AssEntryGroup::STYLE) found_style = true;
|
|
|
|
if (type == AssEntryGroup::DIALOGUE) found_dialogue = true;
|
2013-01-26 02:57:46 +01:00
|
|
|
if (found_style && found_dialogue) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// And if it doesn't add defaults for each
|
|
|
|
if (!found_style)
|
|
|
|
temp.InsertLine(new AssStyle);
|
|
|
|
if (!found_dialogue)
|
|
|
|
temp.InsertLine(new AssDialogue);
|
|
|
|
|
|
|
|
context->ass->swap(temp);
|
|
|
|
}
|
|
|
|
catch (agi::UserCancelException const&) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
catch (agi::fs::FileNotFound const&) {
|
|
|
|
wxMessageBox(filename.wstring() + " not found.", "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
|
|
config::mru->Remove("Subtitle", filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
catch (agi::Exception const& err) {
|
|
|
|
wxMessageBox(to_wx(err.GetChainedMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
catch (std::exception const& err) {
|
|
|
|
wxMessageBox(to_wx(err.what()), "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
wxMessageBox("Unknown error", "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SetFileName(filename);
|
|
|
|
|
|
|
|
// Push the initial state of the file onto the undo stack
|
|
|
|
undo_stack.clear();
|
|
|
|
redo_stack.clear();
|
|
|
|
autosaved_commit_id = saved_commit_id = commit_id + 1;
|
|
|
|
context->ass->Commit("", AssFile::COMMIT_NEW);
|
|
|
|
|
|
|
|
// Save backup of file
|
|
|
|
if (CanSave() && OPT_GET("App/Auto/Backup")->GetBool()) {
|
|
|
|
auto path_str = OPT_GET("Path/Auto/Backup")->GetString();
|
|
|
|
agi::fs::path path;
|
|
|
|
if (path_str.empty())
|
|
|
|
path = filename.parent_path();
|
|
|
|
else
|
2013-01-30 04:35:37 +01:00
|
|
|
path = config::path->Decode(path_str);
|
2013-01-26 02:57:46 +01:00
|
|
|
agi::fs::CreateDirectory(path);
|
|
|
|
agi::fs::Copy(filename, path/(filename.stem().string() + ".ORIGINAL" + filename.extension().string()));
|
|
|
|
}
|
|
|
|
|
|
|
|
FileOpen(filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SubsController::Save(agi::fs::path const& filename, std::string const& encoding) {
|
|
|
|
const SubtitleFormat *writer = SubtitleFormat::GetWriter(filename);
|
|
|
|
if (!writer)
|
|
|
|
throw "Unknown file type.";
|
|
|
|
|
|
|
|
int old_autosaved_commit_id = autosaved_commit_id, old_saved_commit_id = saved_commit_id;
|
|
|
|
try {
|
|
|
|
autosaved_commit_id = saved_commit_id = commit_id;
|
|
|
|
|
2013-01-30 04:35:37 +01:00
|
|
|
// Have to set this now for the sake of things that want to save paths
|
2013-01-26 02:57:46 +01:00
|
|
|
// relative to the script in the header
|
|
|
|
this->filename = filename;
|
2013-01-30 04:35:37 +01:00
|
|
|
config::path->SetToken("?script", filename.parent_path());
|
2013-01-26 02:57:46 +01:00
|
|
|
|
|
|
|
FileSave();
|
|
|
|
|
|
|
|
writer->WriteFile(context->ass, filename, encoding);
|
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
autosaved_commit_id = old_autosaved_commit_id;
|
|
|
|
saved_commit_id = old_saved_commit_id;
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
SetFileName(filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SubsController::Close() {
|
|
|
|
undo_stack.clear();
|
|
|
|
redo_stack.clear();
|
|
|
|
autosaved_commit_id = saved_commit_id = commit_id + 1;
|
|
|
|
filename.clear();
|
2013-10-27 14:59:56 +01:00
|
|
|
AssFile blank;
|
|
|
|
swap(blank.Line, context->ass->Line);
|
2013-01-26 02:57:46 +01:00
|
|
|
context->ass->LoadDefault();
|
|
|
|
context->ass->Commit("", AssFile::COMMIT_NEW);
|
|
|
|
}
|
|
|
|
|
|
|
|
int SubsController::TryToClose(bool allow_cancel) const {
|
|
|
|
if (!IsModified())
|
|
|
|
return wxYES;
|
|
|
|
|
|
|
|
int flags = wxYES_NO;
|
|
|
|
if (allow_cancel)
|
|
|
|
flags |= wxCANCEL;
|
|
|
|
int result = wxMessageBox(wxString::Format(_("Do you want to save changes to %s?"), Filename().wstring()), _("Unsaved changes"), flags, context->parent);
|
|
|
|
if (result == wxYES) {
|
|
|
|
cmd::call("subtitle/save", context);
|
|
|
|
// If it fails saving, return cancel anyway
|
|
|
|
return IsModified() ? wxCANCEL : wxYES;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
agi::fs::path SubsController::AutoSave() {
|
|
|
|
if (commit_id == autosaved_commit_id)
|
|
|
|
return "";
|
|
|
|
|
2013-01-30 04:35:37 +01:00
|
|
|
auto path = config::path->Decode(OPT_GET("Path/Auto/Save")->GetString());
|
2013-01-26 02:57:46 +01:00
|
|
|
if (path.empty())
|
|
|
|
path = filename.parent_path();
|
|
|
|
|
|
|
|
agi::fs::CreateDirectory(path);
|
|
|
|
|
|
|
|
auto name = filename.filename();
|
|
|
|
if (name.empty())
|
|
|
|
name = "Untitled";
|
|
|
|
|
|
|
|
path /= str(boost::format("%s.%s.AUTOSAVE.ass") % name.string() % agi::util::strftime("%Y-%m-%d-%H-%M-%S"));
|
|
|
|
|
|
|
|
SubtitleFormat::GetWriter(path)->WriteFile(context->ass, path);
|
|
|
|
autosaved_commit_id = commit_id;
|
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SubsController::CanSave() const {
|
|
|
|
try {
|
|
|
|
return SubtitleFormat::GetWriter(filename)->CanSave(context->ass);
|
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SubsController::SetFileName(agi::fs::path const& path) {
|
|
|
|
filename = path;
|
2013-01-30 04:35:37 +01:00
|
|
|
config::path->SetToken("?script", path.parent_path());
|
2013-01-26 02:57:46 +01:00
|
|
|
config::mru->Add("Subtitle", path);
|
|
|
|
OPT_SET("Path/Last/Subtitles")->SetString(filename.parent_path().string());
|
|
|
|
}
|
|
|
|
|
|
|
|
void SubsController::OnCommit(AssFileCommit c) {
|
|
|
|
if (c.message.empty() && !undo_stack.empty()) return;
|
|
|
|
|
2013-05-06 06:01:02 +02:00
|
|
|
static int next_commit_id = 1;
|
|
|
|
|
|
|
|
commit_id = next_commit_id++;
|
2013-01-26 02:57:46 +01:00
|
|
|
// Allow coalescing only if it's the last change and the file has not been
|
|
|
|
// saved since the last change
|
|
|
|
if (commit_id == *c.commit_id+1 && redo_stack.empty() && saved_commit_id+1 != commit_id && autosaved_commit_id+1 != commit_id) {
|
|
|
|
// If only one line changed just modify it instead of copying the file
|
2014-03-04 17:32:29 +01:00
|
|
|
if (c.single_line && c.single_line->Group() == AssEntryGroup::DIALOGUE) {
|
|
|
|
auto src_diag = static_cast<const AssDialogue *>(c.single_line);
|
|
|
|
for (auto& diag : undo_stack.back().events) {
|
|
|
|
if (diag.Id == src_diag->Id) {
|
|
|
|
diag = *src_diag;
|
|
|
|
break;
|
|
|
|
}
|
2013-01-26 02:57:46 +01:00
|
|
|
}
|
2014-03-04 17:32:29 +01:00
|
|
|
*c.commit_id = commit_id;
|
|
|
|
return;
|
2013-01-26 02:57:46 +01:00
|
|
|
}
|
|
|
|
|
2014-03-04 17:32:29 +01:00
|
|
|
undo_stack.pop_back();
|
2013-01-26 02:57:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
redo_stack.clear();
|
|
|
|
|
2014-03-05 02:36:02 +01:00
|
|
|
undo_stack.emplace_back(context, c.message, commit_id);
|
2013-01-26 02:57:46 +01:00
|
|
|
|
|
|
|
int depth = std::max<int>(OPT_GET("Limits/Undo Levels")->GetInt(), 2);
|
|
|
|
while ((int)undo_stack.size() > depth)
|
|
|
|
undo_stack.pop_front();
|
|
|
|
|
|
|
|
if (undo_stack.size() > 1 && OPT_GET("App/Auto/Save on Every Change")->GetBool() && !filename.empty() && CanSave())
|
|
|
|
Save(filename);
|
|
|
|
|
|
|
|
*c.commit_id = commit_id;
|
|
|
|
}
|
|
|
|
|
2014-03-05 02:36:02 +01:00
|
|
|
void SubsController::OnActiveLineChanged() {
|
|
|
|
if (!undo_stack.empty())
|
|
|
|
undo_stack.back().UpdateActiveLine(context);
|
|
|
|
}
|
2013-01-26 02:57:46 +01:00
|
|
|
|
2014-03-05 02:36:02 +01:00
|
|
|
void SubsController::OnSelectionChanged() {
|
|
|
|
if (!undo_stack.empty())
|
|
|
|
undo_stack.back().UpdateSelection(context);
|
2013-01-26 02:57:46 +01:00
|
|
|
}
|
|
|
|
|
2014-03-04 17:32:29 +01:00
|
|
|
void SubsController::Undo() {
|
|
|
|
if (undo_stack.size() <= 1) return;
|
|
|
|
redo_stack.splice(redo_stack.end(), undo_stack, std::prev(undo_stack.end()));
|
2014-03-05 02:36:02 +01:00
|
|
|
|
|
|
|
commit_id = undo_stack.back().commit_id;
|
|
|
|
undo_stack.back().Apply(context);
|
2014-03-04 17:32:29 +01:00
|
|
|
}
|
|
|
|
|
2013-01-26 02:57:46 +01:00
|
|
|
void SubsController::Redo() {
|
|
|
|
if (redo_stack.empty()) return;
|
2014-03-04 17:32:29 +01:00
|
|
|
undo_stack.splice(undo_stack.end(), redo_stack, std::prev(redo_stack.end()));
|
2014-03-05 02:36:02 +01:00
|
|
|
|
|
|
|
commit_id = undo_stack.back().commit_id;
|
|
|
|
undo_stack.back().Apply(context);
|
2013-01-26 02:57:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
wxString SubsController::GetUndoDescription() const {
|
|
|
|
return IsUndoStackEmpty() ? "" : undo_stack.back().undo_description;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxString SubsController::GetRedoDescription() const {
|
|
|
|
return IsRedoStackEmpty() ? "" : redo_stack.back().undo_description;
|
|
|
|
}
|
|
|
|
|
|
|
|
agi::fs::path SubsController::Filename() const {
|
|
|
|
if (!filename.empty()) return filename;
|
|
|
|
|
|
|
|
// Apple HIG says "untitled" should not be capitalised
|
|
|
|
#ifndef __WXMAC__
|
|
|
|
return _("Untitled").wx_str();
|
|
|
|
#else
|
|
|
|
return _("untitled").wx_str();
|
|
|
|
#endif
|
|
|
|
}
|