From 0c4eb020a42e273a6d170661f6b1064519196389 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 15 Sep 2011 05:17:14 +0000 Subject: [PATCH] Rewrite the shift times dialog, detangling it from the grid and making it modeless, and add support for characters outside the local charset in the shift history file path. Closes #1269. Originally committed to SVN as r5596. --- aegisub/src/command/time.cpp | 2 +- aegisub/src/dialog_shift_times.cpp | 576 ++++++++++------------- aegisub/src/dialog_shift_times.h | 116 ++--- aegisub/src/libresrc/default_config.json | 3 +- aegisub/src/subs_grid.cpp | 16 - aegisub/src/subs_grid.h | 11 - 6 files changed, 302 insertions(+), 422 deletions(-) diff --git a/aegisub/src/command/time.cpp b/aegisub/src/command/time.cpp index a0845c557..1ffda77fd 100644 --- a/aegisub/src/command/time.cpp +++ b/aegisub/src/command/time.cpp @@ -161,7 +161,7 @@ struct time_shift : public Command { void operator()(agi::Context *c) { c->videoController->Stop(); - DialogShiftTimes(c).ShowModal(); + (new DialogShiftTimes(c))->Show(); } }; diff --git a/aegisub/src/dialog_shift_times.cpp b/aegisub/src/dialog_shift_times.cpp index 14c269079..2f456794e 100644 --- a/aegisub/src/dialog_shift_times.cpp +++ b/aegisub/src/dialog_shift_times.cpp @@ -1,29 +1,16 @@ -// Copyright (c) 2005, Rodrigo Braz Monteiro -// All rights reserved. +// Copyright (c) 2011, Thomas Goyne // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// 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. // -// * 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. +// 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/ // @@ -36,10 +23,11 @@ #include "config.h" +#include "dialog_shift_times.h" + #ifndef AGI_PRE #include -#include -#include +#include #include #include @@ -49,357 +37,303 @@ #include #endif +#include +#include +#include +#include + #include "ass_dialogue.h" #include "ass_file.h" #include "ass_time.h" -#include "charset_conv.h" -#include "dialog_shift_times.h" +#include "compat.h" #include "include/aegisub/context.h" #include "help_button.h" #include "libresrc/libresrc.h" #include "main.h" #include "standard_paths.h" -#include "subs_grid.h" #include "timeedit_ctrl.h" #include "utils.h" #include "video_context.h" -// IDs -enum { - TEXT_SHIFT_TIME = 1100, - TEXT_SHIFT_FRAME, - RADIO_BACKWARD, - RADIO_FORWARD, - RADIO_TIME, - RADIO_FRAME, - SHIFT_CLEAR_HISTORY -}; - -DialogShiftTimes::DialogShiftTimes (agi::Context *context) -: wxDialog(context->parent, -1, _("Shift Times"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE, _T("JumpTo")) +DialogShiftTimes::DialogShiftTimes(agi::Context *context) +: wxDialog(context->parent, -1, _("Shift Times")) , context(context) +, history_filename(STD_STR(StandardPaths::DecodePath("?user/shift_history.txt"))) +, timecodes_loaded_slot(context->videoController->AddTimecodesListener(&DialogShiftTimes::OnTimecodesLoaded, this)) { - // Set icon SetIcon(BitmapToIcon(GETIMAGE(shift_times_toolbutton_24))); - // Set initial values - ready = true; - shiftframe = 0; - - // Static-box sizers before anything else - wxSizer *TimesSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Shift by")); - wxSizer *HistorySizer = new wxStaticBoxSizer(wxVERTICAL,this,_("History")); + // Create controls + shift_by_time = new wxRadioButton(this, -1, _("Time: "), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); + shift_by_time->SetToolTip(_("Shift by time")); + shift_by_time->Bind(wxEVT_COMMAND_RADIOBUTTON_SELECTED, &DialogShiftTimes::OnByTime, this); - // Times - RadioTime = new wxRadioButton(this,RADIO_TIME,_("Time: "),wxDefaultPosition,wxDefaultSize, wxRB_GROUP); - RadioFrames = new wxRadioButton(this,RADIO_FRAME,_("Frames: "),wxDefaultPosition,wxDefaultSize); - ShiftTime = new TimeEdit(this,TEXT_SHIFT_TIME,_T(""),wxDefaultPosition,wxDefaultSize); - ShiftFrame = new wxTextCtrl(this,TEXT_SHIFT_FRAME,wxString::Format(_T("%i"),shiftframe),wxDefaultPosition,wxDefaultSize); - ShiftTime->SetToolTip(_("Enter time in h:mm:ss.cs notation")); - RadioTime->SetToolTip(_("Shift by time")); - ShiftFrame->Disable(); - if (!context->videoController->TimecodesLoaded()) RadioFrames->Disable(); - else { - ShiftFrame->SetToolTip(_("Enter number of frames to shift by")); - RadioFrames->SetToolTip(_("Shift by frames")); - } - wxSizer *TimeFrameSizer = new wxFlexGridSizer(2,2,5,5); - TimeFrameSizer->Add(RadioTime,0,wxALIGN_CENTER_VERTICAL,0); - TimeFrameSizer->Add(ShiftTime,1); - TimeFrameSizer->Add(RadioFrames,0,wxALIGN_CENTER_VERTICAL,0); - TimeFrameSizer->Add(ShiftFrame,1); + shift_by_frames = new wxRadioButton(this, -1 , _("Frames: ")); + shift_by_frames->SetToolTip(_("Shift by frames")); + shift_by_frames->Bind(wxEVT_COMMAND_RADIOBUTTON_SELECTED, &DialogShiftTimes::OnByFrames, this); - // Direction - DirectionForward = new wxRadioButton(this,-1,_("Forward"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); - DirectionBackward = new wxRadioButton(this,-1,_("Backward"), wxDefaultPosition, wxDefaultSize); - DirectionForward->SetToolTip(_("Shifts subs forward, making them appear later. Use if they are appearing too soon.")); - DirectionBackward->SetToolTip(_("Shifts subs backward, making them appear earlier. Use if they are appearing too late.")); - wxSizer *DirectionSizer = new wxBoxSizer(wxHORIZONTAL); - DirectionSizer->Add(DirectionForward,1,wxEXPAND,0); - DirectionSizer->Add(DirectionBackward,1,wxLEFT | wxEXPAND,5); - TimesSizer->Add(TimeFrameSizer,0,wxEXPAND,0); - TimesSizer->Add(DirectionSizer,0,wxEXPAND | wxTOP,5); + shift_time = new TimeEdit(this, -1); + shift_time->SetToolTip(_("Enter time in h:mm:ss.cs notation")); - // Selection - wxString SelChoices[3] = { _("All rows"), _("Selected rows"), _("Selection onward") }; - SelChoice = new wxRadioBox(this,-1,_("Affect"), wxDefaultPosition, wxDefaultSize, 3, SelChoices, 3, wxRA_SPECIFY_ROWS); + shift_frames = new wxTextCtrl(this, -1); + shift_frames->SetToolTip(_("Enter number of frames to shift by")); - // Times - wxString TimesChoices[3] = { _("Start and End times"), _("Start times only"), _("End times only") }; - TimesChoice = new wxRadioBox(this,-1,_("Times"), wxDefaultPosition, wxDefaultSize, 3, TimesChoices, 3, wxRA_SPECIFY_ROWS); + shift_forward = new wxRadioButton(this, -1, _("Forward"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP); + shift_forward->SetToolTip(_("Shifts subs forward, making them appear later. Use if they are appearing too soon.")); - // History - History = new wxListBox(this,-1,wxDefaultPosition,wxSize(350,100), 0, NULL, wxLB_HSCROLL); - wxButton *ClearButton = new wxButton(this,SHIFT_CLEAR_HISTORY,_("Clear")); - HistorySizer->Add(History,1,wxEXPAND,0); - HistorySizer->Add(ClearButton,0,wxEXPAND,0); + shift_backward = new wxRadioButton(this, -1, _("Backward")); + shift_backward->SetToolTip(_("Shifts subs backward, making them appear earlier. Use if they are appearing too late.")); - // Buttons - wxStdDialogButtonSizer *ButtonSizer = new wxStdDialogButtonSizer(); - ButtonSizer->AddButton(new wxButton(this,wxID_OK)); - ButtonSizer->AddButton(new wxButton(this,wxID_CANCEL)); - ButtonSizer->AddButton(new HelpButton(this,_T("Shift Times"))); - ButtonSizer->Realize(); + wxString selection_mode_vals[] = { _("All rows"), _("Selected rows"), _("Selection onward") }; + selection_mode = new wxRadioBox(this, -1, _("Affect"), wxDefaultPosition, wxDefaultSize, 3, selection_mode_vals, 1); - // General layout - wxSizer *LeftSizer = new wxBoxSizer(wxVERTICAL); - wxSizer *RightSizer = new wxBoxSizer(wxHORIZONTAL); - wxSizer *TopSizer = new wxBoxSizer(wxHORIZONTAL); - wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL); - LeftSizer->Add(TimesSizer,0,wxEXPAND | wxBOTTOM,5); - LeftSizer->Add(SelChoice,0,wxEXPAND | wxBOTTOM,5); - LeftSizer->Add(TimesChoice,0,wxEXPAND,5); - RightSizer->Add(HistorySizer,0,wxEXPAND,0); - TopSizer->Add(LeftSizer,0,wxEXPAND | wxRIGHT,5); - TopSizer->Add(RightSizer,0,wxEXPAND,0); - MainSizer->Add(TopSizer,0,wxEXPAND | wxLEFT | wxBOTTOM | wxRIGHT,5); - MainSizer->Add(ButtonSizer,0,wxEXPAND | wxLEFT | wxBOTTOM | wxRIGHT,5); - - // Set sizer - SetSizer(MainSizer); - MainSizer->SetSizeHints(this); + wxString time_field_vals[] = { _("Start and End times"), _("Start times only"), _("End times only") }; + time_fields = new wxRadioBox(this, -1, _("Times"), wxDefaultPosition, wxDefaultSize, 3, time_field_vals, 1); + + history = new wxListBox(this, -1, wxDefaultPosition, wxSize(350, 100), 0, NULL, wxLB_HSCROLL); + + wxButton *clear_button = new wxButton(this, -1, _("Clear")); + clear_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogShiftTimes::OnClear, this); + + // Set initial control states + OnTimecodesLoaded(context->videoController->FPS()); + OnSelectedSetChanged(Selection(), Selection()); + LoadHistory(); + + shift_time->SetTime(OPT_GET("Tool/Shift Times/Time")->GetInt()); + *shift_frames << (int)OPT_GET("Tool/Shift Times/Frames")->GetInt(); + shift_frames->Disable(); + shift_by_frames->SetValue(!OPT_GET("Tool/Shift Times/ByTime")->GetBool() && shift_by_frames->IsEnabled()); + time_fields->SetSelection(OPT_GET("Tool/Shift Times/Type")->GetInt()); + selection_mode->SetSelection(OPT_GET("Tool/Shift Times/Affect")->GetInt()); + shift_backward->SetValue(OPT_GET("Tool/Shift Times/Direction")->GetBool()); + + // Position controls + wxSizer *shift_amount_sizer = new wxFlexGridSizer(2, 2, 5, 5); + shift_amount_sizer->Add(shift_by_time, wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL)); + shift_amount_sizer->Add(shift_time, wxSizerFlags(1)); + shift_amount_sizer->Add(shift_by_frames, wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL)); + shift_amount_sizer->Add(shift_frames, wxSizerFlags(1)); + + wxSizer *shift_direction_sizer = new wxBoxSizer(wxHORIZONTAL); + shift_direction_sizer->Add(shift_forward, wxSizerFlags(1).Expand()); + shift_direction_sizer->Add(shift_backward, wxSizerFlags(1).Expand().Border(wxLEFT)); + + wxSizer *shift_by_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Shift by")); + shift_by_sizer->Add(shift_amount_sizer, wxSizerFlags().Expand()); + shift_by_sizer->Add(shift_direction_sizer, wxSizerFlags().Expand().Border(wxTOP)); + + wxSizer *left_sizer = new wxBoxSizer(wxVERTICAL); + left_sizer->Add(shift_by_sizer, wxSizerFlags().Expand().Border(wxBOTTOM)); + left_sizer->Add(selection_mode, wxSizerFlags().Expand().Border(wxBOTTOM)); + left_sizer->Add(time_fields, wxSizerFlags().Expand()); + + wxSizer *history_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("History")); + history_sizer->Add(history, wxSizerFlags(1).Expand()); + history_sizer->Add(clear_button, wxSizerFlags().Expand().Border(wxTOP)); + + wxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); + top_sizer->Add(left_sizer, wxSizerFlags().Border(wxALL & ~wxRIGHT).Expand()); + top_sizer->Add(history_sizer, wxSizerFlags().Border().Expand()); + + wxSizer *main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(top_sizer, wxSizerFlags().Border(wxALL & ~wxBOTTOM)); + main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL | wxHELP), wxSizerFlags().Right().Border()); + SetSizerAndFit(main_sizer); CenterOnParent(); - // Load values from options - if (!OPT_GET("Tool/Shift Times/ByTime")->GetBool()) { - if (RadioFrames->IsEnabled()) { - RadioFrames->SetValue(true); - ShiftFrame->Enable(true); - ShiftTime->Enable(false); - ShiftFrame->SetValue(AegiIntegerToString(OPT_GET("Tool/Shift Times/Length")->GetInt())); - } + Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogShiftTimes::Process, this, wxID_OK); + Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogShiftTimes::OnClose, this, wxID_CANCEL); + Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::tr1::bind(&HelpButton::OpenPage, "Shift Times"), wxID_HELP); + context->selectionController->AddSelectionListener(this); +} + +DialogShiftTimes::~DialogShiftTimes() { + context->selectionController->RemoveSelectionListener(this); +} + +void DialogShiftTimes::OnTimecodesLoaded(agi::vfr::Framerate const& new_fps) { + fps = new_fps; + if (fps.IsLoaded()) { + shift_by_frames->Enable(); } else { - ShiftTime->SetTime(OPT_GET("Tool/Shift Times/Length")->GetInt()); + shift_by_time->SetValue(true); + shift_by_frames->Disable(); + shift_time->Enable(); + shift_frames->Disable(); } - TimesChoice->SetSelection(OPT_GET("Tool/Shift Times/Type")->GetInt()); - SelChoice->SetSelection(OPT_GET("Tool/Shift Times/Affect")->GetInt()); - if (OPT_GET("Tool/Shift Times/Direction")->GetBool()) DirectionBackward->SetValue(true); +} - // Has selection? - wxArrayInt sel = context->subsGrid->GetSelection(); - if (sel.Count() == 0) { - SelChoice->Enable(1,false); - SelChoice->Enable(2,false); - SelChoice->SetSelection(0); +void DialogShiftTimes::OnSelectedSetChanged(Selection const&, Selection const&) { + if (context->selectionController->GetSelectedSet().empty()) { + selection_mode->Enable(1, false); + selection_mode->Enable(2, false); + selection_mode->SetSelection(0); + } + else { + selection_mode->Enable(1, true); + selection_mode->Enable(2, true); } - - // Load history - LoadHistory(StandardPaths::DecodePath(_T("?user/shift_history.txt"))); } -/////////////// -// Event table -BEGIN_EVENT_TABLE(DialogShiftTimes, wxDialog) - EVT_BUTTON(wxID_CANCEL,DialogShiftTimes::OnClose) - EVT_BUTTON(wxID_OK,DialogShiftTimes::OnOK) - EVT_BUTTON(SHIFT_CLEAR_HISTORY,DialogShiftTimes::OnClear) - EVT_RADIOBUTTON(RADIO_TIME,DialogShiftTimes::OnRadioTime) - EVT_RADIOBUTTON(RADIO_FRAME,DialogShiftTimes::OnRadioFrame) -END_EVENT_TABLE() - - - -/// @brief Clear History -/// @param event -/// -void DialogShiftTimes::OnClear(wxCommandEvent &event) { - wxRemoveFile(StandardPaths::DecodePath(_T("?user/shift_history.txt"))); - History->Clear(); +void DialogShiftTimes::OnClear(wxCommandEvent &) { + wxRemoveFile(lagi_wxString(history_filename)); + history->Clear(); } +void DialogShiftTimes::OnClose(wxCommandEvent &) { + long shift; + shift_frames->GetValue().ToLong(&shift); -/// @brief Cancel -/// @param event -/// -void DialogShiftTimes::OnClose(wxCommandEvent &event) { - EndModal(0); + OPT_SET("Tool/Shift Times/Time")->SetInt(shift_time->time.GetMS()); + OPT_SET("Tool/Shift Times/Frames")->SetInt(shift); + OPT_SET("Tool/Shift Times/ByTime")->SetBool(shift_by_time->GetValue()); + OPT_SET("Tool/Shift Times/Type")->SetInt(time_fields->GetSelection()); + OPT_SET("Tool/Shift Times/Affect")->SetInt(selection_mode->GetSelection()); + OPT_SET("Tool/Shift Times/Direction")->SetBool(shift_backward->GetValue()); + + Destroy(); } +void DialogShiftTimes::OnByTime(wxCommandEvent &) { + shift_time->Enable(true); + shift_frames->Enable(false); +} +void DialogShiftTimes::OnByFrames(wxCommandEvent &) { + shift_time->Enable(false); + shift_frames->Enable(true); +} -/// @brief Apply -/// @param event -/// @return -/// -void DialogShiftTimes::OnOK(wxCommandEvent &event) { - // General values - int type = TimesChoice->GetSelection(); - int affect = SelChoice->GetSelection(); - bool allrows = affect == 0; - bool selOnward = affect == 2; - long len; - bool byTime = RadioTime->GetValue(); - bool backward = DirectionBackward->GetValue(); - bool didSomething = false; +void DialogShiftTimes::SaveHistory(std::vector > const& shifted_blocks) { + wxString filename = wxFileName(context->ass->filename).GetFullName(); + int fields = time_fields->GetSelection(); - // Selection - int nrows = context->subsGrid->GetRows(); - wxArrayInt sel = context->subsGrid->GetSelection(); - int firstSel = 0; - if (sel.Count()) firstSel = sel[0]; + wxString new_line = wxString::Format("%s, %s %s, %s, ", + filename.empty() ? _("unsaved") : filename, + shift_by_time->GetValue() ? shift_time->GetValue() : shift_frames->GetValue() + _(" frames"), + shift_backward->GetValue() ? _("backward") : _("forward"), + fields == 0 ? _("s+e") : fields == 1 ? _("s") : _("e")); - // Get length - if (byTime) len = ShiftTime->time.GetMS(); - else ShiftFrame->GetValue().ToLong(&len); - - if (byTime && len == 0) { - // Shift zero milliseconds in time mode - // Equivalent to doing nothing at all, so just dismiss - EndModal(0); - return; - } - - // If backwards, invert - if (backward) len = -len; - - // Shift - for (int i=0;i= firstSel && selOnward) || context->subsGrid->IsInSelection(i)) { - if (byTime) context->subsGrid->ShiftLineByTime(i,len,type); - else context->subsGrid->ShiftLineByFrames(i,len,type); - didSomething = true; + int sel_mode = selection_mode->GetSelection(); + if (sel_mode == 0) + new_line += _("all"); + else if (sel_mode == 2) + new_line += wxString::Format(_("from %d onward"), shifted_blocks.front().first); + else { + new_line += _("sel "); + for (size_t i = 0; i < shifted_blocks.size(); ++i) { + std::pair const& b = shifted_blocks[i]; + wxString term = i == shifted_blocks.size() - 1 ? "" : ";"; + if (b.first == b.second) + new_line += wxString::Format("%d%s", b.first, term); + else + new_line += wxString::Format("%d-%d%s", b.first, b.second, term); } } - // Add entry to history - if (didSomething) { - if (backward) len = -len; - wxString message; - wxFileName assfile(context->ass->filename); - wxString filename = assfile.GetFullName(); + try { + agi::io::Save file(history_filename); - // File - if (filename.IsEmpty()) message << _("unsaved, "); - else message << filename << _T(", "); + for (size_t i = 0; i < history->GetCount(); ++i) + file.Get() << history->GetString(i).utf8_str() << std::endl; + file.Get() << new_line.utf8_str() << std::endl; + } + catch (agi::acs::AcsError const& e) { + LOG_E("dialog_shift_times/save_history") << "Cannot save shift times history: " << e.GetChainedMessage(); + } +} - // Time/frames - if (byTime) message << ShiftTime->GetValue() << _T(" "); - else message << len << _(" frames "); +void DialogShiftTimes::LoadHistory() { + history->Clear(); + history->Freeze(); - // Forward/backwards - if (backward) message << _("backward, "); - else message << _("forward, "); + try { + agi::scoped_ptr file(agi::io::Open(history_filename)); + std::string buffer; + while(!file->eof()) { + getline(*file, buffer); + if (buffer.size()) + history->Insert(lagi_wxString(buffer), 0); + } + } + catch (agi::acs::AcsError const& e) { + LOG_E("dialog_shift_times/save_history") << "Cannot load shift times history: " << e.GetChainedMessage(); + } + catch (...) { + history->Thaw(); + throw; + } - // Start/end - if (type == 0) message << _("s+e, "); - if (type == 1) message << _("s, "); - if (type == 2) message << _("e, "); + history->Thaw(); +} - // Selection range - if (affect == 0) message << _("all"); - else if (affect == 2) message << wxString::Format(_("from %i onward"),sel[0]+1); - else { // This huge block of code prints the selected ranges of subs - message << _("sel "); - int last = sel[0]-1; - int first = sel[0]; - for (unsigned int i=0;iGetSelection(); + int type = time_fields->GetSelection(); + bool reverse = shift_backward->GetValue(); + bool by_time = shift_by_time->GetValue(); + + bool start = type != 2; + bool end = type != 1; + + Selection sel = context->selectionController->GetSelectedSet(); + + long shift; + if (by_time) { + shift = shift_time->time.GetMS(); + if (shift == 0) { + Close(); + return; + } + } + else + shift_frames->GetValue().ToLong(&shift); + + if (reverse) + shift = -shift; + + // Track which rows were shifted for the log + int row_number = 0; + int block_start = 0; + std::vector > shifted_blocks; + + for (entryIter it = context->ass->Line.begin(); it != context->ass->Line.end(); ++it) { + AssDialogue *line = dynamic_cast(*it); + if (!line) continue; + ++row_number; + + if (!sel.count(line)) { + if (block_start) { + shifted_blocks.push_back(std::make_pair(block_start, row_number - 1)); + block_start = 0; } - if (first != last) message << wxString::Format(_T("%i"),first+1) << _T("-") << wxString::Format(_T("%i"),last+1); - else message << wxString::Format(_T("%i"),first+1); + if (mode == 1) continue; + if (mode == 2 && shifted_blocks.empty()) continue; } + else if (!block_start) + block_start = row_number; - // Done, append - AppendToHistory(message); + if (start) + line->Start.SetMS(Shift(line->Start.GetMS(), shift, by_time, agi::vfr::START)); + if (end) + line->End.SetMS(Shift(line->End.GetMS(), shift, by_time, agi::vfr::END)); } - // Store modifications - OPT_SET("Tool/Shift Times/ByTime")->SetBool(byTime); - OPT_SET("Tool/Shift Times/Type")->SetInt(type); - OPT_SET("Tool/Shift Times/Length")->SetInt(len); - OPT_SET("Tool/Shift Times/Affect")->SetInt(affect); - OPT_SET("Tool/Shift Times/Direction")->SetBool(backward); - - // End dialog context->ass->Commit(_("shifting"), AssFile::COMMIT_DIAG_TIME); - EndModal(0); + + if (block_start) + shifted_blocks.push_back(std::make_pair(block_start, row_number - 1)); + + SaveHistory(shifted_blocks); + Close(); } - - -/// @brief Set to time -/// @param event -/// -void DialogShiftTimes::OnRadioTime(wxCommandEvent &event) { - ShiftTime->Enable(true); - ShiftFrame->Enable(false); - event.Skip(); +int DialogShiftTimes::Shift(int initial_time, int shift, bool by_time, agi::vfr::Time type) { + if (by_time) + return initial_time + shift; + else + return fps.TimeAtFrame(shift + fps.FrameAtTime(initial_time, type), type); } - - - -/// @brief Set to frame -/// @param event -/// -void DialogShiftTimes::OnRadioFrame(wxCommandEvent &event) { - ShiftTime->Enable(false); - ShiftFrame->Enable(true); - event.Skip(); -} - - - -/// @brief Appends a line to history -/// @param text -/// @return -/// -void DialogShiftTimes::AppendToHistory(wxString text) { - // Open file - if (HistoryFile.IsEmpty()) return; - using namespace std; - ofstream file; - file.open(HistoryFile.mb_str(csConvLocal),ios::out | ios::app); - if (!file.is_open()) { - return; - } - - // Insert line - file << text.mb_str(wxConvUTF8) << endl; - - // Close - file.close(); -} - - - -/// @brief Loads history from disk -/// @param filename -/// -void DialogShiftTimes::LoadHistory(wxString filename) { - // Open file - using namespace std; - HistoryFile = filename; - ifstream file; - file.open(filename.mb_str(csConvLocal)); - if (!file.is_open()) { - return; - } - - // Setup - string buffer; - History->Clear(); - History->Freeze(); - - // Get lines - while (!file.eof()) { - getline(file,buffer); - wxString curLine(buffer.c_str(),wxConvUTF8); - if (curLine != _T("")) History->Insert(curLine,0); - } - - // Finish updating - History->Thaw(); - //History->SetFirstItem(History->GetCount()-1); - - // Close - file.close(); -} - - diff --git a/aegisub/src/dialog_shift_times.h b/aegisub/src/dialog_shift_times.h index 74e19af12..dc461b8c0 100644 --- a/aegisub/src/dialog_shift_times.h +++ b/aegisub/src/dialog_shift_times.h @@ -1,29 +1,16 @@ -// Copyright (c) 2005, Rodrigo Braz Monteiro -// All rights reserved. +// Copyright (c) 2011, Thomas Goyne // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// 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. // -// * 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. +// 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/ // @@ -38,71 +25,56 @@ #include #endif -namespace agi { struct Context; } +#include +#include + +#include "selection_controller.h" + +class AssDialogue; class TimeEdit; class wxListBox; class wxRadioBox; class wxRadioButton; class wxTextCtrl; +namespace agi { struct Context; } /// DOCME /// @class DialogShiftTimes /// @brief DOCME /// /// DOCME -class DialogShiftTimes : public wxDialog { +class DialogShiftTimes : public wxDialog, private SelectionListener { agi::Context *context; - /// DOCME - bool ready; + std::string history_filename; + agi::vfr::Framerate fps; + agi::signal::Connection timecodes_loaded_slot; - /// DOCME - int shiftframe; + TimeEdit *shift_time; + wxTextCtrl *shift_frames; + wxRadioButton *shift_by_time; + wxRadioButton *shift_by_frames; + wxRadioButton *shift_forward; + wxRadioButton *shift_backward; + wxRadioBox *selection_mode; + wxRadioBox *time_fields; + wxListBox *history; - /// DOCME - wxString HistoryFile; + void SaveHistory(std::vector > const& shifted_blocks); + void LoadHistory(); + void Process(wxCommandEvent&); + int Shift(int initial_time, int shift, bool by_time, agi::vfr::Time type); + void OnClear(wxCommandEvent&); + void OnClose(wxCommandEvent&); + void OnByTime(wxCommandEvent&); + void OnByFrames(wxCommandEvent&); - /// DOCME - TimeEdit *ShiftTime; - - /// DOCME - wxTextCtrl *ShiftFrame; - - /// DOCME - wxRadioButton *RadioTime; - - /// DOCME - wxRadioButton *RadioFrames; - - /// DOCME - wxRadioButton *DirectionForward; - - /// DOCME - wxRadioButton *DirectionBackward; - - /// DOCME - wxRadioBox *SelChoice; - - /// DOCME - wxRadioBox *TimesChoice; - - /// DOCME - wxListBox *History; - - void AppendToHistory(wxString text); - void LoadHistory(wxString filename); - void OnClear(wxCommandEvent &event); - void OnKey(wxKeyEvent &event); - void OnClose(wxCommandEvent &event); - void OnOK(wxCommandEvent &event); - void OnEditTime(wxCommandEvent &event); - void OnEditFrame(wxCommandEvent &event); - void OnRadioTime(wxCommandEvent &event); - void OnRadioFrame(wxCommandEvent &event); + void OnActiveLineChanged(AssDialogue*) { } + void OnSelectedSetChanged(Selection const&, Selection const&); + void OnTimecodesLoaded(agi::vfr::Framerate const& new_fps); public: - DialogShiftTimes (agi::Context *context); - - DECLARE_EVENT_TABLE() + DialogShiftTimes(agi::Context *context); + ~DialogShiftTimes(); }; diff --git a/aegisub/src/libresrc/default_config.json b/aegisub/src/libresrc/default_config.json index 47d9bcade..ece1c3a06 100644 --- a/aegisub/src/libresrc/default_config.json +++ b/aegisub/src/libresrc/default_config.json @@ -338,7 +338,8 @@ "Affect" : 0, "ByTime" : true, "Direction" : true, - "Length" : 0, + "Frames" : 0, + "Time" : 0, "Type" : 0 }, "Spell Checker" : { diff --git a/aegisub/src/subs_grid.cpp b/aegisub/src/subs_grid.cpp index 3df73307a..fc102d42b 100644 --- a/aegisub/src/subs_grid.cpp +++ b/aegisub/src/subs_grid.cpp @@ -496,22 +496,6 @@ void SubtitlesGrid::DuplicateLines(int n1,int n2,bool nextFrame) { SetActiveLine(GetDialogue(n1+step)); } -void SubtitlesGrid::ShiftLineByTime(int n,int len,int type) { - assert(type >= 0 && type <= 2); - AssDialogue *cur = GetDialogue(n); - - if (type != 2) cur->Start.SetMS(cur->Start.GetMS() + len); - if (type != 1) cur->End.SetMS(cur->End.GetMS() + len); -} - -void SubtitlesGrid::ShiftLineByFrames(int n,int len,int type) { - assert(type >= 0 && type <= 2); - AssDialogue *cur = GetDialogue(n); - - if (type != 2) cur->Start.SetMS(context->videoController->TimeAtFrame(len + context->videoController->FrameAtTime(cur->Start.GetMS(),agi::vfr::START),agi::vfr::START)); - if (type != 1) cur->End.SetMS(context->videoController->TimeAtFrame(len + context->videoController->FrameAtTime(cur->End.GetMS(),agi::vfr::END),agi::vfr::END)); -} - void SubtitlesGrid::SplitLine(AssDialogue *n1,int pos,bool estimateTimes) { AssDialogue *n2 = new AssDialogue(*n1); InsertLine(n2,GetDialogueIndex(n1),true,false); diff --git a/aegisub/src/subs_grid.h b/aegisub/src/subs_grid.h index 7d66db963..7ebea5afc 100644 --- a/aegisub/src/subs_grid.h +++ b/aegisub/src/subs_grid.h @@ -102,17 +102,6 @@ public: /// @param nextFrame Set the new lines to start and end on the next frame void DuplicateLines(int first,int last,bool nextFrame=false); - /// @brief Shift line by time - /// @param n Line to shift - /// @param len ms to shift by - /// @param type 0: Start + End; 1: Start; 2: End - void ShiftLineByTime(int lineNumber,int len,int type); - /// @brief Shift line by frames - /// @param n Line to shift - /// @param len frames to shift by - /// @param type 0: Start + End; 1: Start; 2: End - void ShiftLineByFrames(int lineNumber,int len,int type); - void InsertLine(AssDialogue *line,int position,bool insertAfter,bool update=true); /// @brief Delete selected lines /// @param target Lines to delete