@@ -11360,23 +11360,23 @@ private void TextBoxRemoveAllFormatting()
1136011360 private void TextBoxBold()
1136111361 {
1136211362 var tb = EditTextBox;
11363- ToggleTextBoxTag(tb, "b", "b1", "b0" );
11363+ ToggleTextBoxTag(tb, "b");
1136411364 _updateAudioVisualizer = true;
1136511365 }
1136611366
1136711367 [RelayCommand]
1136811368 private void TextBoxItalic()
1136911369 {
1137011370 var tb = EditTextBox;
11371- ToggleTextBoxTag(tb, "i", "i1", "i0" );
11371+ ToggleTextBoxTag(tb, "i");
1137211372 _updateAudioVisualizer = true;
1137311373 }
1137411374
1137511375 [RelayCommand]
1137611376 private void TextBoxUnderline()
1137711377 {
1137811378 var tb = EditTextBox;
11379- ToggleTextBoxTag(tb, "u", "u1", "u0" );
11379+ ToggleTextBoxTag(tb, "u");
1138011380 _updateAudioVisualizer = true;
1138111381 }
1138211382
@@ -13443,72 +13443,105 @@ public void SelectAndScrollToSubtitle(SubtitleLineViewModel subtitle)
1344313443 }, DispatcherPriority.Background);
1344413444 }
1344513445
13446- private bool ToggleTextBoxTag(ITextBoxWrapper tb, string htmlTag, string assaOn, string assaOff )
13446+ private bool ToggleTextBoxTag(ITextBoxWrapper tb, string tag )
1344713447 {
1344813448 if (tb == null || tb.Text == null)
1344913449 {
1345013450 return false;
1345113451 }
1345213452
13453+ var isAssa = SelectedSubtitleFormat is AdvancedSubStationAlpha;
1345313454 var selectionStart = Math.Min(tb.SelectionStart, tb.SelectionEnd);
1345413455 var selectionEnd = Math.Max(tb.SelectionStart, tb.SelectionEnd);
1345513456 var selectionLength = selectionEnd - selectionStart;
1345613457
13457- var isAssa = SelectedSubtitleFormat is AdvancedSubStationAlpha;
13458+ // No text selected - toggle the whole line (or just the current dialog line).
1345813459 if (selectionLength == 0)
1345913460 {
13460- if (isAssa)
13461+ var text = tb.Text;
13462+ var lines = text.SplitToLines();
13463+
13464+ // Find the line where the caret is currently located (do not count wrapped lines).
13465+ var numberOfNewLines = 0;
13466+ for (var i = 0; i < selectionStart && i < text.Length; i++)
1346113467 {
13462- if (tb.Text.Contains("{\\" + assaOn + "}") )
13468+ if (text[i] == '\n' )
1346313469 {
13464- tb.Text = tb.Text.Replace("{\\" + assaOn + "}", string.Empty)
13465- .Replace("{\\" + assaOff + "}", string.Empty);
13466- }
13467- else
13468- {
13469- tb.Text = "{\\" + assaOn + "}" + tb.Text + "{\\" + assaOff + "}";
13470+ numberOfNewLines++;
1347013471 }
1347113472 }
13473+
13474+ var selectedLineIdx = Math.Min(numberOfNewLines, lines.Count - 1);
13475+ var selectedLine = lines[selectedLineIdx];
13476+
13477+ // When the caret is on a dialog line ("- ..."), only toggle that line so the
13478+ // other speaker's line keeps its formatting.
13479+ var isDialog = selectedLine.StartsWith('-') ||
13480+ selectedLine.StartsWith("<" + tag + ">-", StringComparison.OrdinalIgnoreCase);
13481+
13482+ var textLen = text.Length;
13483+ if (isDialog)
13484+ {
13485+ lines[selectedLineIdx] = HtmlUtil.ToggleTag(selectedLine, tag, false, isAssa);
13486+ text = string.Join(Environment.NewLine, lines);
13487+ }
1347213488 else
1347313489 {
13474- if (tb.Text.Contains("<" + htmlTag + ">"))
13475- {
13476- tb.Text = HtmlUtil.RemoveOpenCloseTags(tb.Text, htmlTag);
13477- }
13478- else
13479- {
13480- tb.Text = "<" + htmlTag + ">" + tb.Text + "</" + htmlTag + ">";
13481- }
13490+ text = HtmlUtil.ToggleTag(text, tag, false, isAssa);
1348213491 }
13492+
13493+ tb.Text = text;
13494+
13495+ // Keep the caret next to where it was, shifting by the length of the opening
13496+ // tag inserted before it: "<i>" (3) for HTML, "{\i1}" (5) for ASSA.
13497+ var openTagLength = isAssa ? tag.Length + 4 : tag.Length + 2;
13498+ var newCaret = textLen > text.Length
13499+ ? Math.Max(selectionStart - openTagLength, 0)
13500+ : selectionStart + openTagLength;
13501+ Dispatcher.UIThread.Post(() =>
13502+ {
13503+ tb.Focus();
13504+ tb.SelectionStart = newCaret;
13505+ tb.SelectionEnd = newCaret;
13506+ });
1348313507 }
1348413508 else
1348513509 {
13510+ // Move leading/trailing white-space (spaces and new-lines) outside the tag so
13511+ // " 'word'" becomes " <i>'word'</i>" instead of "<i> 'word'</i>".
13512+ var pre = string.Empty;
13513+ var post = string.Empty;
1348613514 var selectedText = tb.Text.Substring(selectionStart, selectionLength);
13487-
13488- if (isAssa )
13515+ while (selectedText.EndsWith(' ') || selectedText.EndsWith(Environment.NewLine, StringComparison.Ordinal) ||
13516+ selectedText.StartsWith(' ') || selectedText.StartsWith(Environment.NewLine, StringComparison.Ordinal) )
1348913517 {
13490- if (selectedText.Contains("{\\" + assaOn + "}" ))
13518+ if (selectedText.EndsWith(' ' ))
1349113519 {
13492- selectedText = selectedText.Replace("{\\ " + assaOn + "}", string.Empty)
13493- .Replace("{\\" + assaOff + "}", string.Empty );
13520+ post = " " + post;
13521+ selectedText = selectedText.Remove(selectedText.Length - 1 );
1349413522 }
13495- else
13523+
13524+ if (selectedText.EndsWith(Environment.NewLine, StringComparison.Ordinal))
1349613525 {
13497- selectedText = "{\\" + assaOn + "}" + selectedText + "{\\" + assaOff + "}";
13526+ post = Environment.NewLine + post;
13527+ selectedText = selectedText.Remove(selectedText.Length - Environment.NewLine.Length);
1349813528 }
13499- }
13500- else
13501- {
13502- if (selectedText.Contains("<" + htmlTag + ">"))
13529+
13530+ if (selectedText.StartsWith(' '))
1350313531 {
13504- selectedText = HtmlUtil.RemoveOpenCloseTags(selectedText, htmlTag);
13532+ pre += " ";
13533+ selectedText = selectedText.Remove(0, 1);
1350513534 }
13506- else
13535+
13536+ if (selectedText.StartsWith(Environment.NewLine, StringComparison.Ordinal))
1350713537 {
13508- selectedText = "<" + htmlTag + ">" + selectedText + "</" + htmlTag + ">";
13538+ pre += Environment.NewLine;
13539+ selectedText = selectedText.Remove(0, Environment.NewLine.Length);
1350913540 }
1351013541 }
1351113542
13543+ selectedText = pre + HtmlUtil.ToggleTag(selectedText, tag, false, isAssa) + post;
13544+
1351213545 tb.Text = tb.Text
1351313546 .Remove(selectionStart, selectionLength)
1351413547 .Insert(selectionStart, selectedText);
0 commit comments