diff --git a/src/Test/Core/LanguageAutoDetectLanguagesTest.cs b/src/Test/Core/LanguageAutoDetectLanguagesTest.cs new file mode 100644 index 000000000..d6b4e4437 --- /dev/null +++ b/src/Test/Core/LanguageAutoDetectLanguagesTest.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Nikse.SubtitleEdit.Core.Common; + +namespace Test.Core +{ + [TestClass] + public class LanguageAutoDetectLanguagesTest + { + [TestMethod] + public void AutoDetectEnglish() + { + var res = LanguageAutoDetect.AutoDetectGoogleLanguageOrNull2(new Subtitle(new List + { + new Paragraph("In this tutorial, I'll show you how to add", 0,0) + })); + Assert.AreEqual("en", res); + } + + [TestMethod] + public void AutoDetectDanish() + { + var res = LanguageAutoDetect.AutoDetectGoogleLanguageOrNull2(new Subtitle(new List + { + new Paragraph("Jeg kan ikke finde på noget at lave i dag. Kun at være glad.", 0,0) + })); + Assert.AreEqual("da", res); + } + } +} diff --git a/src/Test/Test.csproj b/src/Test/Test.csproj index fc23a3b7c..bb5bfae6a 100644 --- a/src/Test/Test.csproj +++ b/src/Test/Test.csproj @@ -71,6 +71,7 @@ + diff --git a/src/libse/AudioToText/VoskModel.cs b/src/libse/AudioToText/VoskModel.cs index 111118214..3a6fa426c 100644 --- a/src/libse/AudioToText/VoskModel.cs +++ b/src/libse/AudioToText/VoskModel.cs @@ -33,8 +33,14 @@ namespace Nikse.SubtitleEdit.Core.AudioToText new VoskModel { TwoLetterLanguageCode = "cn", - LanguageName = "Chinese (very large, 1.5G)", - Url = "https://alphacephei.com/vosk/models/vosk-model-cn-kaldi-multicn-2-lgraph.zip", + LanguageName = "Chinese (small, 42 MB)", + Url = "https://alphacephei.com/vosk/models/vosk-model-cn-0.22.zip", + }, + new VoskModel + { + TwoLetterLanguageCode = "cn", + LanguageName = "Chinese (very large, 1.3 GB)", + Url = "https://alphacephei.com/vosk/models/vosk-model-cn-0.22.zip", }, new VoskModel { @@ -51,8 +57,14 @@ namespace Nikse.SubtitleEdit.Core.AudioToText new VoskModel { TwoLetterLanguageCode = "es", - LanguageName = "Spanish", - Url = "https://alphacephei.com/vosk/models/vosk-model-small-es-0.3.zip", + LanguageName = "Spanish (small, 39 MB)", + Url = "https://alphacephei.com/vosk/models/vosk-model-small-es-0.42.zip", + }, + new VoskModel + { + TwoLetterLanguageCode = "es", + LanguageName = "Spanish (large, 1.4 GB)", + Url = "https://alphacephei.com/vosk/models/vosk-model-es-0.42.zip", }, new VoskModel { @@ -75,19 +87,31 @@ namespace Nikse.SubtitleEdit.Core.AudioToText new VoskModel { TwoLetterLanguageCode = "pt", - LanguageName = "Portuguese", + LanguageName = "Portuguese (small, 31 MB)", Url = "https://alphacephei.com/vosk/models/vosk-model-small-pt-0.3.zip", }, new VoskModel + { + TwoLetterLanguageCode = "pt", + LanguageName = "Portuguese (large, 1.6 GB)", + Url = "https://alphacephei.com/vosk/models/vosk-model-pt-fb-v0.1.1-20220516_2113.zip", + }, + new VoskModel { TwoLetterLanguageCode = "it", - LanguageName = "Italian", - Url = "https://alphacephei.com/vosk/models/vosk-model-small-it-0.4.zip", + LanguageName = "Italian (small, 48 MB)", + Url = "https://alphacephei.com/vosk/models/vosk-model-small-it-0.22.zip", + }, + new VoskModel + { + TwoLetterLanguageCode = "it", + LanguageName = "Italian (large, 1.2 GB)", + Url = "https://alphacephei.com/vosk/models/vosk-model-it-0.22.zip", }, new VoskModel { TwoLetterLanguageCode = "nl", - LanguageName = "Dutch", + LanguageName = "Dutch (large, 860 MB)", Url = "https://alphacephei.com/vosk/models/vosk-model-nl-spraakherkenning-0.6-lgraph.zip", }, new VoskModel @@ -99,10 +123,16 @@ namespace Nikse.SubtitleEdit.Core.AudioToText new VoskModel { TwoLetterLanguageCode = "ru", - LanguageName = "Russian", + LanguageName = "Russian (small, 45 MB)", Url = "https://alphacephei.com/vosk/models/vosk-model-small-ru-0.22.zip", }, new VoskModel + { + TwoLetterLanguageCode = "ru", + LanguageName = "Russian (large, 1.8 GB)", + Url = "https://alphacephei.com/vosk/models/vosk-model-ru-0.42.zip", + }, + new VoskModel { TwoLetterLanguageCode = "fa", LanguageName = "Farsi", @@ -123,10 +153,16 @@ namespace Nikse.SubtitleEdit.Core.AudioToText new VoskModel { TwoLetterLanguageCode = "ar", - LanguageName = "Arabic", + LanguageName = "Arabic (small, 318 MB)", Url = "https://alphacephei.com/vosk/models/vosk-model-ar-mgb2-0.4.zip", }, new VoskModel + { + TwoLetterLanguageCode = "ar", + LanguageName = "Arabic (large, 1.3 GB)", + Url = "https://alphacephei.com/vosk/models/vosk-model-ar-0.22-linto-1.1.0.zip", + }, + new VoskModel { TwoLetterLanguageCode = "uk", LanguageName = "Ukrainian (small, 133 MB)", @@ -197,7 +233,13 @@ namespace Nikse.SubtitleEdit.Core.AudioToText TwoLetterLanguageCode = "pl", LanguageName = "Polish", Url = "https://alphacephei.com/vosk/models/vosk-model-small-pl-0.22.zip", - } + }, + new VoskModel + { + TwoLetterLanguageCode = "br", + LanguageName = "Breton (small, 70 MB)", + Url = "https://alphacephei.com/vosk/models/vosk-model-br-0.7.zip", + }, }; } } \ No newline at end of file diff --git a/src/libse/Common/LanguageAutoDetect.cs b/src/libse/Common/LanguageAutoDetect.cs index 76f5fe503..3d82e29eb 100644 --- a/src/libse/Common/LanguageAutoDetect.cs +++ b/src/libse/Common/LanguageAutoDetect.cs @@ -77,14 +77,18 @@ namespace Nikse.SubtitleEdit.Core.Common private static readonly string[] AutoDetectWordsEnglish = { - "we", "are", "and", "your", "what", "[TW]hat's", "You're", "(any|some|every)thing", "money", "because", "human", "because", "welcome", "really", "something", "confusing", "about", "know", "people", "other", "which", "would", - "these", "could" + "we", "are", "and", "your", "what", "[TW]hat's", "You're", "(any|some|every)thing", "money", "because", "human", "abandon", + "welcome", "really", "something", "confusing", "about", "know", "people", "other", "which", "would", "these", "could", + "tutorial", "I'll", "show", "you", "about", "absolutely", "accept", "wrong", "thought", "training", "treatment", "successful", + "strong", "simply", "situation","soldier", "somebody", "someone","something", "sometimes", "speak","statement", "perhaps", + "piece", "picture", "police", "popular", "population", "suggest", "weapon", "water", "window", "woman", "talk", "security", + "season", "remain", "relationship", "report" ,"user" ,"result", "this", }; private static readonly string[] AutoDetectWordsDanish = { "vi", "han", "og", "jeg", "var", "men", "gider", "bliver", "virkelig", "kommer", "tilbage", "Hej", "længere", "gjorde", "dig", "havde", "[Uu]ndskyld", "arbejder", "vidste", "troede", "stadigvæk", "[Mm]åske", "første", "gik", - "fortælle", "begyndt", "spørgsmål", "pludselig" + "fortælle", "begyndt", "spørgsmål", "pludselig", "være", "fordi", "været", "sidste","også", "løb", "sagde", "hjem", }; private static readonly string[] AutoDetectWordsNorwegian = @@ -796,11 +800,275 @@ namespace Nikse.SubtitleEdit.Core.Common return languageId; } + public class LanguageForAutoDetect + { + public string LanguageCode { get; set; } + public string[] Words { get; set; } + public int WordCount { get; set; } + } + + public static LanguageForAutoDetect[] GetLanguagesWithCount(string text) + { + var list = new LanguageForAutoDetect[] + { + new LanguageForAutoDetect + { + LanguageCode = "en", + Words = AutoDetectWordsEnglish, + }, + new LanguageForAutoDetect + { + LanguageCode = "da", + Words = AutoDetectWordsDanish, + }, + new LanguageForAutoDetect + { + LanguageCode = "no", + Words = AutoDetectWordsNorwegian, + }, + new LanguageForAutoDetect + { + LanguageCode = "sv", + Words = AutoDetectWordsSwedish, + }, + new LanguageForAutoDetect + { + LanguageCode = "es", + Words = AutoDetectWordsSpanish, + }, + new LanguageForAutoDetect + { + LanguageCode = "it", + Words = AutoDetectWordsItalian, + }, + new LanguageForAutoDetect + { + LanguageCode = "fr", + Words = AutoDetectWordsFrench, + }, + new LanguageForAutoDetect + { + LanguageCode = "pt", + Words = AutoDetectWordsPortuguese, + }, + new LanguageForAutoDetect + { + LanguageCode = "de", + Words = AutoDetectWordsGerman, + }, + new LanguageForAutoDetect + { + LanguageCode = "nl", + Words = AutoDetectWordsDutch, + }, + new LanguageForAutoDetect + { + LanguageCode = "pl", + Words = AutoDetectWordsPolish, + }, + new LanguageForAutoDetect + { + LanguageCode = "el", + Words = AutoDetectWordsGreek, + }, + new LanguageForAutoDetect + { + LanguageCode = "ru", + Words = AutoDetectWordsRussian, + }, + new LanguageForAutoDetect + { + LanguageCode = "bg", + Words = AutoDetectWordsBulgarian, + }, + new LanguageForAutoDetect + { + LanguageCode = "uk", + Words = AutoDetectWordsUkrainian, + }, + new LanguageForAutoDetect + { + LanguageCode = "sq", + Words = AutoDetectWordsAlbanian, + }, + new LanguageForAutoDetect + { + LanguageCode = "ar", + Words = AutoDetectWordsArabic, + }, + new LanguageForAutoDetect + { + LanguageCode = "fa", + Words = AutoDetectWordsFarsi, + }, + new LanguageForAutoDetect + { + LanguageCode = "he", + Words = AutoDetectWordsHebrew, + }, + new LanguageForAutoDetect + { + LanguageCode = "vi", + Words = AutoDetectWordsVietnamese, + }, + new LanguageForAutoDetect + { + LanguageCode = "hu", + Words = AutoDetectWordsHungarian, + }, + new LanguageForAutoDetect + { + LanguageCode = "tr", + Words = AutoDetectWordsTurkish, + }, + new LanguageForAutoDetect + { + LanguageCode = "-", + Words = AutoDetectWordsCroatianAndSerbian, + }, + new LanguageForAutoDetect + { + LanguageCode = "hr", + Words = AutoDetectWordsCroatian, + }, + new LanguageForAutoDetect + { + LanguageCode = "sr", + Words = AutoDetectWordsSerbian, + }, + new LanguageForAutoDetect + { + LanguageCode = "sr", + Words = AutoDetectWordsSerbianCyrillic, + }, + new LanguageForAutoDetect + { + LanguageCode = "sr-cy", + Words = AutoDetectWordsSerbianCyrillicOnly, + }, + new LanguageForAutoDetect + { + LanguageCode = "id", + Words = AutoDetectWordsIndonesian, + }, + new LanguageForAutoDetect + { + LanguageCode = "th", + Words = AutoDetectWordsThai, + }, + new LanguageForAutoDetect + { + LanguageCode = "ko", + Words = AutoDetectWordsKorean, + }, + new LanguageForAutoDetect + { + LanguageCode = "mk", + Words = AutoDetectWordsMacedonian, + }, + new LanguageForAutoDetect + { + LanguageCode = "fi", + Words = AutoDetectWordsFinnish, + }, + new LanguageForAutoDetect + { + LanguageCode = "ro", + Words = AutoDetectWordsRomanian, + }, + new LanguageForAutoDetect + { + LanguageCode = "-", + Words = AutoDetectWordsCzechAndSlovak, + }, + new LanguageForAutoDetect + { + LanguageCode = "cs", + Words = AutoDetectWordsCzech, + }, + new LanguageForAutoDetect + { + LanguageCode = "sk", + Words = AutoDetectWordsSlovak, + }, + new LanguageForAutoDetect + { + LanguageCode = "sl", + Words = AutoDetectWordsSlovenian, + }, + new LanguageForAutoDetect + { + LanguageCode = "is", + Words = AutoDetectWordsIcelandic, + }, + new LanguageForAutoDetect + { + LanguageCode = "lv", + Words = AutoDetectWordsLatvian, + }, + new LanguageForAutoDetect + { + LanguageCode = "lt", + Words = AutoDetectWordsLithuanian, + }, + new LanguageForAutoDetect + { + LanguageCode = "hi", + Words = AutoDetectWordsHindi, + }, + new LanguageForAutoDetect + { + LanguageCode = "ur", + Words = AutoDetectWordsUrdu, + }, + new LanguageForAutoDetect + { + LanguageCode = "si", + Words = AutoDetectWordsSinhalese, + }, + }; + + foreach (var item in list) + { + item.WordCount = GetCount(text, item.Words); + } + + return list; + } + + //TODO: improve... a lot ;) + public static string AutoDetectGoogleLanguageOrNull2(Subtitle subtitle) + { + var s = new Subtitle(subtitle); + s.RemoveEmptyLines(); + var allText = s.GetAllTexts(500000).TrimEnd(); + var languagesAndWords = GetLanguagesWithCount(allText); + var languageAndWordHitsOrdered = languagesAndWords + .Where(p => p.WordCount > 0) + .OrderByDescending(p => p.WordCount); + var languageIdFromWordCount = languageAndWordHitsOrdered + .FirstOrDefault(p => p.LanguageCode != "-")?.LanguageCode; + + var languageIdViaLetters = GetEncodingViaLetter(allText); + + if (languageIdFromWordCount != null) + { + return languageIdFromWordCount; + } + + if (languageIdViaLetters != null) + { + return languageIdViaLetters; + } + + return null; + } + public static string AutoDetectGoogleLanguageOrNull(Subtitle subtitle) { var s = new Subtitle(subtitle); s.RemoveEmptyLines(); - var allText = s.GetAllTexts(500000); + var allText = s.GetAllTexts(500000).TrimEnd(); var languageId = AutoDetectGoogleLanguage(allText, s.Paragraphs.Count / 14); if (string.IsNullOrEmpty(languageId)) { @@ -811,6 +1079,7 @@ namespace Nikse.SubtitleEdit.Core.Common { languageId = null; } + return languageId; } diff --git a/src/ui/Controls/NikseTimeUpDown.cs b/src/ui/Controls/NikseTimeUpDown.cs new file mode 100644 index 000000000..8b0fcb4fe --- /dev/null +++ b/src/ui/Controls/NikseTimeUpDown.cs @@ -0,0 +1,787 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Globalization; +using System.Windows.Forms; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Logic; + +namespace Nikse.SubtitleEdit.Controls +{ + [Category("NikseTimeUpDown"), Description("Subtitle time with better support for color theme")] + public sealed class NikseTimeUpDown : Control + { + // ReSharper disable once InconsistentNaming + public EventHandler TimeCodeChanged; + + public enum TimeMode + { + HHMMSSMS, + HHMMSSFF + } + + private readonly bool _designMode = LicenseManager.UsageMode == LicenseUsageMode.Designtime; + + private const int NumericUpDownValue = 50; + + + private bool _forceHHMMSSFF; + + public bool UseVideoOffset { get; set; } + + private static readonly char[] SplitChars = GetSplitChars(); + + private bool _dirty; + double _initialTotalMilliseconds; + + internal void ForceHHMMSSFF() + { + _forceHHMMSSFF = true; + _maskedTextBox.Mask = "00:00:00:00"; + } + + public void SetAutoWidth() + { + using (var g = Graphics.FromHwnd(IntPtr.Zero)) + { + var widthOfUpDown = 25; + if (Configuration.IsRunningOnLinux) + { + widthOfUpDown += 20; + } + var actualWidth = g.MeasureString("00:00:00:000", Font).Width; + Width = (int)Math.Round(actualWidth + ButtonsWidth + 6); + } + } + + public TimeMode Mode + { + get + { + if (_forceHHMMSSFF || Configuration.Settings?.General.UseTimeFormatHHMMSSFF == true) + { + return TimeMode.HHMMSSFF; + } + + return TimeMode.HHMMSSMS; + } + } + + [Category("NikseTimeUpDown"), Description("Gets or sets the increment value")] + public decimal Increment { get; set; } = 100; + + [Category("NikseTimeUpDown"), Description("Allow arrow keys to set increment/decrement value")] + [DefaultValue(true)] + public bool InterceptArrowKeys { get; set; } + + private Color _buttonForeColor; + private Brush _buttonForeColorBrush; + [Category("NikseTimeUpDown"), Description("Gets or sets the button foreground color"), + RefreshProperties(RefreshProperties.Repaint)] + public Color ButtonForeColor + { + get => _buttonForeColor; + set + { + if (value.A == 0) + { + return; + } + + _buttonForeColor = value; + _buttonForeColorBrush?.Dispose(); + _buttonForeColorBrush = new SolidBrush(_buttonForeColor); + Invalidate(); + } + } + + private Color _buttonForeColorOver; + private Brush _buttonForeColorOverBrush; + [Category("NikseTimeUpDown"), Description("Gets or sets the button foreground mouse over color"), RefreshProperties(RefreshProperties.Repaint)] + public Color ButtonForeColorOver + { + get => _buttonForeColorOver; + + set + { + if (value.A == 0) + { + return; + } + + _buttonForeColorOver = value; + _buttonForeColorOverBrush?.Dispose(); + _buttonForeColorOverBrush = new SolidBrush(_buttonForeColorOver); + Invalidate(); + } + } + + private Color _buttonForeColorDown; + private Brush _buttonForeColorDownBrush; + [Category("NikseTimeUpDown"), Description("Gets or sets the button foreground mouse down color"), RefreshProperties(RefreshProperties.Repaint)] + public Color ButtonForeColorDown + { + get => _buttonForeColorDown; + + set + { + if (value.A == 0) + { + return; + } + + _buttonForeColorDown = value; + _buttonForeColorDownBrush?.Dispose(); + _buttonForeColorDownBrush = new SolidBrush(_buttonForeColorDown); + Invalidate(); + } + } + + private Color _borderColor; + [Category("NikseTimeUpDown"), Description("Gets or sets the border color"), RefreshProperties(RefreshProperties.Repaint)] + public Color BorderColor + { + get => _borderColor; + + set + { + if (value.A == 0) + { + return; + } + + _borderColor = value; + Invalidate(); + } + } + + private Color _backColorDisabled; + [Category("NikseTimeUpDown"), Description("Gets or sets the button foreground color"), + RefreshProperties(RefreshProperties.Repaint)] + public Color BackColorDisabled + { + get => _backColorDisabled; + set + { + if (value.A == 0) + { + return; + } + + _backColorDisabled = value; + Invalidate(); + } + } + + private Color _borderColorDisabled; + [Category("NikseTimeUpDown"), Description("Gets or sets the disabled border color"), RefreshProperties(RefreshProperties.Repaint)] + public Color BorderColorDisabled + { + get => _borderColorDisabled; + + set + { + if (value.A == 0) + { + return; + } + + _borderColorDisabled = value; + Invalidate(); + } + } + + private readonly MaskedTextBox _maskedTextBox; + + public NikseTimeUpDown() + { + _maskedTextBox = new MaskedTextBox(); + _maskedTextBox.Font = UiUtil.GetDefaultFont(); + _maskedTextBox.KeyPress += TextBox_KeyPress; + _maskedTextBox.KeyDown += (sender, e) => + { + if (InterceptArrowKeys && e.KeyCode == Keys.Down) + { + AddValue(-Increment); + e.Handled = true; + } + else if (InterceptArrowKeys && e.KeyCode == Keys.Up) + { + AddValue(Increment); + e.Handled = true; + } + else if (e.KeyData == Keys.Enter) + { + TimeCodeChanged?.Invoke(this, e); + e.SuppressKeyPress = true; + } + else if (e.KeyData != (Keys.Tab | Keys.Shift) && + e.KeyData != Keys.Tab && + e.KeyData != Keys.Left && + e.KeyData != Keys.Right) + { + _dirty = true; + } + }; + _maskedTextBox.LostFocus += (sender, args) => Invalidate(); + _maskedTextBox.GotFocus += (sender, args) => Invalidate(); + _maskedTextBox.MouseDown += (sender, e) => + { + if (e.Button == MouseButtons.Right) + { + _dirty = true; + } + }; + _maskedTextBox.BorderStyle = BorderStyle.None; + + Controls.Add(_maskedTextBox); + BackColor = new TextBox().BackColor; + ButtonForeColor = DefaultForeColor; + ButtonForeColorOver = Color.FromArgb(0, 120, 215); + ButtonForeColorDown = Color.Orange; + BorderColor = Color.FromArgb(171, 173, 179); + BorderColorDisabled = Color.FromArgb(120, 120, 120); + BackColorDisabled = Color.FromArgb(240, 240, 240); + DoubleBuffered = true; + InterceptArrowKeys = true; + Increment = 100; + + _repeatTimer = new Timer(); + _repeatTimer.Tick += (sender, args) => + { + if (_repeatTimerArrowUp) + { + AddValue(Increment); + } + else + { + AddValue(-Increment); + } + + _repeatCount++; + _repeatTimer.Interval = _repeatCount < 8 ? 75 : 10; + }; + + LostFocus += (sender, args) => _repeatTimer.Stop(); + + _maskedTextBox.InsertKeyMode = InsertKeyMode.Overwrite; + _maskedTextBox.LostFocus += (sender, args) => + { + AddValue(0); + }; + } + + public MaskedTextBox MaskedTextBox => _maskedTextBox; + + public void SetTotalMilliseconds(double milliseconds) + { + _dirty = false; + _initialTotalMilliseconds = milliseconds; + if (UseVideoOffset) + { + milliseconds += Configuration.Settings.General.CurrentVideoOffsetInMs; + } + if (Mode == TimeMode.HHMMSSMS) + { + _maskedTextBox.Mask = GetMask(milliseconds); + _maskedTextBox.Text = new TimeCode(milliseconds).ToString(); + } + else + { + var tc = new TimeCode(milliseconds); + _maskedTextBox.Mask = GetMaskFrames(milliseconds); + _maskedTextBox.Text = tc.ToString().Substring(0, 9) + $"{Core.SubtitleFormats.SubtitleFormat.MillisecondsToFrames(tc.Milliseconds):00}"; + } + _dirty = false; + } + + public double? GetTotalMilliseconds() + { + return _dirty ? TimeCode?.TotalMilliseconds : _initialTotalMilliseconds; + } + + public TimeCode TimeCode + { + get + { + if (_designMode) + { + return new TimeCode(); + } + + if (string.IsNullOrWhiteSpace(_maskedTextBox.Text.RemoveChar('.').Replace(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, string.Empty).RemoveChar(',', ':'))) + { + return new TimeCode(TimeCode.MaxTimeTotalMilliseconds); + } + + if (!_dirty) + { + return new TimeCode(_initialTotalMilliseconds); + } + + string startTime = _maskedTextBox.Text; + bool isNegative = startTime.StartsWith('-'); + startTime = startTime.TrimStart('-').Replace(' ', '0'); + if (Mode == TimeMode.HHMMSSMS) + { + if (startTime.EndsWith(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, StringComparison.Ordinal)) + { + startTime += "000"; + } + + var times = startTime.Split(SplitChars, StringSplitOptions.RemoveEmptyEntries); + + if (times.Length == 4) + { + int.TryParse(times[0], out var hours); + + int.TryParse(times[1], out var minutes); + if (minutes > 59) + { + minutes = 59; + } + + int.TryParse(times[2], out var seconds); + if (seconds > 59) + { + seconds = 59; + } + + int.TryParse(times[3].PadRight(3, '0'), out var milliseconds); + var tc = new TimeCode(hours, minutes, seconds, milliseconds); + + if (UseVideoOffset) + { + tc.TotalMilliseconds -= Configuration.Settings.General.CurrentVideoOffsetInMs; + } + + if (isNegative) + { + tc.TotalMilliseconds *= -1; + } + + return tc; + } + } + else + { + if (startTime.EndsWith(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, StringComparison.Ordinal) || startTime.EndsWith(':')) + { + startTime += "00"; + } + + string[] times = startTime.Split(SplitChars, StringSplitOptions.RemoveEmptyEntries); + + if (times.Length == 4) + { + int.TryParse(times[0], out var hours); + + int.TryParse(times[1], out var minutes); + + int.TryParse(times[2], out var seconds); + + if (int.TryParse(times[3], out var milliseconds)) + { + milliseconds = Core.SubtitleFormats.SubtitleFormat.FramesToMillisecondsMax999(milliseconds); + } + + var tc = new TimeCode(hours, minutes, seconds, milliseconds); + + if (UseVideoOffset) + { + tc.TotalMilliseconds -= Configuration.Settings.General.CurrentVideoOffsetInMs; + } + + if (isNegative) + { + tc.TotalMilliseconds *= -1; + } + + return tc; + } + } + return null; + } + set + { + if (_designMode) + { + return; + } + + if (value != null) + { + _dirty = false; + _initialTotalMilliseconds = value.TotalMilliseconds; + } + + if (value == null || value.TotalMilliseconds >= TimeCode.MaxTimeTotalMilliseconds - 0.1) + { + _maskedTextBox.Text = string.Empty; + return; + } + + var v = new TimeCode(value.TotalMilliseconds); + if (UseVideoOffset) + { + v.TotalMilliseconds += Configuration.Settings.General.CurrentVideoOffsetInMs; + } + + if (Mode == TimeMode.HHMMSSMS) + { + _maskedTextBox.Mask = GetMask(v.TotalMilliseconds); + _maskedTextBox.Text = v.ToString(); + } + else + { + _maskedTextBox.Mask = GetMaskFrames(v.TotalMilliseconds); + _maskedTextBox.Text = v.ToHHMMSSFF(); + } + } + } + + private static string GetMask(double val) => val >= 0 ? "00:00:00.000" : "-00:00:00.000"; + + private static string GetMaskFrames(double val) => val >= 0 ? "00:00:00:00" : "-00:00:00:00"; + + public void Theme() + { + var enabled = Enabled; + Enabled = true; + if (Configuration.Settings.General.UseDarkTheme) + { + BackColor = DarkTheme.BackColor; + MaskedTextBox.BackColor = DarkTheme.BackColor; + BackColor = DarkTheme.BackColor; + } + else + { + BackColor = DefaultBackColor; + using (var tb = new TextBox()) + { + MaskedTextBox.BackColor = tb.BackColor; + BackColor = tb.BackColor; + } + } + + Enabled = enabled; + } + + /// + /// Allow only digits, Enter and Backspace key. + /// + private void TextBox_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == (char)Keys.Enter) + { + AddValue(0); + e.Handled = false; + } + else if (char.IsDigit(e.KeyChar) || e.KeyChar == (char)Keys.Back) + { + e.Handled = false; + } + else + { + e.Handled = true; + } + } + + /// + /// Increment or decrement the TextBox value. + /// + /// Value to increment/decrement + private void AddValue(decimal value) + { + _dirty = true; + var milliseconds = GetTotalMilliseconds(); + if (milliseconds.HasValue) + { + if (milliseconds.Value >= TimeCode.MaxTimeTotalMilliseconds - 0.1) + { + milliseconds = 0; + } + + if (Mode == TimeMode.HHMMSSMS) + { + SetTotalMilliseconds(milliseconds.Value + (double)value); + } + else + { + if (value > NumericUpDownValue) + { + SetTotalMilliseconds(milliseconds.Value + Core.SubtitleFormats.SubtitleFormat.FramesToMilliseconds(1)); + } + else if (value < NumericUpDownValue) + { + SetTotalMilliseconds(milliseconds.Value - Core.SubtitleFormats.SubtitleFormat.FramesToMilliseconds(1)); + } + } + TimeCodeChanged?.Invoke(this, null); + } + } + + private bool _buttonUpActive; + private bool _buttonDownActive; + + private bool _buttonLeftIsDown; + + private int _mouseX; + private int _mouseY; + + private readonly Timer _repeatTimer; + private bool _repeatTimerArrowUp; + private int _repeatCount; + + protected override void OnMouseEnter(EventArgs e) + { + _buttonUpActive = false; + _buttonDownActive = false; + base.OnMouseEnter(e); + Invalidate(); + } + + protected override void OnMouseLeave(EventArgs e) + { + _buttonUpActive = false; + _buttonDownActive = false; + base.OnMouseLeave(e); + Invalidate(); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + _buttonLeftIsDown = true; + + if (_buttonUpActive) + { + AddValue(Increment); + _repeatTimerArrowUp = true; + _repeatTimer.Interval = 300; + _repeatCount = 0; + _repeatTimer.Start(); + } + else if (_buttonDownActive) + { + AddValue(-Increment); + _repeatTimerArrowUp = false; + _repeatTimer.Interval = 300; + _repeatCount = 0; + _repeatTimer.Start(); + } + + Invalidate(); + } + base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + _repeatTimer.Stop(); + + if (_buttonLeftIsDown) + { + _buttonLeftIsDown = false; + Invalidate(); + } + base.OnMouseDown(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + var left = RightToLeft == RightToLeft.Yes ? 0 : Width - ButtonsWidth; + var right = RightToLeft == RightToLeft.Yes ? ButtonsWidth : Width; + var height = Height / 2 - 3; + const int top = 2; + + _mouseX = e.X; + _mouseY = e.Y; + + if (_mouseX >= left && _mouseX <= right) + { + if (_mouseY > top + height) + { + if (!_buttonDownActive) + { + _buttonUpActive = false; + _buttonDownActive = true; + Invalidate(); + } + } + else + { + if (!_buttonUpActive) + { + _buttonUpActive = true; + _buttonDownActive = false; + Invalidate(); + } + } + } + else + { + if (_buttonUpActive || _buttonDownActive) + { + _buttonUpActive = false; + _buttonDownActive = false; + Invalidate(); + } + _repeatTimer.Stop(); + } + + base.OnMouseMove(e); + } + + private const int ButtonsWidth = 13; + + protected override void OnPaint(PaintEventArgs e) + { + e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + + _maskedTextBox.BackColor = BackColor; + _maskedTextBox.ForeColor = ButtonForeColor; + _maskedTextBox.Top = 2; + _maskedTextBox.Left = RightToLeft == RightToLeft.Yes ? ButtonsWidth : 3; + _maskedTextBox.Height = Height - 4; + _maskedTextBox.Width = Width - ButtonsWidth - 3; + _maskedTextBox.Invalidate(); + + if (!Enabled) + { + DrawDisabled(e); + return; + } + + base.OnPaint(e); + using (var pen = _maskedTextBox.Focused ? new Pen(_buttonForeColorOver, 1f) : new Pen(BorderColor, 1f)) + { + var borderRectangle = new Rectangle(e.ClipRectangle.X, e.ClipRectangle.Y, e.ClipRectangle.Width - 1, e.ClipRectangle.Height - 1); + e.Graphics.DrawRectangle(pen, borderRectangle); + } + + var brush = _buttonForeColorBrush; + var left = RightToLeft == RightToLeft.Yes ? 3 : Width - ButtonsWidth; + var height = e.ClipRectangle.Height / 2 - 4; + var top = 2; + if (_buttonUpActive) + { + brush = _buttonLeftIsDown ? _buttonForeColorDownBrush : _buttonForeColorOverBrush; + } + + DrawArrowUp(e, brush, left, top, height); + + if (_buttonDownActive) + { + brush = _buttonLeftIsDown ? _buttonForeColorDownBrush : _buttonForeColorOverBrush; + } + else + { + brush = _buttonForeColorBrush; + } + + top = height + 5; + DrawArrowDown(e, brush, left, top, height); + } + + public override RightToLeft RightToLeft + { + get => base.RightToLeft; + set + { + base.RightToLeft = value; + Application.DoEvents(); + Invalidate(); + } + } + + public override Color ForeColor + { + get => base.ForeColor; + set + { + base.ForeColor = value; + _maskedTextBox.ForeColor = value; + Application.DoEvents(); + Invalidate(); + } + } + + public override Color BackColor + { + get => base.BackColor; + set + { + base.BackColor = value; + _maskedTextBox.BackColor = value; + Application.DoEvents(); + Invalidate(); + } + } + + private static void DrawArrowDown(PaintEventArgs e, Brush brush, int left, int top, int height) + { + e.Graphics.FillPolygon(brush, + new[] + { + new Point(left + 5, top + height), + new Point(left + 0, top + 0), + new Point(left + 10, top + 0) + }); + } + + private static void DrawArrowUp(PaintEventArgs e, Brush brush, int left, int top, int height) + { + e.Graphics.FillPolygon(brush, + new[] + { + new Point(left + 5, top + 0), + new Point(left + 0, top + height), + new Point(left + 10, top + height) + }); + } + + private void DrawDisabled(PaintEventArgs e) + { + using (var brushBg = new SolidBrush(BackColorDisabled)) + { + e.Graphics.FillRectangle(brushBg, e.ClipRectangle); + } + + using (var pen = new Pen(BorderColorDisabled, 1f)) + { + var borderRectangle = new Rectangle(e.ClipRectangle.X, e.ClipRectangle.Y, e.ClipRectangle.Width - 1, e.ClipRectangle.Height - 1); + e.Graphics.DrawRectangle(pen, borderRectangle); + } + + var left = RightToLeft == RightToLeft.Yes ? 3 : e.ClipRectangle.Width - ButtonsWidth; + var height = e.ClipRectangle.Height / 2 - 4; + var top = 2; + using (var brush = new SolidBrush(BorderColorDisabled)) + { + DrawArrowUp(e, brush, left, top, height); + top = height + 5; + DrawArrowDown(e, brush, left, top, height); + } + } + + private static char[] GetSplitChars() + { + var splitChars = new List { ':', ',', '.' }; + string cultureSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + if (cultureSeparator.Length == 1) + { + var ch = Convert.ToChar(cultureSeparator); + if (!splitChars.Contains(ch)) + { + splitChars.Add(ch); + } + } + return splitChars.ToArray(); + } + + } +} diff --git a/src/ui/Controls/NikseUpDown.cs b/src/ui/Controls/NikseUpDown.cs new file mode 100644 index 000000000..683d66dd2 --- /dev/null +++ b/src/ui/Controls/NikseUpDown.cs @@ -0,0 +1,615 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace Nikse.SubtitleEdit.Controls +{ + [Category("NikseUpDown"), Description("Numeric Up/Down with better support for color theme")] + public sealed class NikseUpDown : Control + { + // ReSharper disable once InconsistentNaming + public event EventHandler ValueChanged; + + private decimal _value; + + [Category("NikseUpDown"), Description("Gets or sets the default value in textBox"), RefreshProperties(RefreshProperties.Repaint)] + public decimal Value + { + get => _value; + set + { + if (DecimalPlaces == 0) + { + _value = value; + } + else if (DecimalPlaces > 0) + { + _value = Math.Round(value, DecimalPlaces); + } + + Invalidate(); + } + } + + private int _decimalPlaces; + [Category("NikseUpDown"), Description("Gets or sets the decimal places (max 4)")] + public int DecimalPlaces + { + get => _decimalPlaces; + set + { + if (value <= 0) + { + _decimalPlaces = 0; + } + else if (value > 3) + { + _decimalPlaces = 4; + } + else + { + _decimalPlaces = value; + } + + Invalidate(); + } + } + + private bool _thousandsSeparator; + + [Category("NikseUpDown"), Description("Gets or sets the thousand seperator")] + public bool ThousandsSeparator + { + get => _thousandsSeparator; + set + { + _thousandsSeparator = value; + Invalidate(); + } + } + + [Category("NikseUpDown"), Description("Gets or sets the increment value")] + public decimal Increment { get; set; } = 1; + + [Category("NikseUpDown"), Description("Gets or sets the Maximum value (max 25 significant digits)")] + public decimal Maximum { get; set; } = 100; + + [Category("NikseUpDown"), Description("Gets or sets the Minimum value")] + public decimal Minimum { get; set; } = 0; + + [Category("NikseUpDown"), Description("Allow arrow keys to set increment/decrement value")] + [DefaultValue(true)] + public bool InterceptArrowKeys { get; set; } + + private Color _buttonForeColor; + private Brush _buttonForeColorBrush; + [Category("NikseUpDown"), Description("Gets or sets the button foreground color"), + RefreshProperties(RefreshProperties.Repaint)] + public Color ButtonForeColor + { + get => _buttonForeColor; + set + { + if (value.A == 0) + { + return; + } + + _buttonForeColor = value; + _buttonForeColorBrush?.Dispose(); + _buttonForeColorBrush = new SolidBrush(_buttonForeColor); + Invalidate(); + } + } + + private Color _buttonForeColorOver; + private Brush _buttonForeColorOverBrush; + [Category("NikseUpDown"), Description("Gets or sets the button foreground mouse over color"), RefreshProperties(RefreshProperties.Repaint)] + public Color ButtonForeColorOver + { + get => _buttonForeColorOver; + + set + { + if (value.A == 0) + { + return; + } + + _buttonForeColorOver = value; + _buttonForeColorOverBrush?.Dispose(); + _buttonForeColorOverBrush = new SolidBrush(_buttonForeColorOver); + Invalidate(); + } + } + + private Color _buttonForeColorDown; + private Brush _buttonForeColorDownBrush; + [Category("NikseUpDown"), Description("Gets or sets the button foreground mouse down color"), RefreshProperties(RefreshProperties.Repaint)] + public Color ButtonForeColorDown + { + get => _buttonForeColorDown; + + set + { + if (value.A == 0) + { + return; + } + + _buttonForeColorDown = value; + _buttonForeColorDownBrush?.Dispose(); + _buttonForeColorDownBrush = new SolidBrush(_buttonForeColorDown); + Invalidate(); + } + } + + private Color _borderColor; + [Category("NikseUpDown"), Description("Gets or sets the border color"), RefreshProperties(RefreshProperties.Repaint)] + public Color BorderColor + { + get => _borderColor; + + set + { + if (value.A == 0) + { + return; + } + + _borderColor = value; + Invalidate(); + } + } + + private Color _backColorDisabled; + [Category("NikseUpDown"), Description("Gets or sets the button foreground color"), + RefreshProperties(RefreshProperties.Repaint)] + public Color BackColorDisabled + { + get => _backColorDisabled; + set + { + if (value.A == 0) + { + return; + } + + _backColorDisabled = value; + Invalidate(); + } + } + + private Color _borderColorDisabled; + [Category("NikseUpDown"), Description("Gets or sets the disabled border color"), RefreshProperties(RefreshProperties.Repaint)] + public Color BorderColorDisabled + { + get => _borderColorDisabled; + + set + { + if (value.A == 0) + { + return; + } + + _borderColorDisabled = value; + Invalidate(); + } + } + + private readonly TextBox _textBox; + + public NikseUpDown() + { + _textBox = new TextBox(); + _textBox.KeyPress += TextBox_KeyPress; + _textBox.KeyDown += (sender, e) => + { + if (InterceptArrowKeys && e.KeyCode == Keys.Down) + { + AddValue(-Increment); + e.Handled = true; + } + else if (InterceptArrowKeys && e.KeyCode == Keys.Up) + { + AddValue(Increment); + e.Handled = true; + } + }; + _textBox.LostFocus += (sender, args) => Invalidate(); + _textBox.GotFocus += (sender, args) => Invalidate(); + _textBox.TextChanged += _textBox_TextChanged; + _textBox.BorderStyle = BorderStyle.None; + + Controls.Add(_textBox); + BackColor = new TextBox().BackColor; + ButtonForeColor = DefaultForeColor; + ButtonForeColorOver = Color.FromArgb(0, 120, 215); + ButtonForeColorDown = Color.Orange; + BorderColor = Color.FromArgb(171, 173, 179); + BorderColorDisabled = Color.FromArgb(120, 120, 120); + BackColorDisabled = Color.FromArgb(240, 240, 240); + DoubleBuffered = true; + InterceptArrowKeys = true; + + _repeatTimer = new Timer(); + _repeatTimer.Tick += (sender, args) => + { + if (_repeatTimerArrowUp) + { + AddValue(Increment); + } + else + { + AddValue(-Increment); + } + + _repeatCount++; + _repeatTimer.Interval = _repeatCount < 8 ? 75 : 10; + }; + + LostFocus += (sender, args) => _repeatTimer.Stop(); + } + + private void _textBox_TextChanged(object sender, EventArgs e) + { + if (decimal.TryParse(_textBox.Text, out var result)) + { + Value = Math.Round(result, DecimalPlaces); + + if (Value < Minimum) + { + Value = Minimum; + Invalidate(); + } + else if (Value > Maximum) + { + Value = Maximum; + Invalidate(); + } + } + } + + /// + /// Allow only digits, Enter and Backspace key. + /// + private void TextBox_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == (char)Keys.Enter) + { + AddValue(0); + e.Handled = false; + } + else if (char.IsDigit(e.KeyChar) || e.KeyChar == (char)Keys.Back) + { + e.Handled = false; + } + else if (e.KeyChar == '.' || e.KeyChar == ',') + { + e.Handled = !(DecimalPlaces > 0); + } + else + { + e.Handled = true; + } + } + + /// + /// Increment or decrement the TextBox value. + /// + /// Value to increment/decrement + private void AddValue(decimal value) + { + if (string.IsNullOrEmpty(_textBox.Text)) + { + Value = 0 >= Minimum && 0 <= Maximum ? 0 : Minimum; + SetText(); + ValueChanged?.Invoke(this, null); + return; + } + + if (_textBox.TextLength > 25) + { + Value = Maximum; + SetText(); + ValueChanged?.Invoke(this, null); + return; + } + + if (decimal.TryParse(_textBox.Text, out var result)) + { + Value = Math.Round(result + value, DecimalPlaces); + + if (Value < Minimum) + { + Value = Minimum; + } + else if (Value > Maximum) + { + Value = Maximum; + } + else + { + SetText(); + ValueChanged?.Invoke(this, null); + return; + } + } + + SetText(); + ValueChanged?.Invoke(this, null); + } + + private bool _buttonUpActive; + private bool _buttonDownActive; + + private bool _buttonLeftIsDown; + + private int _mouseX; + private int _mouseY; + + private readonly Timer _repeatTimer; + private bool _repeatTimerArrowUp; + private int _repeatCount; + + protected override void OnMouseEnter(EventArgs e) + { + _buttonUpActive = false; + _buttonDownActive = false; + base.OnMouseEnter(e); + Invalidate(); + } + + protected override void OnMouseLeave(EventArgs e) + { + _buttonUpActive = false; + _buttonDownActive = false; + base.OnMouseLeave(e); + Invalidate(); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + _buttonLeftIsDown = true; + + if (_buttonUpActive) + { + AddValue(Increment); + _repeatTimerArrowUp = true; + _repeatTimer.Interval = 300; + _repeatCount = 0; + _repeatTimer.Start(); + } + else if (_buttonDownActive) + { + AddValue(-Increment); + _repeatTimerArrowUp = false; + _repeatTimer.Interval = 300; + _repeatCount = 0; + _repeatTimer.Start(); + } + + Invalidate(); + } + base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + _repeatTimer.Stop(); + + if (_buttonLeftIsDown) + { + _buttonLeftIsDown = false; + Invalidate(); + } + base.OnMouseDown(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + var left = RightToLeft == RightToLeft.Yes ? 0 : Width - ButtonsWidth; + var right = RightToLeft == RightToLeft.Yes ? ButtonsWidth : Width; + var height = Height / 2 - 3; + const int top = 2; + + _mouseX = e.X; + _mouseY = e.Y; + + if (_mouseX >= left && _mouseX <= right) + { + if (_mouseY > top + height) + { + if (!_buttonDownActive) + { + _buttonUpActive = false; + _buttonDownActive = true; + Invalidate(); + } + } + else + { + if (!_buttonUpActive) + { + _buttonUpActive = true; + _buttonDownActive = false; + Invalidate(); + } + } + } + else + { + if (_buttonUpActive || _buttonDownActive) + { + _buttonUpActive = false; + _buttonDownActive = false; + Invalidate(); + } + _repeatTimer.Stop(); + } + + base.OnMouseMove(e); + } + + private const int ButtonsWidth = 13; + + protected override void OnPaint(PaintEventArgs e) + { + e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + + _textBox.BackColor = BackColor; + _textBox.ForeColor = ButtonForeColor; + _textBox.Top = 2; + _textBox.Left = RightToLeft == RightToLeft.Yes ? ButtonsWidth : 3; + _textBox.Height = Height - 4; + _textBox.Width = Width - ButtonsWidth - 3; + SetText(); + + if (!Enabled) + { + DrawDisabled(e); + return; + } + + base.OnPaint(e); + using (var pen = _textBox.Focused ? new Pen(_buttonForeColorOver, 1f) : new Pen(BorderColor, 1f)) + { + var borderRectangle = new Rectangle(e.ClipRectangle.X, e.ClipRectangle.Y, e.ClipRectangle.Width - 1, e.ClipRectangle.Height - 1); + e.Graphics.DrawRectangle(pen, borderRectangle); + } + + var brush = _buttonForeColorBrush; + var left = RightToLeft == RightToLeft.Yes ? 3 : Width - ButtonsWidth; + var height = e.ClipRectangle.Height / 2 - 4; + var top = 2; + if (_buttonUpActive) + { + brush = _buttonLeftIsDown ? _buttonForeColorDownBrush : _buttonForeColorOverBrush; + } + + DrawArrowUp(e, brush, left, top, height); + + if (_buttonDownActive) + { + brush = _buttonLeftIsDown ? _buttonForeColorDownBrush : _buttonForeColorOverBrush; + } + else + { + brush = _buttonForeColorBrush; + } + + top = height + 5; + DrawArrowDown(e, brush, left, top, height); + } + + public override RightToLeft RightToLeft + { + get => base.RightToLeft; + set + { + base.RightToLeft = value; + Application.DoEvents(); + Invalidate(); + } + } + + public override Color ForeColor + { + get => base.ForeColor; + set + { + base.ForeColor = value; + _textBox.ForeColor = value; + Application.DoEvents(); + Invalidate(); + } + } + + public override Color BackColor + { + get => base.BackColor; + set + { + base.BackColor = value; + _textBox.BackColor = value; + Application.DoEvents(); + Invalidate(); + } + } + + private void SetText() + { + if (DecimalPlaces <= 0) + { + _textBox.Text = ThousandsSeparator ? $"{Value:#,###,##0}" : $"{Value:########0}"; + } + else if (DecimalPlaces == 1) + { + _textBox.Text = ThousandsSeparator ? $"{Value:#,###,##0.0}" : $"{Value:########0.0}"; + } + else if (DecimalPlaces == 2) + { + _textBox.Text = ThousandsSeparator ? $"{Value:#,###,##0.00}" : $"{Value:#########0.00}"; + } + else if (DecimalPlaces == 3) + { + _textBox.Text = ThousandsSeparator ? $"{Value:#,###,##0.000}" : $"{Value:#########0.000}"; + } + else + { + _textBox.Text = ThousandsSeparator ? $"{Value:#,###,##0.0000}" : $"{Value:#########0.0000}"; + } + } + + private static void DrawArrowDown(PaintEventArgs e, Brush brush, int left, int top, int height) + { + e.Graphics.FillPolygon(brush, + new[] + { + new Point(left + 5, top + height), + new Point(left + 0, top + 0), + new Point(left + 10, top + 0) + }); + } + + private static void DrawArrowUp(PaintEventArgs e, Brush brush, int left, int top, int height) + { + e.Graphics.FillPolygon(brush, + new[] + { + new Point(left + 5, top + 0), + new Point(left + 0, top + height), + new Point(left + 10, top + height) + }); + } + + private void DrawDisabled(PaintEventArgs e) + { + using (var brushBg = new SolidBrush(BackColorDisabled)) + { + e.Graphics.FillRectangle(brushBg, e.ClipRectangle); + } + + using (var pen = new Pen(BorderColorDisabled, 1f)) + { + var borderRectangle = new Rectangle(e.ClipRectangle.X, e.ClipRectangle.Y, e.ClipRectangle.Width - 1, e.ClipRectangle.Height - 1); + e.Graphics.DrawRectangle(pen, borderRectangle); + } + + var left = RightToLeft == RightToLeft.Yes ? 3 : e.ClipRectangle.Width - ButtonsWidth; + var height = e.ClipRectangle.Height / 2 - 4; + var top = 2; + using (var brush = new SolidBrush(BorderColorDisabled)) + { + DrawArrowUp(e, brush, left, top, height); + top = height + 5; + DrawArrowDown(e, brush, left, top, height); + } + } + } +} diff --git a/src/ui/Forms/Main.Designer.cs b/src/ui/Forms/Main.Designer.cs index bbf834ce2..141c9f893 100644 --- a/src/ui/Forms/Main.Designer.cs +++ b/src/ui/Forms/Main.Designer.cs @@ -542,8 +542,8 @@ namespace Nikse.SubtitleEdit.Forms this.labelTextLineTotal = new System.Windows.Forms.Label(); this.labelCharactersPerSecond = new System.Windows.Forms.Label(); this.buttonUnBreak = new System.Windows.Forms.Button(); - this.timeUpDownStartTime = new Nikse.SubtitleEdit.Controls.TimeUpDown(); - this.numericUpDownDuration = new System.Windows.Forms.NumericUpDown(); + this.timeUpDownStartTime = new Nikse.SubtitleEdit.Controls.NikseTimeUpDown(); + this.numericUpDownDuration = new Nikse.SubtitleEdit.Controls.NikseUpDown(); this.buttonPrevious = new System.Windows.Forms.Button(); this.buttonNext = new System.Windows.Forms.Button(); this.labelStartTime = new System.Windows.Forms.Label(); @@ -616,7 +616,6 @@ namespace Nikse.SubtitleEdit.Forms this.panelBookmark.SuspendLayout(); this.contextMenuStripTextBoxListView.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.pictureBoxBookmark)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDownDuration)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.pictureBoxRecord)).BeginInit(); this.contextMenuStripTextBoxSourceView.SuspendLayout(); this.panelVideoPlayer.SuspendLayout(); @@ -5302,15 +5301,24 @@ namespace Nikse.SubtitleEdit.Forms // // timeUpDownStartTime // - this.timeUpDownStartTime.AutoSize = true; - this.timeUpDownStartTime.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; this.timeUpDownStartTime.BackColor = System.Drawing.SystemColors.Control; + this.timeUpDownStartTime.BackColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240))))); + this.timeUpDownStartTime.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); + this.timeUpDownStartTime.BorderColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(120)))), ((int)(((byte)(120))))); + this.timeUpDownStartTime.ButtonForeColor = System.Drawing.SystemColors.ControlText; + this.timeUpDownStartTime.ButtonForeColorDown = System.Drawing.Color.Orange; + this.timeUpDownStartTime.ButtonForeColorOver = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); this.timeUpDownStartTime.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F); this.timeUpDownStartTime.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(155)))), ((int)(((byte)(155)))), ((int)(((byte)(155))))); + this.timeUpDownStartTime.Increment = new decimal(new int[] { + 100, + 0, + 0, + 0}); this.timeUpDownStartTime.Location = new System.Drawing.Point(8, 26); this.timeUpDownStartTime.Margin = new System.Windows.Forms.Padding(4); this.timeUpDownStartTime.Name = "timeUpDownStartTime"; - this.timeUpDownStartTime.Size = new System.Drawing.Size(113, 27); + this.timeUpDownStartTime.Size = new System.Drawing.Size(113, 21); this.timeUpDownStartTime.TabIndex = 0; timeCode3.Hours = 0; timeCode3.Milliseconds = 0; @@ -5324,13 +5332,20 @@ namespace Nikse.SubtitleEdit.Forms // // numericUpDownDuration // + this.numericUpDownDuration.BackColor = System.Drawing.SystemColors.Window; + this.numericUpDownDuration.BackColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240))))); + this.numericUpDownDuration.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); + this.numericUpDownDuration.BorderColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(120)))), ((int)(((byte)(120))))); + this.numericUpDownDuration.ButtonForeColor = System.Drawing.SystemColors.ControlText; + this.numericUpDownDuration.ButtonForeColorDown = System.Drawing.Color.Orange; + this.numericUpDownDuration.ButtonForeColorOver = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); this.numericUpDownDuration.DecimalPlaces = 3; this.numericUpDownDuration.Increment = new decimal(new int[] { 1, 0, 0, 65536}); - this.numericUpDownDuration.Location = new System.Drawing.Point(122, 27); + this.numericUpDownDuration.Location = new System.Drawing.Point(122, 26); this.numericUpDownDuration.Maximum = new decimal(new int[] { 999999999, 0, @@ -5342,8 +5357,14 @@ namespace Nikse.SubtitleEdit.Forms 0, -2147483648}); this.numericUpDownDuration.Name = "numericUpDownDuration"; - this.numericUpDownDuration.Size = new System.Drawing.Size(56, 20); + this.numericUpDownDuration.Size = new System.Drawing.Size(56, 21); this.numericUpDownDuration.TabIndex = 1; + this.numericUpDownDuration.ThousandsSeparator = false; + this.numericUpDownDuration.Value = new decimal(new int[] { + 0, + 0, + 0, + 196608}); this.numericUpDownDuration.ValueChanged += new System.EventHandler(this.NumericUpDownDurationValueChanged); // // buttonPrevious @@ -5733,7 +5754,6 @@ namespace Nikse.SubtitleEdit.Forms this.panelBookmark.PerformLayout(); this.contextMenuStripTextBoxListView.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.pictureBoxBookmark)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDownDuration)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.pictureBoxRecord)).EndInit(); this.contextMenuStripTextBoxSourceView.ResumeLayout(false); this.panelVideoPlayer.ResumeLayout(false); @@ -5816,14 +5836,14 @@ namespace Nikse.SubtitleEdit.Forms private System.Windows.Forms.Button buttonAutoBreak; private System.Windows.Forms.ToolStripMenuItem removeFormattinglToolStripMenuItem; private System.Windows.Forms.Label labelTextLineLengths; - private System.Windows.Forms.NumericUpDown numericUpDownDuration; + private Nikse.SubtitleEdit.Controls.NikseUpDown numericUpDownDuration; private System.Windows.Forms.Label labelStartTimeWarning; private System.Windows.Forms.Button buttonUnBreak; private System.Windows.Forms.ToolStripMenuItem colorToolStripMenuItem; private System.Windows.Forms.Label labelTextLineTotal; private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; private System.Windows.Forms.ToolStripSeparator toolStripSeparator10; - private Nikse.SubtitleEdit.Controls.TimeUpDown timeUpDownStartTime; + private Nikse.SubtitleEdit.Controls.NikseTimeUpDown timeUpDownStartTime; private System.Windows.Forms.ToolStripMenuItem ChangeCasingToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem toolStripMenuItemMergeLines; private System.Windows.Forms.ToolStripMenuItem toolStripMenuItemSortBy; diff --git a/src/ui/Forms/Options/Settings.Designer.cs b/src/ui/Forms/Options/Settings.Designer.cs index 227864071..a9199262b 100644 --- a/src/ui/Forms/Options/Settings.Designer.cs +++ b/src/ui/Forms/Options/Settings.Designer.cs @@ -4689,9 +4689,9 @@ this.groupBoxGraphicsButtons.Controls.Add(this.pictureBoxPreview1); this.groupBoxGraphicsButtons.Controls.Add(this.labelToolbarIconTheme); this.groupBoxGraphicsButtons.Controls.Add(this.comboBoxToolbarIconTheme); - this.groupBoxGraphicsButtons.Location = new System.Drawing.Point(383, 310); + this.groupBoxGraphicsButtons.Location = new System.Drawing.Point(383, 307); this.groupBoxGraphicsButtons.Name = "groupBoxGraphicsButtons"; - this.groupBoxGraphicsButtons.Size = new System.Drawing.Size(461, 109); + this.groupBoxGraphicsButtons.Size = new System.Drawing.Size(461, 114); this.groupBoxGraphicsButtons.TabIndex = 41; this.groupBoxGraphicsButtons.TabStop = false; this.groupBoxGraphicsButtons.Text = "Graphics buttons"; @@ -5414,8 +5414,8 @@ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1092, 574); this.Controls.Add(this.labelUpdateFileTypeAssociationsStatus); - this.Controls.Add(this.panelVideoPlayer); this.Controls.Add(this.panelFont); + this.Controls.Add(this.panelVideoPlayer); this.Controls.Add(this.panelToolBar); this.Controls.Add(this.panelTools); this.Controls.Add(this.panelWaveform); diff --git a/src/ui/Languages/it-IT.xml b/src/ui/Languages/it-IT.xml index 460ce28ed..63127a19a 100644 --- a/src/ui/Languages/it-IT.xml +++ b/src/ui/Languages/it-IT.xml @@ -3,7 +3,7 @@ Subtitle Edit 3.6.13 - Tradotto da NAMP e bovirus - Data traduzione: 13.07.2023 + Tradotto da NAMP e bovirus - Data traduzione: 15.07.2023 it-IT OK @@ -2840,7 +2840,7 @@ Vuoi continuare? Numero tag grassetto: {0} Numero tag sottolineatura: {0} Numero tag per font: {0} - Numero di tag allineamento: {0} + Numero tag allineamento: {0} Lunghezza sottotitolo - minima: {0} Lunghezza sottotitolo - massima: {0} Lunghezza sottotitolo - media: {0} @@ -2848,18 +2848,29 @@ Vuoi continuare? Lunghezza riga singola - minimo: {0} Lunghezza riga singola - massima: {0} Lunghezza riga singola - media: {0} - Larghezza riga singola - minima: {0} pixels - Larghezza riga singola - massima: {0} pixels - Larghezza riga singola - media: {0} pixels + Lunghezza riga singola - superiore al massimo ({0} caratteri): {1} ({2:0.00}%) + Larghezza riga singola - minima: {0} pixel + Larghezza riga singola - massima: {0} pixel + Larghezza riga singola - superiore al massimo ({0} pixel): {1} ({2:0.00}%) + Larghezza riga singola - media: {0} pixel Durata - minima: {0:0.000} secondi Durata - massima: {0:0.000} secondi Durata - media: {0:0.000} secondi + Durata - inferiore al minimo ({0:0.###} sec): {1} ({2:0.00}%) + Durata - superiore al massimo ({0:0.###} sec): {1} ({2:0.00}%) Caratteri/sec - minimuo: {0:0.000} Caratteri/sec - massimo: {0:0.000} Caratteri/sec - media: {0:0.000} + Caratteri/sec - superiore ad ottimale ({0:0.##} cps): {1} ({2:0.00}%) + Caratteri/sec - superiore al massimo ({0:0.##} cps): {1} ({2:0.00}%) + Parole/min - minimo: {0:0.000} + Parole/min - massimo: {0:0.000} + Parole/min - media: {0:0.000} + Words/min - exceeding maximum ({0} wpm): {1} ({2:0.00}%) Gap - minimo: {0:#,##0} ms Gap - massimo: {0:#,##0} ms Gap - media: {0:#,##0.##} ms + Gap - inferiore al minimo ({0:#,##0} ms): {1} ({2:0.00}%) Esporta... diff --git a/src/ui/Languages/ko-KR.xml b/src/ui/Languages/ko-KR.xml index 5ac2e8d00..5542c8ba7 100644 --- a/src/ui/Languages/ko-KR.xml +++ b/src/ui/Languages/ko-KR.xml @@ -2411,6 +2411,7 @@ FFmpeg를 다운로드하고 사용하시겠습니까? 어두운 테마 어두운 테마 사용 목록 보기 그리드 선 표시 + 그래픽 버튼 업데이트 초점에 비디오 위치 설정 비디오 컨트롤 연결 | 해제 @@ -2846,18 +2847,29 @@ FFmpeg를 다운로드하고 사용하시겠습니까? 한 줄의 길이 - 최소: {0} 한 줄의 길이 - 최대: {0} 한 줄의 길이 - 평균: {0} + 한 줄의 길이 - 최대값 초과 ({0} 문자): {1} ({2:0.00}%) 한 줄의 너비 - 최소: {0} 픽셀 한 줄의 너비 - 최대: {0} 픽셀 한 줄의 너비 - 평균: {0} 픽셀 + 한 줄의 너비 - 최대값 초과 ({0} 픽셀): {1} ({2:0.00}%) 표시시간 - 최소: {0:0.000}초 표시시간 - 최대: {0:0.000}초 표시시간 - 평균: {0:0.000}초 + 표시시간 - 최소값 미만 ({0:0.###} 초): {1} ({2:0.00}%) + 표시시간 - 최대값 초과 ({0:0.###} 초): {1} ({2:0.00}%) 문자/초 - 최소: {0:0.000} 문자/초 - 최대: {0:0.000} 문자/초 - 평균: {0:0.000} + 문자/초 - 최적값 초과 ({0:0.##} cps): {1} ({2:0.00}%) + 문자/초 - 최대값 초과 ({0:0.##} cps): {1} ({2:0.00}%) + 단어/분 - 최소: {0:0.000} + 단어/분 - 최대: {0:0.000} + 단어/분 - 평균: {0:0.000} + 단어/분 - 최대값 초과 ({0} wpm): {1} ({2:0.00}%) 간격 - 최소: {0:#,##0} 밀리초 간격 - 최대: {0:#,##0} 밀리초 간격 - 평균: {0:#,##0.##} 밀리초 + 간격 - 최소값 미만 ({0:#,##0} ms): {1} ({2:0.00}%) 내보내기 @@ -3219,4 +3231,9 @@ FFmpeg를 다운로드하고 사용하시겠습니까? WebVTT 스타일 + + Whisper Advanced - 추가 명령줄 인수 + Whisper 명령줄에 대한 추가 매개변수: + 참고: Whisper 구현에 따라 명령줄 매개 변수는 다릅니다! + \ No newline at end of file diff --git a/src/ui/Logic/DarkTheme.cs b/src/ui/Logic/DarkTheme.cs index 613b50f7e..b7a3fe525 100644 --- a/src/ui/Logic/DarkTheme.cs +++ b/src/ui/Logic/DarkTheme.cs @@ -11,8 +11,8 @@ namespace Nikse.SubtitleEdit.Logic { public static class DarkTheme { - internal static readonly Color BackColor = Configuration.Settings.General.DarkThemeBackColor; - internal static readonly Color ForeColor = Configuration.Settings.General.DarkThemeForeColor; + public static Color BackColor => Configuration.Settings.General.DarkThemeBackColor; + public static Color ForeColor => Configuration.Settings.General.DarkThemeForeColor; private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19; private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20; @@ -264,11 +264,16 @@ namespace Nikse.SubtitleEdit.Logic c.BackColor = Control.DefaultBackColor; c.ForeColor = Control.DefaultForeColor; + var newButton = new TextBox(); + var buttonBackColor = newButton.BackColor; + newButton.Dispose(); + if (c is Button b) { b.FlatStyle = FlatStyle.Standard; b.EnabledChanged -= Button_EnabledChanged; b.Paint -= Button_Paint; + b.BackColor = buttonBackColor; } if (c is CheckBox cb) @@ -354,6 +359,21 @@ namespace Nikse.SubtitleEdit.Logic lv.EnabledChanged -= ListView_EnabledChanged; lv.HandleCreated -= ListView_HandleCreated; } + else if (c is NikseUpDown) + { + c.BackColor = buttonBackColor; + c.ForeColor = Control.DefaultForeColor; + } + else if (c is NikseTimeUpDown) + { + c.BackColor = buttonBackColor; + c.ForeColor = Control.DefaultForeColor; + } + + if (c is Button bu) + { + bu.BackColor = buttonBackColor; + } } private static void FixControl(Control c) @@ -451,6 +471,20 @@ namespace Nikse.SubtitleEdit.Logic lv.EnabledChanged += ListView_EnabledChanged; lv.HandleCreated += ListView_HandleCreated; } + else if (c is NikseUpDown ud) + { + ud.BackColor = BackColor; + ud.ForeColor = ForeColor; + ud.ButtonForeColor = ForeColor; + ud.BackColorDisabled = BackColor; + } + else if (c is NikseTimeUpDown tud) + { + tud.BackColor = BackColor; + tud.ForeColor = ForeColor; + tud.ButtonForeColor = ForeColor; + tud.BackColorDisabled = BackColor; + } } private static void Button_EnabledChanged(object sender, EventArgs e) diff --git a/src/ui/SubtitleEdit.csproj b/src/ui/SubtitleEdit.csproj index 1c233df1c..1e2c367a1 100644 --- a/src/ui/SubtitleEdit.csproj +++ b/src/ui/SubtitleEdit.csproj @@ -109,6 +109,12 @@ Component + + Component + + + Component + Component