diff --git a/core/audio_box.cpp b/core/audio_box.cpp index 771ca060c..68b204d8b 100644 --- a/core/audio_box.cpp +++ b/core/audio_box.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2005, Rodrigo Braz Monteiro +// Copyright (c) 2005, Rodrigo Braz Monteiro, Niels Martin Hansen // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -171,9 +171,10 @@ wxPanel(parent,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL|wxBORDER_RAISE JoinButton->SetToolTip(_("Join selected syllables")); JoinButton->Enable(false); karaokeSizer->Add(JoinButton,0,wxRIGHT,0); - SplitButton = new wxButton(this,Audio_Button_Split,_("Split"),wxDefaultPosition,wxSize(-1,-1)); - SplitButton->SetToolTip(_("Split selected syllables")); + SplitButton = new wxToggleButton(this,Audio_Button_Split,_("Split"),wxDefaultPosition,wxSize(-1,-1)); + SplitButton->SetToolTip(_("Toggle splitting-mode")); SplitButton->Enable(false); + SplitButton->SetValue(false); karaokeSizer->Add(SplitButton,0,wxRIGHT,5); karaokeSizer->Add(audioKaraoke,1,wxEXPAND,0); @@ -234,12 +235,12 @@ BEGIN_EVENT_TABLE(AudioBox,wxPanel) EVT_BUTTON(Audio_Button_Commit, AudioBox::OnCommit) EVT_BUTTON(Audio_Button_Goto, AudioBox::OnGoto) EVT_BUTTON(Audio_Button_Join,AudioBox::OnJoin) - EVT_BUTTON(Audio_Button_Split,AudioBox::OnSplit) EVT_BUTTON(Audio_Button_Leadin,AudioBox::OnLeadIn) EVT_BUTTON(Audio_Button_Leadout,AudioBox::OnLeadOut) EVT_TOGGLEBUTTON(Audio_Button_Karaoke, AudioBox::OnKaraoke) EVT_TOGGLEBUTTON(Audio_Check_AutoGoto,AudioBox::OnAutoGoto) + EVT_TOGGLEBUTTON(Audio_Button_Split,AudioBox::OnSplit) EVT_TOGGLEBUTTON(Audio_Check_SSA,AudioBox::OnSSAMode) EVT_TOGGLEBUTTON(Audio_Check_Spectrum,AudioBox::OnSpectrumMode) EVT_TOGGLEBUTTON(Audio_Check_AutoCommit,AudioBox::OnAutoCommit) @@ -427,6 +428,9 @@ void AudioBox::OnCommit(wxCommandEvent &event) { void AudioBox::OnKaraoke(wxCommandEvent &event) { audioDisplay->SetFocus(); if (karaokeMode) { + if (audioKaraoke->splitting) { + audioKaraoke->EndSplit(false); + } karaokeMode = false; audioKaraoke->enabled = false; SetKaraokeButtons(false,false); @@ -447,8 +451,14 @@ void AudioBox::OnKaraoke(wxCommandEvent &event) { // Sets karaoke buttons void AudioBox::SetKaraokeButtons(bool join,bool split) { audioDisplay->SetFocus(); - JoinButton->Enable(join); + JoinButton->Enable(join && !audioKaraoke->splitting); SplitButton->Enable(split); + SplitButton->SetValue(audioKaraoke->splitting); + if (audioKaraoke->splitting) { + SplitButton->SetLabel(_("Cancel Split")); + } else { + SplitButton->SetLabel(_("Split")); + } } @@ -464,7 +474,11 @@ void AudioBox::OnJoin(wxCommandEvent &event) { // Split button void AudioBox::OnSplit(wxCommandEvent &event) { audioDisplay->SetFocus(); - audioKaraoke->Split(); + if (!audioKaraoke->splitting) { + audioKaraoke->BeginSplit(); + } else { + audioKaraoke->EndSplit(false); + } } diff --git a/core/audio_box.h b/core/audio_box.h index da31978ce..cb41f7cb5 100644 --- a/core/audio_box.h +++ b/core/audio_box.h @@ -69,7 +69,7 @@ private: wxSizer *DisplaySizer; wxSashWindow *Sash; - wxButton *SplitButton; + wxToggleButton *SplitButton; wxButton *JoinButton; ToggleBitmap *AutoScroll; ToggleBitmap *SSAMode; diff --git a/core/audio_display.cpp b/core/audio_display.cpp index c0c88a7d6..9e632a007 100644 --- a/core/audio_display.cpp +++ b/core/audio_display.cpp @@ -979,7 +979,9 @@ void AudioDisplay::CommitChanges () { // Update karaoke int karSyl = 0; + bool wasKaraSplitting = false; if (karaoke->enabled) { + wasKaraSplitting = box->audioKaraoke->splitting; karaoke->Commit(); karSyl = karaoke->curSyllable; } @@ -996,8 +998,8 @@ void AudioDisplay::CommitChanges () { karaoke->curSyllable = karSyl; blockUpdate = false; - // If in SSA mode, select next line and "move timing forward" - if (Options.AsBool(_T("Audio SSA Mode")) && Options.AsBool(_T("Audio SSA Next Line on Commit"))) { + // If in SSA mode, select next line and "move timing forward" (unless the user was splitting karaoke) + if (Options.AsBool(_T("Audio SSA Mode")) && Options.AsBool(_T("Audio SSA Next Line on Commit")) && !wasKaraSplitting) { dontReadTimes = true; Next(); dontReadTimes = false; diff --git a/core/audio_karaoke.cpp b/core/audio_karaoke.cpp index c7689c892..25be3a196 100644 --- a/core/audio_karaoke.cpp +++ b/core/audio_karaoke.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2005, Rodrigo Braz Monteiro +// Copyright (c) 2005, Rodrigo Braz Monteiro, Niels Martin Hansen // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -42,6 +42,7 @@ #include "audio_box.h" #include "ass_dialogue.h" #include "ass_override.h" +#include //////////////////////// @@ -51,6 +52,7 @@ KaraokeSyllable::KaraokeSyllable() { position = 0; display_w = 0; display_x = 0; + pending_splits.clear(); selected = false; } @@ -61,6 +63,8 @@ AudioKaraoke::AudioKaraoke(wxWindow *parent) : wxWindow (parent,-1,wxDefaultPosition,wxSize(10,5),wxTAB_TRAVERSAL|wxBORDER_SUNKEN) { enabled = false; + splitting = false; + split_cursor_syl = -1; curSyllable = 0; diag = NULL; } @@ -69,6 +73,11 @@ AudioKaraoke::AudioKaraoke(wxWindow *parent) ////////////////////// // Load from dialogue bool AudioKaraoke::LoadFromDialogue(AssDialogue *_diag) { + // Make sure we're not in splitting-mode + if (splitting) { + EndSplit(false); + } + // Set dialogue diag = _diag; if (!diag) { @@ -117,6 +126,9 @@ wxString AudioKaraoke::GetSyllableTag(AssDialogueBlockOverride *block,int n) { //////////////////// // Writes line back void AudioKaraoke::Commit() { + if (splitting) { + EndSplit(true); + } wxString finalText = _T(""); KaraokeSyllable *syl; size_t n = syllables.size(); @@ -255,7 +267,6 @@ void AudioKaraoke::OnPaint(wxPaintEvent &event) { // Set syllable font wxFont curFont(9,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_NORMAL,false,_T("Verdana"),wxFONTENCODING_SYSTEM); dc.SetFont(curFont); - dc.SetPen(wxPen(wxColour(0,0,0))); // Draw syllables if (enabled) { @@ -266,17 +277,22 @@ void AudioKaraoke::OnPaint(wxPaintEvent &event) { int delta; int dlen; for (size_t i=0;i 0) { + wxArrayInt widths; + if (dc.GetPartialTextExtents(temptext, widths)) { + for (int i = 0; i < syl.pending_splits.size(); i++) { + dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); + dc.DrawLine(dx+4+widths[syl.pending_splits[i]], 0, dx+4+widths[syl.pending_splits[i]], h); + } + } else { + wxLogError(_T("WTF? Failed to GetPartialTextExtents")); + } + } + + if (splitting && split_cursor_syl == i && split_cursor_x > 0) { + dc.SetPen(*wxRED); + dc.DrawLine(dx+4+split_cursor_x, 0, dx+4+split_cursor_x, h); + dc.SetPen(wxPen(wxColour(0,0,0))); + } // Set syllable data - syllables.at(i).display_x = dx; - syllables.at(i).display_w = dlen; + syl.display_x = dx; + syl.display_w = dlen; // Increment dx dx += dlen; @@ -319,23 +359,99 @@ void AudioKaraoke::OnMouse(wxMouseEvent &event) { int y = event.GetY(); bool shift = event.m_shiftDown; - // Left button down - if (event.LeftDown()) { - int syl = GetSylAtX(x); + if (!splitting) { + // Left button down + if (event.LeftDown()) { + int syl = GetSylAtX(x); - if (syl != -1) { - if (shift) { - SetSelection(syl,startClickSyl); - Refresh(false); + if (syl != -1) { + if (shift) { + SetSelection(syl,startClickSyl); + Refresh(false); + } + + else { + SetSelection(syl); + startClickSyl = syl; + curSyllable = syl; + Refresh(false); + display->Update(); + } + } + } + } else { + int syli = GetSylAtX(x); + + if (syli != -1) { + KaraokeSyllable &syl = syllables.at(syli); + + // Get the widths after each character in the text + wxClientDC dc(this); + wxFont curFont(9,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_NORMAL,false,_T("Verdana"),wxFONTENCODING_SYSTEM); + dc.SetFont(curFont); + wxArrayInt widths; + dc.GetPartialTextExtents(syl.contents, widths); + + // Find the character closest to the mouse + int rx = x - syl.display_x - 4; + int split_cursor_char = -1; + split_cursor_syl = -1; + split_cursor_x = -1; + if (syl.contents.Len() >= 2) { + int lastx = widths[0]; + split_cursor_syl = syli; + for (int i = 1; i < widths.size()-1; i++) { + //wxLogDebug(_T("rx=%d, lastx=%d, widths[i]=%d, i=%d, widths.size()=%d, syli=%d"), rx, lastx, widths[i], i, widths.size(), syli); + if (lastx - rx < widths[i] - rx) { + if (rx - lastx < widths[i] - rx) { + //wxLogDebug(_T("Found at PREV!")); + split_cursor_x = lastx; + split_cursor_char = i - 1; + break; + } else if (rx < widths[i]) { + //wxLogDebug(_T("Found at CURRENT!")); + split_cursor_x = widths[i]; + split_cursor_char = i; + break; + } + } + lastx = widths[i]; + } + // If no split-point was caught by the for-loop, it must be over the last character, + // ie. split at next-to-last position + if (split_cursor_x < 0) { + //wxLogDebug(_T("Emergency picking LAST!")); + split_cursor_x = widths[widths.size()-2]; + split_cursor_char = widths.size() - 2; + } } - else { - SetSelection(syl); - startClickSyl = syl; - curSyllable = syl; - Refresh(false); - display->Update(); + // Do something if there was a click and we're at a valid position + if (event.LeftDown() && split_cursor_char >= 0) { + //wxLogDebug(_T("A click!")); + int num_removed = 0; + std::vector::iterator i = syl.pending_splits.begin(); + while (i != syl.pending_splits.end()) { + if (split_cursor_char == *i) { + //wxLogDebug(_T("Erasing entry")); + num_removed++; + syl.pending_splits.erase(i); + } else { + i++; + } + } + if (num_removed == 0) { + syl.pending_splits.push_back(split_cursor_char); + } } + + } else { + split_cursor_syl = -1; + split_cursor_x = -1; + } + + if (split_cursor_syl >= 0) { + Refresh(false); } } } @@ -424,84 +540,98 @@ void AudioKaraoke::Join() { } -/////////////////// -// Split syllables -void AudioKaraoke::Split() { - // Variables - bool hasSplit = false; +//////////////////////// +// Enter splitting-mode +void AudioKaraoke::BeginSplit() { + splitting = true; + split_cursor_syl = -1; + split_cursor_x = -1; + box->SetKaraokeButtons(false, true); + Refresh(false); +} - // Loop - size_t syls = syllables.size(); - for (size_t i=0;i 0) { - syls += split; - i += split; + +//////////////////////////////////////////// +// Leave splitting-mode, committing changes +void AudioKaraoke::EndSplit(bool commit) { + splitting = false; + bool hasSplit = false; + for (int i = 0; i < syllables.size(); i ++) { + if (syllables[i].pending_splits.size() > 0) { + if (commit) { + SplitSyl(i); hasSplit = true; + } else { + syllables[i].pending_splits.clear(); } - if (split == -1) break; } } + SetSelection(0); + // Update if (hasSplit) { display->NeedCommit = true; display->Update(); - Refresh(false); } + // Always redraw, since the display is different in splitting mode + Refresh(false); } -//////////////////// -// Split a syllable +///////////////////////////////////////////////// +// Split a syllable using the pending_slits data int AudioKaraoke::SplitSyl (int n) { - // Get split text - KaraokeSyllable *curSyl = &syllables.at(n); - wxString result = wxGetTextFromUser(_("Enter pipes (\"|\") to split:"), _("Split syllable"), curSyl->contents); - if (result.IsEmpty()) return -1; + syllables.reserve(syllables.size() + syllables[n].pending_splits.size()); - // Prepare parsing - const int splits = result.Freq(_T('|')); - const int totalCharLen = curSyl->contents.Length() + splits + 1; - const int charRound = totalCharLen / 2; - const int totalTimeLen = curSyl->length; - int curpos = curSyl->position; - int curlen = 0; - wxStringTokenizer tkn(result,_T("|"),wxTOKEN_RET_EMPTY_ALL); - bool isFirst = true; + // Start by sorting the split points + std::sort(syllables[n].pending_splits.begin(),syllables[n].pending_splits.end()); - // Parse - for (int curn=n;tkn.HasMoreTokens();curn++) { - // Prepare syllable - if (!isFirst) { - KaraokeSyllable temp; - syllables.insert(syllables.begin()+curn,temp); - temp.selected = true; + wxString originalText = syllables[n].contents; + int originalDuration = syllables[n].length; + + // Fixup the first syllable + syllables[n].contents = originalText.Mid(0, syllables[n].pending_splits[0] + 1); + syllables[n].length = originalDuration * syllables[n].contents.Length() / originalText.Length(); + int curpos = syllables[n].position + syllables[n].length; + + // For each split, make a new syllable + for (int i = 0; i < syllables[n].pending_splits.size(); i++) { + KaraokeSyllable temp; + if (i < syllables[n].pending_splits.size()-1) { + // in the middle + temp.contents = originalText.Mid(syllables[n].pending_splits[i]+1, syllables[n].pending_splits[i+1] - syllables[n].pending_splits[i]); + } else { + // the last one (take the rest) + temp.contents = originalText.Mid(syllables[n].pending_splits[i]+1); } - curSyl = &syllables.at(curn); - - // Set text - wxString token = tkn.GetNextToken(); - curSyl->contents = token; - - // Set position - int len = (totalTimeLen * (token.Length() + 1) + charRound) / totalCharLen; - curlen += len; - if (curlen > totalTimeLen) { - len -= totalTimeLen - curlen; - curlen = totalTimeLen; - } - curSyl->length = len; - curSyl->position = curpos; - curpos += len; - - // Done - isFirst = false; + temp.length = originalDuration * temp.contents.Length() / originalText.Length(); + temp.position = curpos; + curpos += temp.length; + syllables.insert(syllables.begin()+n+i+1, temp); } - // Return splits - return splits; + // The total duration of the new syllables will be equal to or less than the original duration + // Fix this, so they'll always add up + // Use an unfair method, just adding 1 to each syllable one after another, until it's correct + int newDuration = 0; + for (int j = n; j < syllables[n].pending_splits.size()+n+1; j++) { + newDuration += syllables[j].length; + } + int k = n; + while (newDuration < originalDuration) { + syllables[k].length++; + k++; + if (k >= syllables.size()) { + k = n; + } + newDuration++; + } + + // Prepare for return and clear pending splits + int numsplits = syllables[n].pending_splits.size(); + syllables[n].pending_splits.clear(); + return numsplits; } diff --git a/core/audio_karaoke.h b/core/audio_karaoke.h index 4271ac4a9..1c0a4c463 100644 --- a/core/audio_karaoke.h +++ b/core/audio_karaoke.h @@ -64,6 +64,8 @@ public: wxString tag; bool selected; + std::vector pending_splits; + KaraokeSyllable(); }; @@ -80,6 +82,9 @@ private: AssDialogue *diag; int startClickSyl; + int split_cursor_syl; + int split_cursor_x; + int GetKaraokeLength(AssDialogueBlockOverride *block); wxString GetSyllableTag(AssDialogueBlockOverride *block,int n); void AutoSplit(); @@ -98,8 +103,8 @@ public: int curSyllable; bool enabled; + bool splitting; SylVector syllables; - SylVector origSyl; AudioKaraoke(wxWindow *parent); bool LoadFromDialogue(AssDialogue *diag); @@ -109,7 +114,8 @@ public: bool SyllableDelta(int n,int delta,int mode); void Join(); - void Split(); + void BeginSplit(); + void EndSplit(bool commit=true); DECLARE_EVENT_TABLE() }; diff --git a/core/changelog.txt b/core/changelog.txt index 8792fa18a..58c0bca7f 100644 --- a/core/changelog.txt +++ b/core/changelog.txt @@ -11,6 +11,8 @@ Please visit http://aegisub.net to download latest version o Fixed bug, triggered when a line had a style not defined in the subs. A warning is now shown instead. - Fixed bug in parser that would cause Aegisub to crash if you had a \fn without parameters and tried to pick font. (AMZ) - Fixed crash when opening audio that appeared in 1.08 (Myrsloik) +- Implemented new graphical, mouse-controlled karaoke syllable splitter (jfs) + = 1.09 beta - 2006.01.16 =========================== diff --git a/core/dialog_timing_processor.cpp b/core/dialog_timing_processor.cpp index 0f167ca06..92cb3d5e4 100644 --- a/core/dialog_timing_processor.cpp +++ b/core/dialog_timing_processor.cpp @@ -157,6 +157,8 @@ DialogTimingProcessor::DialogTimingProcessor(wxWindow *parent,SubtitlesGrid *_gr MainSizer->SetSizeHints(this); SetSizer(MainSizer); + CenterOnParent(); + // Update UpdateControls(); }