Browse Source

commit: a bunch of patch

HOME 3 years ago
parent
commit
a01ca4c06d

+ 6 - 9
BeatLyrics.Common/DirProvider.cs

@@ -5,18 +5,15 @@ namespace BeatLyrics.Common
 {
     public static class DirProvider
     {
+        public const string SuffixSet = ".set.txt";
+        public const string SuffixVerify = ".verify.txt";
+
         public static string BeatSaberDir => Environment.GetEnvironmentVariable("BeatSaberDir");
         public static string BeatLyricsDir => Environment.GetEnvironmentVariable("BeatLyricsDir");
 
-        public static string GetLyricDir(string hash)
-        {
-            var dir = Path.Combine(BeatLyricsDir, "custom_level_" + hash);
-            return dir;
-        }
+        public static string GetLyricDir(string hash) => Path.Combine(BeatLyricsDir, "custom_level_" + hash);
 
-        public static string GetDefaultFile(string hash)
-        {
-            return GetLyricDir(hash) + ".default.txt";
-        }
+        public static string GetDefaultFile(string hash) => GetLyricDir(hash) + SuffixSet;
+        public static string GetVerifyFile(string hash) => GetLyricDir(hash) + SuffixVerify;
     }
 }

+ 10 - 1
BeatLyrics.Tool/BeatLyrics.Tool.csproj

@@ -82,6 +82,12 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="DataProvider\BeatMaps\LocalCustomLevelProvider.cs" />
+    <Compile Include="Dialogs\TextDialog.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Dialogs\TextDialog.Designer.cs">
+      <DependentUpon>TextDialog.cs</DependentUpon>
+    </Compile>
     <Compile Include="Models\LyricDetailExt.cs" />
     <Compile Include="Models\LyricFileExt.cs" />
     <Compile Include="Dialogs\TextEditDialog.cs">
@@ -97,7 +103,7 @@
       <DependentUpon>SplitDialog.cs</DependentUpon>
     </Compile>
     <Compile Include="Models\LevelInfo.cs" />
-    <Compile Include="DataProvider\OnlineLyric\LyricProviderBase.cs" />
+    <Compile Include="DataProvider\OnlineLyric\LyricProvider.cs" />
     <Compile Include="DataProvider\OnlineLyric\NeteaseCloudMusicLyricProvider.cs" />
     <Compile Include="UserControls\TimelineEditorUserControl.cs">
       <SubType>UserControl</SubType>
@@ -143,6 +149,9 @@
     <Compile Include="Utils\OggAudioPlayer.cs" />
     <Compile Include="Utils\JapanesePhonetic.cs" />
     <Compile Include="Utils\JapaneseConverter.cs" />
+    <EmbeddedResource Include="Dialogs\TextDialog.resx">
+      <DependentUpon>TextDialog.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Dialogs\TextEditDialog.resx">
       <DependentUpon>TextEditDialog.cs</DependentUpon>
     </EmbeddedResource>

+ 68 - 0
BeatLyrics.Tool/DataProvider/OnlineLyric/LyricProvider.cs

@@ -0,0 +1,68 @@
+using BeatLyrics.Tool.DataProvider.OnlineLyric.Models;
+using BeatLyrics.Tool.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace BeatLyrics.Tool.DataProvider.OnlineLyric
+{
+    public abstract class LyricProvider
+    {
+        private static readonly Regex RegexLrc = new Regex(@"\[(\d{2})\:(\d{2})[\.\:](\d{2,3})\](.*)", RegexOptions.Compiled);
+
+        public abstract LyricSearchResultItem[] Search(string songName, string artist);
+
+        public abstract bool GetDetail(LyricSearchResultItem input);
+
+        public static List<LyricDetailExt> ParseLrc(string input, int duration)
+        {
+            var matches = RegexLrc.Matches(input);
+
+            var ret = new List<LyricDetailExt>();
+
+            foreach (Match match in matches)
+            {
+                var sameList = new List<LyricDetailExt>();
+                var p = match;
+
+                string text;
+                do
+                {
+                    var timeStamp = $"{p.Groups[1]}:{p.Groups[2]},{p.Groups[3].Value.PadLeft(3, '0')}";
+                    text = p.Groups[4].Value;
+
+                    var item = new LyricDetailExt
+                    {
+                        TimeMs = (int)TimeSpan.ParseExact(timeStamp, @"mm\:ss\,fff", null).TotalMilliseconds,
+                        Text = text,
+                    };
+                    ret.Add(item);
+                    sameList.Add(item);
+
+                    p = RegexLrc.Match(text); // handle multi time stamp tag
+                } while (p.Success);
+
+                if (sameList.Count <= 1) continue;
+                foreach (var same in sameList) same.Text = text;
+            }
+
+            ret = ret.GroupBy(p => new { p.TimeMs, p.Text }).Select(q => q.First()).ToList();
+
+            ret = ret.Where(p => false == string.IsNullOrWhiteSpace(p.Text)).OrderBy(p => p.TimeMs).ToList();
+
+            for (var i = 0; i < ret.Count; i++)
+            {
+                var item = ret[i];
+                var nextItem = ret.Count > i + 1 ? ret[i + 1] : null;
+
+                if (nextItem != null) item.DurationMs = nextItem.TimeMs - item.TimeMs;
+                else item.DurationMs = duration - item.TimeMs;
+
+                if (item.DurationMs < 0) item.DurationMs = 1000;
+            }
+
+            return ret;
+        }
+    }
+}

+ 0 - 49
BeatLyrics.Tool/DataProvider/OnlineLyric/LyricProviderBase.cs

@@ -1,49 +0,0 @@
-using BeatLyrics.Tool.DataProvider.OnlineLyric.Models;
-using BeatLyrics.Tool.Models;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.RegularExpressions;
-
-namespace BeatLyrics.Tool.DataProvider.OnlineLyric
-{
-    public abstract class LyricProviderBase
-    {
-        private static readonly Regex RegexLrc = new Regex(@"\[(\d{2})\:(\d{2})[\.\:](\d{2,3})\](.+)", RegexOptions.Compiled);
-
-        public abstract LyricSearchResultItem[] Search(string songName, string artist);
-
-        public abstract void GetDetail(LyricSearchResultItem input);
-
-        protected static List<LyricDetailExt> ParseLrc(string input, int duration)
-        {
-            var matches = RegexLrc.Matches(input);
-
-            var ret = matches.Cast<Match>().Select(p =>
-            {
-                var timeStamp = $"{p.Groups[1]}:{p.Groups[2]},{p.Groups[3].Value.PadLeft(3, '0')}";
-
-                var text = p.Groups[4].Value;
-
-                return new LyricDetailExt
-                {
-                    TimeMs = (int)TimeSpan.ParseExact(timeStamp, @"mm\:ss\,fff", null).TotalMilliseconds,
-                    Text = text,
-                };
-            }).ToList();
-
-            for (var i = 0; i < ret.Count; i++)
-            {
-                var item = ret[i];
-                var nextItem = ret.Count > i + 1 ? ret[i + 1] : null;
-
-                if (nextItem != null) item.DurationMs = nextItem.TimeMs - item.TimeMs;
-                else item.DurationMs = duration - item.TimeMs;
-
-                if (item.DurationMs < 0) item.DurationMs = 1000;
-            }
-
-            return ret;
-        }
-    }
-}

+ 3 - 0
BeatLyrics.Tool/DataProvider/OnlineLyric/Models/LyricSearchResultItem.cs

@@ -34,6 +34,9 @@ namespace BeatLyrics.Tool.DataProvider.OnlineLyric.Models
         public List<LyricDetailExt> Details { get; set; }
         public List<LyricDetailExt> DetailsTranslated { get; set; }
 
+        public string DetailsText { get; set; }
+        public string DetailsTranslatedText { get; set; }
+
         public override string ToString()
         {
             return $"{Name} - {Artists} [{DurationText}] #{Id} {Remarks}";

+ 17 - 4
BeatLyrics.Tool/DataProvider/OnlineLyric/NeteaseCloudMusicLyricProvider.cs

@@ -1,11 +1,12 @@
 using System;
 using System.Linq;
 using BeatLyrics.Tool.DataProvider.OnlineLyric.Models;
+using BrightIdeasSoftware;
 using Newtonsoft.Json;
 
 namespace BeatLyrics.Tool.DataProvider.OnlineLyric
 {
-    public class NeteaseCloudMusicLyricProvider : LyricProviderBase
+    public class NeteaseCloudMusicLyricProvider : LyricProvider
     {
         public override LyricSearchResultItem[] Search(string songName, string artist)
         {
@@ -48,7 +49,7 @@ namespace BeatLyrics.Tool.DataProvider.OnlineLyric
             return arr;
         }
 
-        public override void GetDetail(LyricSearchResultItem input)
+        public override bool GetDetail(LyricSearchResultItem input)
         {
             if (input.ProviderName != nameof(NeteaseCloudMusicLyricProvider)) throw new ArgumentException("Provider no matched");
 
@@ -72,9 +73,21 @@ namespace BeatLyrics.Tool.DataProvider.OnlineLyric
                 }
             });
 
-            if (obj.lrc != null) input.Details = ParseLrc(obj.lrc.lyric, input.Duration);
-            if (obj.tlyric != null) input.DetailsTranslated = ParseLrc(obj.tlyric.lyric, input.Duration);
+            if (obj.lrc?.lyric != null)
+            {
+                input.DetailsText = obj.lrc.lyric;
+                input.Details = ParseLrc(obj.lrc.lyric, input.Duration);
+            }
+
+            if (obj.tlyric?.lyric != null)
+            {
+                input.DetailsTranslatedText = obj.tlyric.lyric;
+                input.DetailsTranslated = ParseLrc(obj.tlyric.lyric, input.Duration);
+            }
+
+            if (obj.lrc == null) return false;
             input.IsDetailsLoaded = true;
+            return true;
         }
     }
 }

+ 147 - 121
BeatLyrics.Tool/Dialogs/OnlineLyricsDialog.Designer.cs

@@ -36,23 +36,24 @@
             System.Windows.Forms.TabPage tabPage2;
             System.Windows.Forms.Label label4;
             System.Windows.Forms.Panel panel1;
-            System.Windows.Forms.TabPage tabPage3;
             BrightIdeasSoftware.OLVColumn olvColumn2;
             BrightIdeasSoftware.OLVColumn olvColumn1;
             BrightIdeasSoftware.OLVColumn olvColumn3;
             BrightIdeasSoftware.OLVColumn olvColumn4;
             this.NcmKeywordTextBox = new System.Windows.Forms.TextBox();
             this.NcmSearchButton = new System.Windows.Forms.Button();
+            this.splitContainer1 = new System.Windows.Forms.SplitContainer();
             this.SongInfoTextBox = new System.Windows.Forms.RichTextBox();
-            this.KanTextBox = new System.Windows.Forms.TextBox();
-            this.HiraTextBox = new System.Windows.Forms.TextBox();
-            this.SelectionTextBox = new System.Windows.Forms.TextBox();
-            this.AppendKanButton = new System.Windows.Forms.Button();
-            this.AppendHiraganaButton = new System.Windows.Forms.Button();
             this.AppendSelectionButton = new System.Windows.Forms.Button();
-            this.ResultObjectListView = new BrightIdeasSoftware.ObjectListView();
+            this.AppendHiraganaButton = new System.Windows.Forms.Button();
+            this.AppendKanButton = new System.Windows.Forms.Button();
+            this.SelectionTextBox = new System.Windows.Forms.TextBox();
+            this.HiraTextBox = new System.Windows.Forms.TextBox();
+            this.KanTextBox = new System.Windows.Forms.TextBox();
             this.OnlineTabControl = new System.Windows.Forms.TabControl();
-            this.DetailsTabControl = new System.Windows.Forms.TabControl();
+            this.SearchResultTabPage = new System.Windows.Forms.TabPage();
+            this.ResultObjectListView = new BrightIdeasSoftware.ObjectListView();
+            this.SearchResultTabControl = new System.Windows.Forms.TabControl();
             this.LyricDetailTabPage = new System.Windows.Forms.TabPage();
             this.EditorControl = new BeatLyrics.Tool.UserControls.TextArrangeUserControl();
             this.DetailsRemarkTextBox = new System.Windows.Forms.TextBox();
@@ -67,7 +68,6 @@
             tabPage2 = new System.Windows.Forms.TabPage();
             label4 = new System.Windows.Forms.Label();
             panel1 = new System.Windows.Forms.Panel();
-            tabPage3 = new System.Windows.Forms.TabPage();
             olvColumn2 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
             olvColumn1 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
             olvColumn3 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
@@ -75,10 +75,14 @@
             tabPage1.SuspendLayout();
             tabPage2.SuspendLayout();
             panel1.SuspendLayout();
-            tabPage3.SuspendLayout();
-            ((System.ComponentModel.ISupportInitialize)(this.ResultObjectListView)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
+            this.splitContainer1.Panel1.SuspendLayout();
+            this.splitContainer1.Panel2.SuspendLayout();
+            this.splitContainer1.SuspendLayout();
             this.OnlineTabControl.SuspendLayout();
-            this.DetailsTabControl.SuspendLayout();
+            this.SearchResultTabPage.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.ResultObjectListView)).BeginInit();
+            this.SearchResultTabControl.SuspendLayout();
             this.LyricDetailTabPage.SuspendLayout();
             this.SuspendLayout();
             // 
@@ -128,7 +132,7 @@
             tabPage1.Location = new System.Drawing.Point(4, 22);
             tabPage1.Name = "tabPage1";
             tabPage1.Padding = new System.Windows.Forms.Padding(3);
-            tabPage1.Size = new System.Drawing.Size(824, 39);
+            tabPage1.Size = new System.Drawing.Size(389, 94);
             tabPage1.TabIndex = 0;
             tabPage1.Text = "NeteaseCloudMusic";
             tabPage1.UseVisualStyleBackColor = true;
@@ -140,15 +144,15 @@
             this.NcmKeywordTextBox.HideSelection = false;
             this.NcmKeywordTextBox.Location = new System.Drawing.Point(77, 6);
             this.NcmKeywordTextBox.Name = "NcmKeywordTextBox";
-            this.NcmKeywordTextBox.Size = new System.Drawing.Size(666, 21);
+            this.NcmKeywordTextBox.Size = new System.Drawing.Size(231, 21);
             this.NcmKeywordTextBox.TabIndex = 0;
             // 
             // NcmSearchButton
             // 
             this.NcmSearchButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.NcmSearchButton.Location = new System.Drawing.Point(749, 6);
+            this.NcmSearchButton.Location = new System.Drawing.Point(314, 6);
             this.NcmSearchButton.Name = "NcmSearchButton";
-            this.NcmSearchButton.Size = new System.Drawing.Size(75, 21);
+            this.NcmSearchButton.Size = new System.Drawing.Size(72, 21);
             this.NcmSearchButton.TabIndex = 1;
             this.NcmSearchButton.Text = "Search";
             this.NcmSearchButton.UseVisualStyleBackColor = true;
@@ -160,7 +164,7 @@
             tabPage2.Location = new System.Drawing.Point(4, 22);
             tabPage2.Name = "tabPage2";
             tabPage2.Padding = new System.Windows.Forms.Padding(3);
-            tabPage2.Size = new System.Drawing.Size(824, 39);
+            tabPage2.Size = new System.Drawing.Size(389, 94);
             tabPage2.TabIndex = 1;
             tabPage2.Text = "Todo";
             tabPage2.UseVisualStyleBackColor = true;
@@ -170,7 +174,7 @@
             label4.Dock = System.Windows.Forms.DockStyle.Fill;
             label4.Location = new System.Drawing.Point(3, 3);
             label4.Name = "label4";
-            label4.Size = new System.Drawing.Size(818, 33);
+            label4.Size = new System.Drawing.Size(383, 88);
             label4.TabIndex = 0;
             label4.Text = "More Online Lyrics";
             label4.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
@@ -178,22 +182,39 @@
             // panel1
             // 
             panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
-            panel1.Controls.Add(this.SongInfoTextBox);
-            panel1.Controls.Add(label3);
-            panel1.Controls.Add(label2);
-            panel1.Controls.Add(label1);
-            panel1.Controls.Add(this.KanTextBox);
-            panel1.Controls.Add(this.HiraTextBox);
-            panel1.Controls.Add(this.SelectionTextBox);
-            panel1.Controls.Add(this.AppendKanButton);
-            panel1.Controls.Add(this.AppendHiraganaButton);
-            panel1.Controls.Add(this.AppendSelectionButton);
+            panel1.Controls.Add(this.splitContainer1);
             panel1.Dock = System.Windows.Forms.DockStyle.Top;
             panel1.Location = new System.Drawing.Point(0, 0);
             panel1.Name = "panel1";
-            panel1.Size = new System.Drawing.Size(832, 116);
+            panel1.Size = new System.Drawing.Size(832, 122);
             panel1.TabIndex = 2;
             // 
+            // splitContainer1
+            // 
+            this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.splitContainer1.Location = new System.Drawing.Point(0, 0);
+            this.splitContainer1.Name = "splitContainer1";
+            // 
+            // splitContainer1.Panel1
+            // 
+            this.splitContainer1.Panel1.Controls.Add(this.SongInfoTextBox);
+            this.splitContainer1.Panel1.Controls.Add(this.AppendSelectionButton);
+            this.splitContainer1.Panel1.Controls.Add(this.AppendHiraganaButton);
+            this.splitContainer1.Panel1.Controls.Add(label3);
+            this.splitContainer1.Panel1.Controls.Add(this.AppendKanButton);
+            this.splitContainer1.Panel1.Controls.Add(label2);
+            this.splitContainer1.Panel1.Controls.Add(this.SelectionTextBox);
+            this.splitContainer1.Panel1.Controls.Add(label1);
+            this.splitContainer1.Panel1.Controls.Add(this.HiraTextBox);
+            this.splitContainer1.Panel1.Controls.Add(this.KanTextBox);
+            // 
+            // splitContainer1.Panel2
+            // 
+            this.splitContainer1.Panel2.Controls.Add(this.OnlineTabControl);
+            this.splitContainer1.Size = new System.Drawing.Size(830, 120);
+            this.splitContainer1.SplitterDistance = 429;
+            this.splitContainer1.TabIndex = 5;
+            // 
             // SongInfoTextBox
             // 
             this.SongInfoTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
@@ -203,34 +224,43 @@
             this.SongInfoTextBox.Multiline = false;
             this.SongInfoTextBox.Name = "SongInfoTextBox";
             this.SongInfoTextBox.ReadOnly = true;
-            this.SongInfoTextBox.Size = new System.Drawing.Size(823, 22);
+            this.SongInfoTextBox.Size = new System.Drawing.Size(423, 22);
             this.SongInfoTextBox.TabIndex = 4;
             this.SongInfoTextBox.Text = "";
             this.SongInfoTextBox.SelectionChanged += new System.EventHandler(this.SongInfoTextBox_SelectionChanged);
             // 
-            // KanTextBox
+            // AppendSelectionButton
             // 
-            this.KanTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
-            | System.Windows.Forms.AnchorStyles.Right)));
-            this.KanTextBox.HideSelection = false;
-            this.KanTextBox.Location = new System.Drawing.Point(81, 85);
-            this.KanTextBox.Name = "KanTextBox";
-            this.KanTextBox.ReadOnly = true;
-            this.KanTextBox.Size = new System.Drawing.Size(664, 21);
-            this.KanTextBox.TabIndex = 2;
-            this.KanTextBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
+            this.AppendSelectionButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.AppendSelectionButton.Location = new System.Drawing.Point(351, 31);
+            this.AppendSelectionButton.Name = "AppendSelectionButton";
+            this.AppendSelectionButton.Size = new System.Drawing.Size(75, 21);
+            this.AppendSelectionButton.TabIndex = 1;
+            this.AppendSelectionButton.Text = "Append";
+            this.AppendSelectionButton.UseVisualStyleBackColor = true;
+            this.AppendSelectionButton.Click += new System.EventHandler(this.AppendSelectionButton_Click);
             // 
-            // HiraTextBox
+            // AppendHiraganaButton
             // 
-            this.HiraTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
-            | System.Windows.Forms.AnchorStyles.Right)));
-            this.HiraTextBox.HideSelection = false;
-            this.HiraTextBox.Location = new System.Drawing.Point(81, 58);
-            this.HiraTextBox.Name = "HiraTextBox";
-            this.HiraTextBox.ReadOnly = true;
-            this.HiraTextBox.Size = new System.Drawing.Size(664, 21);
-            this.HiraTextBox.TabIndex = 2;
-            this.HiraTextBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
+            this.AppendHiraganaButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.AppendHiraganaButton.Location = new System.Drawing.Point(351, 58);
+            this.AppendHiraganaButton.Name = "AppendHiraganaButton";
+            this.AppendHiraganaButton.Size = new System.Drawing.Size(75, 21);
+            this.AppendHiraganaButton.TabIndex = 1;
+            this.AppendHiraganaButton.Text = "Append";
+            this.AppendHiraganaButton.UseVisualStyleBackColor = true;
+            this.AppendHiraganaButton.Click += new System.EventHandler(this.AppendHiraganaButton_Click);
+            // 
+            // AppendKanButton
+            // 
+            this.AppendKanButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.AppendKanButton.Location = new System.Drawing.Point(351, 85);
+            this.AppendKanButton.Name = "AppendKanButton";
+            this.AppendKanButton.Size = new System.Drawing.Size(75, 21);
+            this.AppendKanButton.TabIndex = 1;
+            this.AppendKanButton.Text = "Append";
+            this.AppendKanButton.UseVisualStyleBackColor = true;
+            this.AppendKanButton.Click += new System.EventHandler(this.AppendKanButton_Click);
             // 
             // SelectionTextBox
             // 
@@ -240,53 +270,55 @@
             this.SelectionTextBox.Location = new System.Drawing.Point(81, 31);
             this.SelectionTextBox.Name = "SelectionTextBox";
             this.SelectionTextBox.ReadOnly = true;
-            this.SelectionTextBox.Size = new System.Drawing.Size(664, 21);
+            this.SelectionTextBox.Size = new System.Drawing.Size(264, 21);
             this.SelectionTextBox.TabIndex = 2;
             this.SelectionTextBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
             // 
-            // AppendKanButton
+            // HiraTextBox
             // 
-            this.AppendKanButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.AppendKanButton.Location = new System.Drawing.Point(751, 84);
-            this.AppendKanButton.Name = "AppendKanButton";
-            this.AppendKanButton.Size = new System.Drawing.Size(75, 21);
-            this.AppendKanButton.TabIndex = 1;
-            this.AppendKanButton.Text = "Append";
-            this.AppendKanButton.UseVisualStyleBackColor = true;
-            this.AppendKanButton.Click += new System.EventHandler(this.AppendKanButton_Click);
+            this.HiraTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.HiraTextBox.HideSelection = false;
+            this.HiraTextBox.Location = new System.Drawing.Point(81, 58);
+            this.HiraTextBox.Name = "HiraTextBox";
+            this.HiraTextBox.ReadOnly = true;
+            this.HiraTextBox.Size = new System.Drawing.Size(264, 21);
+            this.HiraTextBox.TabIndex = 2;
+            this.HiraTextBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
             // 
-            // AppendHiraganaButton
+            // KanTextBox
             // 
-            this.AppendHiraganaButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.AppendHiraganaButton.Location = new System.Drawing.Point(751, 57);
-            this.AppendHiraganaButton.Name = "AppendHiraganaButton";
-            this.AppendHiraganaButton.Size = new System.Drawing.Size(75, 21);
-            this.AppendHiraganaButton.TabIndex = 1;
-            this.AppendHiraganaButton.Text = "Append";
-            this.AppendHiraganaButton.UseVisualStyleBackColor = true;
-            this.AppendHiraganaButton.Click += new System.EventHandler(this.AppendHiraganaButton_Click);
+            this.KanTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.KanTextBox.HideSelection = false;
+            this.KanTextBox.Location = new System.Drawing.Point(81, 85);
+            this.KanTextBox.Name = "KanTextBox";
+            this.KanTextBox.ReadOnly = true;
+            this.KanTextBox.Size = new System.Drawing.Size(264, 21);
+            this.KanTextBox.TabIndex = 2;
+            this.KanTextBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
             // 
-            // AppendSelectionButton
+            // OnlineTabControl
             // 
-            this.AppendSelectionButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.AppendSelectionButton.Location = new System.Drawing.Point(751, 30);
-            this.AppendSelectionButton.Name = "AppendSelectionButton";
-            this.AppendSelectionButton.Size = new System.Drawing.Size(75, 21);
-            this.AppendSelectionButton.TabIndex = 1;
-            this.AppendSelectionButton.Text = "Append";
-            this.AppendSelectionButton.UseVisualStyleBackColor = true;
-            this.AppendSelectionButton.Click += new System.EventHandler(this.AppendSelectionButton_Click);
+            this.OnlineTabControl.Controls.Add(tabPage1);
+            this.OnlineTabControl.Controls.Add(tabPage2);
+            this.OnlineTabControl.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.OnlineTabControl.Location = new System.Drawing.Point(0, 0);
+            this.OnlineTabControl.Name = "OnlineTabControl";
+            this.OnlineTabControl.SelectedIndex = 0;
+            this.OnlineTabControl.Size = new System.Drawing.Size(397, 120);
+            this.OnlineTabControl.TabIndex = 1;
             // 
-            // tabPage3
+            // SearchResultTabPage
             // 
-            tabPage3.Controls.Add(this.ResultObjectListView);
-            tabPage3.Location = new System.Drawing.Point(4, 22);
-            tabPage3.Name = "tabPage3";
-            tabPage3.Padding = new System.Windows.Forms.Padding(3);
-            tabPage3.Size = new System.Drawing.Size(824, 482);
-            tabPage3.TabIndex = 0;
-            tabPage3.Text = "Result";
-            tabPage3.UseVisualStyleBackColor = true;
+            this.SearchResultTabPage.Controls.Add(this.ResultObjectListView);
+            this.SearchResultTabPage.Location = new System.Drawing.Point(4, 22);
+            this.SearchResultTabPage.Name = "SearchResultTabPage";
+            this.SearchResultTabPage.Padding = new System.Windows.Forms.Padding(3);
+            this.SearchResultTabPage.Size = new System.Drawing.Size(824, 462);
+            this.SearchResultTabPage.TabIndex = 0;
+            this.SearchResultTabPage.Text = "Result";
+            this.SearchResultTabPage.UseVisualStyleBackColor = true;
             // 
             // ResultObjectListView
             // 
@@ -307,7 +339,7 @@
             this.ResultObjectListView.Location = new System.Drawing.Point(3, 3);
             this.ResultObjectListView.Name = "ResultObjectListView";
             this.ResultObjectListView.ShowGroups = false;
-            this.ResultObjectListView.Size = new System.Drawing.Size(818, 476);
+            this.ResultObjectListView.Size = new System.Drawing.Size(818, 456);
             this.ResultObjectListView.TabIndex = 0;
             this.ResultObjectListView.UseCompatibleStateImageBehavior = false;
             this.ResultObjectListView.View = System.Windows.Forms.View.Details;
@@ -336,27 +368,16 @@
             olvColumn4.Text = "Remarks";
             olvColumn4.Width = 320;
             // 
-            // OnlineTabControl
+            // SearchResultTabControl
             // 
-            this.OnlineTabControl.Controls.Add(tabPage1);
-            this.OnlineTabControl.Controls.Add(tabPage2);
-            this.OnlineTabControl.Dock = System.Windows.Forms.DockStyle.Top;
-            this.OnlineTabControl.Location = new System.Drawing.Point(0, 116);
-            this.OnlineTabControl.Name = "OnlineTabControl";
-            this.OnlineTabControl.SelectedIndex = 0;
-            this.OnlineTabControl.Size = new System.Drawing.Size(832, 65);
-            this.OnlineTabControl.TabIndex = 1;
-            // 
-            // DetailsTabControl
-            // 
-            this.DetailsTabControl.Controls.Add(tabPage3);
-            this.DetailsTabControl.Controls.Add(this.LyricDetailTabPage);
-            this.DetailsTabControl.Dock = System.Windows.Forms.DockStyle.Fill;
-            this.DetailsTabControl.Location = new System.Drawing.Point(0, 181);
-            this.DetailsTabControl.Name = "DetailsTabControl";
-            this.DetailsTabControl.SelectedIndex = 0;
-            this.DetailsTabControl.Size = new System.Drawing.Size(832, 508);
-            this.DetailsTabControl.TabIndex = 3;
+            this.SearchResultTabControl.Controls.Add(this.SearchResultTabPage);
+            this.SearchResultTabControl.Controls.Add(this.LyricDetailTabPage);
+            this.SearchResultTabControl.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.SearchResultTabControl.Location = new System.Drawing.Point(0, 122);
+            this.SearchResultTabControl.Name = "SearchResultTabControl";
+            this.SearchResultTabControl.SelectedIndex = 0;
+            this.SearchResultTabControl.Size = new System.Drawing.Size(832, 488);
+            this.SearchResultTabControl.TabIndex = 3;
             // 
             // LyricDetailTabPage
             // 
@@ -368,7 +389,7 @@
             this.LyricDetailTabPage.Location = new System.Drawing.Point(4, 22);
             this.LyricDetailTabPage.Name = "LyricDetailTabPage";
             this.LyricDetailTabPage.Padding = new System.Windows.Forms.Padding(3);
-            this.LyricDetailTabPage.Size = new System.Drawing.Size(824, 482);
+            this.LyricDetailTabPage.Size = new System.Drawing.Size(824, 462);
             this.LyricDetailTabPage.TabIndex = 1;
             this.LyricDetailTabPage.Text = "Details";
             this.LyricDetailTabPage.UseVisualStyleBackColor = true;
@@ -380,7 +401,7 @@
             | System.Windows.Forms.AnchorStyles.Right)));
             this.EditorControl.Location = new System.Drawing.Point(9, 61);
             this.EditorControl.Name = "EditorControl";
-            this.EditorControl.Size = new System.Drawing.Size(807, 385);
+            this.EditorControl.Size = new System.Drawing.Size(807, 366);
             this.EditorControl.TabIndex = 2;
             // 
             // DetailsRemarkTextBox
@@ -410,15 +431,15 @@
             this.SaveTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
             this.SaveTextBox.HideSelection = false;
-            this.SaveTextBox.Location = new System.Drawing.Point(8, 453);
+            this.SaveTextBox.Location = new System.Drawing.Point(9, 434);
             this.SaveTextBox.Name = "SaveTextBox";
-            this.SaveTextBox.Size = new System.Drawing.Size(727, 21);
+            this.SaveTextBox.Size = new System.Drawing.Size(726, 21);
             this.SaveTextBox.TabIndex = 0;
             // 
             // SaveButton
             // 
             this.SaveButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-            this.SaveButton.Location = new System.Drawing.Point(741, 452);
+            this.SaveButton.Location = new System.Drawing.Point(741, 434);
             this.SaveButton.Name = "SaveButton";
             this.SaveButton.Size = new System.Drawing.Size(75, 21);
             this.SaveButton.TabIndex = 1;
@@ -430,9 +451,8 @@
             // 
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
-            this.ClientSize = new System.Drawing.Size(832, 689);
-            this.Controls.Add(this.DetailsTabControl);
-            this.Controls.Add(this.OnlineTabControl);
+            this.ClientSize = new System.Drawing.Size(832, 610);
+            this.Controls.Add(this.SearchResultTabControl);
             this.Controls.Add(panel1);
             this.KeyPreview = true;
             this.MinimizeBox = false;
@@ -444,11 +464,15 @@
             tabPage1.PerformLayout();
             tabPage2.ResumeLayout(false);
             panel1.ResumeLayout(false);
-            panel1.PerformLayout();
-            tabPage3.ResumeLayout(false);
-            ((System.ComponentModel.ISupportInitialize)(this.ResultObjectListView)).EndInit();
+            this.splitContainer1.Panel1.ResumeLayout(false);
+            this.splitContainer1.Panel1.PerformLayout();
+            this.splitContainer1.Panel2.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
+            this.splitContainer1.ResumeLayout(false);
             this.OnlineTabControl.ResumeLayout(false);
-            this.DetailsTabControl.ResumeLayout(false);
+            this.SearchResultTabPage.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.ResultObjectListView)).EndInit();
+            this.SearchResultTabControl.ResumeLayout(false);
             this.LyricDetailTabPage.ResumeLayout(false);
             this.LyricDetailTabPage.PerformLayout();
             this.ResumeLayout(false);
@@ -465,7 +489,7 @@
         private System.Windows.Forms.Button AppendHiraganaButton;
         private System.Windows.Forms.TextBox NcmKeywordTextBox;
         private System.Windows.Forms.Button NcmSearchButton;
-        private System.Windows.Forms.TabControl DetailsTabControl;
+        private System.Windows.Forms.TabControl SearchResultTabControl;
         private System.Windows.Forms.TabPage LyricDetailTabPage;
         private System.Windows.Forms.RichTextBox SongInfoTextBox;
         private BrightIdeasSoftware.ObjectListView ResultObjectListView;
@@ -474,5 +498,7 @@
         private System.Windows.Forms.TextBox DetailsRemarkTextBox;
         private System.Windows.Forms.TextBox DetailsInfoTextBox;
         private UserControls.TextArrangeUserControl EditorControl;
+        private System.Windows.Forms.SplitContainer splitContainer1;
+        private System.Windows.Forms.TabPage SearchResultTabPage;
     }
 }

+ 31 - 12
BeatLyrics.Tool/Dialogs/OnlineLyricsDialog.cs

@@ -1,14 +1,14 @@
-using System;
-using System.IO;
-using System.Threading.Tasks;
-using System.Windows.Forms;
-using BeatLyrics.Common;
+using BeatLyrics.Common;
 using BeatLyrics.Tool.DataProvider.OnlineLyric;
 using BeatLyrics.Tool.DataProvider.OnlineLyric.Models;
 using BeatLyrics.Tool.Models;
 using BeatLyrics.Tool.Utils;
 using Microsoft.International.Converters;
 using Newtonsoft.Json;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows.Forms;
 
 namespace BeatLyrics.Tool.Dialogs
 {
@@ -48,7 +48,7 @@ namespace BeatLyrics.Tool.Dialogs
 
         private void SongInfoTextBox_SelectionChanged(object sender, EventArgs e)
         {
-            SelectionTextBox.Text = SongInfoTextBox.SelectedText;
+            SelectionTextBox.Text = SongInfoTextBox.SelectedText.Trim();
             HiraTextBox.Text = KanaConverter.RomajiToHiragana(SelectionTextBox.Text).Replace(" ", "");
             KanTextBox.Text = _converter.HiraganaToKanji(HiraTextBox.Text);
         }
@@ -61,6 +61,7 @@ namespace BeatLyrics.Tool.Dialogs
 
         private void NcmSearchButton_Click(object sender, EventArgs e)
         {
+            if (NcmKeywordTextBox.TextLength < 1) return;
             if (_searchTask != null) return;
             _searchTask = Task.Factory.StartNew(() =>
             {
@@ -69,7 +70,11 @@ namespace BeatLyrics.Tool.Dialogs
                     using (this.ShowLoading("Searching..."))
                     {
                         var result = _ncmLyricProvider.Search(NcmKeywordTextBox.Text, "");
-                        ResultObjectListView.SetObjects(result);
+                        RunOnUiThread(() =>
+                        {
+                            ResultObjectListView.SetObjects(result);
+                            SearchResultTabControl.SelectedTab = SearchResultTabPage;
+                        });
                     }
                 }
                 catch (Exception exception)
@@ -102,10 +107,13 @@ namespace BeatLyrics.Tool.Dialogs
                     case nameof(NeteaseCloudMusicLyricProvider):
                         Task.Factory.StartNew(() =>
                         {
+                            bool found;
                             using (this.ShowLoading("Fetching Details..."))
                             {
-                                _ncmLyricProvider.GetDetail(item);
+                                found = _ncmLyricProvider.GetDetail(item);
                             }
+
+                            if (false == found) RunOnUiThread(() => MessageBox.Show("Lyric not found"));
                         }).ContinueWith(t =>
                         {
                             if (t.Exception != null)
@@ -113,7 +121,10 @@ namespace BeatLyrics.Tool.Dialogs
                                 RunOnUiThread(() => new ThreadExceptionDialog(t.Exception).ShowDialog(this));
                                 return;
                             }
-                            RunOnUiThread(() => SetEditorControlData(item));
+                            RunOnUiThread(() =>
+                            {
+                                if (item.IsDetailsLoaded) SetEditorControlData(item);
+                            });
                         });
                         break;
                 }
@@ -126,16 +137,24 @@ namespace BeatLyrics.Tool.Dialogs
 
         private void SetEditorControlData(LyricSearchResultItem item)
         {
-            _selectedItem = new LyricFileExt { Main = item.Details, Subtitle = item.DetailsTranslated };
+            _selectedItem = new LyricFileExt
+            {
+                Main = item.Details,
+                MainText = item.DetailsText,
+                Subtitle = item.DetailsTranslated,
+                SubtitleText = item.DetailsTranslatedText,
+            };
 
-            DetailsTabControl.SelectedTab = LyricDetailTabPage;
+            SearchResultTabControl.SelectedTab = LyricDetailTabPage;
 
             DetailsInfoTextBox.Text = $"{item.Name} - {item.Artists} - {item.DurationText}";
             DetailsRemarkTextBox.Text = item.Remarks;
 
             EditorControl.SetLyricFile(_selectedItem);
 
-            SaveTextBox.Text = item.Remarks.ToSafeFileName() + ".json";
+            SaveTextBox.Text = false == string.IsNullOrWhiteSpace(item.Remarks)
+                ? item.Remarks.ToSafeFileName() + ".json"
+                : $"{item.Name} - {item.Artists}".ToSafeFileName() + ".json";
         }
 
         private void SaveButton_Click(object sender, EventArgs e)

+ 0 - 3
BeatLyrics.Tool/Dialogs/OnlineLyricsDialog.resx

@@ -141,9 +141,6 @@
   <metadata name="panel1.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="tabPage3.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>False</value>
-  </metadata>
   <metadata name="olvColumn2.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>

+ 62 - 0
BeatLyrics.Tool/Dialogs/TextDialog.Designer.cs

@@ -0,0 +1,62 @@
+namespace BeatLyrics.Tool.Dialogs
+{
+    partial class TextDialog
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.TextRichTextBox = new System.Windows.Forms.RichTextBox();
+            this.SuspendLayout();
+            // 
+            // TextRichTextBox
+            // 
+            this.TextRichTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.TextRichTextBox.Location = new System.Drawing.Point(0, 0);
+            this.TextRichTextBox.Name = "TextRichTextBox";
+            this.TextRichTextBox.ReadOnly = true;
+            this.TextRichTextBox.Size = new System.Drawing.Size(555, 327);
+            this.TextRichTextBox.TabIndex = 0;
+            this.TextRichTextBox.Text = "";
+            // 
+            // TextDialog
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(555, 327);
+            this.Controls.Add(this.TextRichTextBox);
+            this.Name = "TextDialog";
+            this.ShowInTaskbar = false;
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Text Viewer";
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        public System.Windows.Forms.RichTextBox TextRichTextBox;
+    }
+}

+ 20 - 0
BeatLyrics.Tool/Dialogs/TextDialog.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace BeatLyrics.Tool.Dialogs
+{
+    public partial class TextDialog : Form
+    {
+        public TextDialog()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 120 - 0
BeatLyrics.Tool/Dialogs/TextDialog.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 1 - 5
BeatLyrics.Tool/Dialogs/TextEditDialog.cs

@@ -27,7 +27,6 @@ namespace BeatLyrics.Tool.Dialogs
             var duration = OggAudioPlayer.Length;
             EditorControl.SetMediaInfo((int)duration.TotalMilliseconds);
 
-            //load local lyric
             var localLyricPath = Path.Combine(DirProvider.GetLyricDir(_nfo.Hash), SaveTextBox.Text);
             var json = File.ReadAllText(localLyricPath);
             _selectedItem = JsonConvert.DeserializeObject<LyricFileExt>(json);
@@ -42,9 +41,6 @@ namespace BeatLyrics.Tool.Dialogs
             Close();
         }
 
-        private void TextEditForm_FormClosed(object sender, FormClosedEventArgs e)
-        {
-            EditorControl.ClearData();
-        }
+        private void TextEditForm_FormClosed(object sender, FormClosedEventArgs e) => EditorControl.ClearData();
     }
 }

+ 28 - 26
BeatLyrics.Tool/MainForm.Designer.cs

@@ -38,15 +38,14 @@
             this.splitContainer1 = new System.Windows.Forms.SplitContainer();
             this.MainObjectListView = new BrightIdeasSoftware.ObjectListView();
             this.MainImageList = new System.Windows.Forms.ImageList(this.components);
+            this.VerifyCheckBox = new System.Windows.Forms.CheckBox();
             this.LocalLyricObjectListView = new BrightIdeasSoftware.ObjectListView();
             this.olvColumn2 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
             this.olvColumn1 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
             this.HashExploreHereLinkLabel = new System.Windows.Forms.LinkLabel();
             this.DirExploreHereLinkLabel = new System.Windows.Forms.LinkLabel();
-            this.DeleteButton = new System.Windows.Forms.Button();
             this.EditButton = new System.Windows.Forms.Button();
             this.SetButton = new System.Windows.Forms.Button();
-            this.NewButton = new System.Windows.Forms.Button();
             this.OnlineButton = new System.Windows.Forms.Button();
             this.HashTextBox = new System.Windows.Forms.TextBox();
             this.LevelAuthorTextBox = new System.Windows.Forms.TextBox();
@@ -57,6 +56,7 @@
             this.CoverPictureBox = new System.Windows.Forms.PictureBox();
             this.MainProgressBar = new System.Windows.Forms.ProgressBar();
             this.panel1 = new System.Windows.Forms.Panel();
+            this.SearchVerifyCheckBox = new System.Windows.Forms.CheckBox();
             this.OtherRadioButton = new System.Windows.Forms.RadioButton();
             this.SetOnlyRadioButton = new System.Windows.Forms.RadioButton();
             this.AllRadioButton = new System.Windows.Forms.RadioButton();
@@ -145,13 +145,12 @@
             // 
             // splitContainer1.Panel2
             // 
+            this.splitContainer1.Panel2.Controls.Add(this.VerifyCheckBox);
             this.splitContainer1.Panel2.Controls.Add(this.LocalLyricObjectListView);
             this.splitContainer1.Panel2.Controls.Add(this.HashExploreHereLinkLabel);
             this.splitContainer1.Panel2.Controls.Add(this.DirExploreHereLinkLabel);
-            this.splitContainer1.Panel2.Controls.Add(this.DeleteButton);
             this.splitContainer1.Panel2.Controls.Add(this.EditButton);
             this.splitContainer1.Panel2.Controls.Add(this.SetButton);
-            this.splitContainer1.Panel2.Controls.Add(this.NewButton);
             this.splitContainer1.Panel2.Controls.Add(this.OnlineButton);
             this.splitContainer1.Panel2.Controls.Add(label6);
             this.splitContainer1.Panel2.Controls.Add(label5);
@@ -196,6 +195,17 @@
             this.MainImageList.ImageSize = new System.Drawing.Size(48, 48);
             this.MainImageList.TransparentColor = System.Drawing.Color.Transparent;
             // 
+            // VerifyCheckBox
+            // 
+            this.VerifyCheckBox.AutoSize = true;
+            this.VerifyCheckBox.Location = new System.Drawing.Point(166, 512);
+            this.VerifyCheckBox.Name = "VerifyCheckBox";
+            this.VerifyCheckBox.Size = new System.Drawing.Size(60, 16);
+            this.VerifyCheckBox.TabIndex = 8;
+            this.VerifyCheckBox.Text = "Verify";
+            this.VerifyCheckBox.UseVisualStyleBackColor = true;
+            this.VerifyCheckBox.CheckedChanged += new System.EventHandler(this.VerifyCheckBox_CheckedChanged);
+            // 
             // LocalLyricObjectListView
             // 
             this.LocalLyricObjectListView.AllColumns.Add(this.olvColumn2);
@@ -253,16 +263,6 @@
             this.DirExploreHereLinkLabel.Text = "Explosion Here";
             this.DirExploreHereLinkLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.DirExploreHereLinkLabel_LinkClicked);
             // 
-            // DeleteButton
-            // 
-            this.DeleteButton.Location = new System.Drawing.Point(210, 508);
-            this.DeleteButton.Name = "DeleteButton";
-            this.DeleteButton.Size = new System.Drawing.Size(55, 23);
-            this.DeleteButton.TabIndex = 5;
-            this.DeleteButton.Text = "Delete";
-            this.DeleteButton.UseVisualStyleBackColor = true;
-            this.DeleteButton.Click += new System.EventHandler(this.DeleteButton_Click);
-            // 
             // EditButton
             // 
             this.EditButton.Location = new System.Drawing.Point(67, 508);
@@ -283,16 +283,6 @@
             this.SetButton.UseVisualStyleBackColor = true;
             this.SetButton.Click += new System.EventHandler(this.SetButton_Click);
             // 
-            // NewButton
-            // 
-            this.NewButton.Location = new System.Drawing.Point(166, 508);
-            this.NewButton.Name = "NewButton";
-            this.NewButton.Size = new System.Drawing.Size(38, 23);
-            this.NewButton.TabIndex = 5;
-            this.NewButton.Text = "New";
-            this.NewButton.UseVisualStyleBackColor = true;
-            this.NewButton.Click += new System.EventHandler(this.NewButton_Click);
-            // 
             // OnlineButton
             // 
             this.OnlineButton.Location = new System.Drawing.Point(6, 508);
@@ -374,6 +364,7 @@
             // 
             // panel1
             // 
+            this.panel1.Controls.Add(this.SearchVerifyCheckBox);
             this.panel1.Controls.Add(this.OtherRadioButton);
             this.panel1.Controls.Add(this.SetOnlyRadioButton);
             this.panel1.Controls.Add(this.AllRadioButton);
@@ -384,6 +375,17 @@
             this.panel1.Size = new System.Drawing.Size(1082, 34);
             this.panel1.TabIndex = 4;
             // 
+            // SearchVerifyCheckBox
+            // 
+            this.SearchVerifyCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.SearchVerifyCheckBox.Location = new System.Drawing.Point(1010, 7);
+            this.SearchVerifyCheckBox.Name = "SearchVerifyCheckBox";
+            this.SearchVerifyCheckBox.Size = new System.Drawing.Size(60, 17);
+            this.SearchVerifyCheckBox.TabIndex = 9;
+            this.SearchVerifyCheckBox.Text = "Verify";
+            this.SearchVerifyCheckBox.UseVisualStyleBackColor = true;
+            this.SearchVerifyCheckBox.CheckedChanged += new System.EventHandler(this.SearchVerifyCheckBox_CheckedChanged);
+            // 
             // OtherRadioButton
             // 
             this.OtherRadioButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
@@ -472,9 +474,7 @@
         private System.Windows.Forms.TextBox SongNameTextBox;
         private System.Windows.Forms.TextBox DirTextBox;
         private System.Windows.Forms.TextBox LevelAuthorTextBox;
-        private System.Windows.Forms.Button DeleteButton;
         private System.Windows.Forms.Button SetButton;
-        private System.Windows.Forms.Button NewButton;
         private System.Windows.Forms.Button OnlineButton;
         private System.Windows.Forms.LinkLabel DirExploreHereLinkLabel;
         private System.Windows.Forms.TextBox HashTextBox;
@@ -486,5 +486,7 @@
         private System.Windows.Forms.RadioButton SetOnlyRadioButton;
         private System.Windows.Forms.RadioButton AllRadioButton;
         private System.Windows.Forms.LinkLabel HashExploreHereLinkLabel;
+        private System.Windows.Forms.CheckBox VerifyCheckBox;
+        private System.Windows.Forms.CheckBox SearchVerifyCheckBox;
     }
 }

+ 41 - 28
BeatLyrics.Tool/MainForm.cs

@@ -80,8 +80,23 @@ namespace BeatLyrics.Tool
             });
         }
 
+        private void ClearDetails()
+        {
+            HashTextBox.Text = null;
+            CoverPictureBox.BackgroundImage = null;
+            DirTextBox.Text = null;
+            SongNameTextBox.Text = null;
+            SubNameTextBox.Text = null;
+            SongAuthorTextBox.Text = null;
+            LevelAuthorTextBox.Text = null;
+            LocalLyricObjectListView.ClearObjects();
+        }
+
         private void BindDetails(LevelInfo item)
         {
+            ClearDetails();
+            VerifyCheckBox.Checked = File.Exists(DirProvider.GetVerifyFile(item.Hash));
+
             CoverPictureBox.BackgroundImage = item.CoverImage;
             DirTextBox.Text = item.Directory;
             SongNameTextBox.Text = item.SongName;
@@ -90,10 +105,7 @@ namespace BeatLyrics.Tool
             LevelAuthorTextBox.Text = item.LevelAuthor;
             HashTextBox.Text = item.Hash;
 
-            if (false == item.IsValid)
-            {
-                SubNameTextBox.Text = item.Error;
-            }
+            if (false == item.IsValid) SubNameTextBox.Text = item.Error;
 
             LocalLyricObjectListView.SetObjects(item.LocalLyrics);
         }
@@ -109,14 +121,13 @@ namespace BeatLyrics.Tool
             if (false == string.IsNullOrWhiteSpace(pattern))
             {
                 items = items.Where(p =>
-                     true == p.SongName?.ToLower().Contains(pattern) ||
-                     true == p.SongSubName?.ToLower().Contains(pattern) ||
-                     true == p.SongAuthor?.ToLower().Contains(pattern) ||
-                     true == p.LevelAuthor?.ToLower().Contains(pattern) ||
-                     true == p.Description?.ToLower().Contains(pattern) ||
-                     true == p.Hash?.ToLower().Contains(pattern) ||
-                     true == p.Directory?.ToLower().Contains(pattern)
-                 ).ToArray();
+                    true == p.SongName?.ToLower().Contains(pattern) ||
+                    true == p.SongSubName?.ToLower().Contains(pattern) ||
+                    true == p.SongAuthor?.ToLower().Contains(pattern) ||
+                    true == p.LevelAuthor?.ToLower().Contains(pattern) ||
+                    true == p.Description?.ToLower().Contains(pattern) ||
+                    true == p.Hash?.ToLower().Contains(pattern) ||
+                    true == p.Directory?.ToLower().Contains(pattern)).ToArray();
             }
 
             if (false == AllRadioButton.Checked)
@@ -126,6 +137,8 @@ namespace BeatLyrics.Tool
                     .ToArray();
             }
 
+            items = items.Where(p => p.IsVerify == SearchVerifyCheckBox.Checked).ToArray();
+
             MainObjectListView.SetObjects(items);
 
             if (MainObjectListView.Items.Count == 1) MainObjectListView.SelectedIndex = 0;
@@ -156,6 +169,8 @@ namespace BeatLyrics.Tool
 
         private void SearchTextBox_TextChanged(object sender, EventArgs e) => Search();
 
+        private void SearchVerifyCheckBox_CheckedChanged(object sender, EventArgs e) => Search();
+
         private void AllRadioButton_CheckedChanged(object sender, EventArgs e)
         {
             if (((RadioButton)sender).Checked) Search();
@@ -166,23 +181,16 @@ namespace BeatLyrics.Tool
             var selectedObject = MainObjectListView.SelectedObject;
             if (null == selectedObject)
             {
-                CoverPictureBox.BackgroundImage = null;
-                DirTextBox.Text = null;
-                SongNameTextBox.Text = null;
-                SubNameTextBox.Text = null;
-                SongAuthorTextBox.Text = null;
-                LevelAuthorTextBox.Text = null;
-                HashTextBox.Text = null;
+                ClearDetails();
             }
             else
             {
-                var item = (LevelInfo)selectedObject;
-
-                BindDetails(item);
+                BindDetails((LevelInfo)selectedObject);
             }
         }
 
         private void DirExploreHereLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) => Process.Start("explorer.exe", DirTextBox.Text);
+
         private void HashExploreHereLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) => Process.Start("explorer.exe", DirProvider.GetLyricDir(HashTextBox.Text));
 
         private void OnlineButton_Click(object sender, EventArgs e)
@@ -237,14 +245,19 @@ namespace BeatLyrics.Tool
             BindDetails(nfo);
         }
 
-        private void NewButton_Click(object sender, EventArgs e)
+        private void VerifyCheckBox_CheckedChanged(object sender, EventArgs e)
         {
-            //TODO: Ask for file name to create, show timeline edit form
-        }
+            if (string.IsNullOrWhiteSpace(HashTextBox.Text)) return;
 
-        private void DeleteButton_Click(object sender, EventArgs e)
-        {
-            //TODO: Delete and refresh ui
+            var verifyFilePath = DirProvider.GetVerifyFile(HashTextBox.Text);
+            if (VerifyCheckBox.Checked && !File.Exists(verifyFilePath))
+            {
+                using var _ = File.Create(verifyFilePath);
+            }
+            else if (File.Exists(verifyFilePath))
+            {
+                File.Delete(verifyFilePath);
+            }
         }
     }
 }

+ 4 - 0
BeatLyrics.Tool/Models/LevelInfo.cs

@@ -1,9 +1,13 @@
 using System.Drawing;
+using System.IO;
+using BeatLyrics.Common;
 
 namespace BeatLyrics.Tool.Models
 {
     internal class LevelInfo
     {
+        public bool IsVerify => File.Exists(DirProvider.GetVerifyFile(Hash));
+
         public bool IsValid { get; set; }
         public string Error { get; set; }
 

+ 3 - 3
BeatLyrics.Tool/Models/LyricDetailExt.cs

@@ -1,8 +1,8 @@
-using System;
-using System.Drawing;
-using BeatLyrics.Common.Models;
+using BeatLyrics.Common.Models;
 using BeatLyrics.Tool.Utils;
 using Newtonsoft.Json;
+using System;
+using System.Drawing;
 
 namespace BeatLyrics.Tool.Models
 {

+ 5 - 0
BeatLyrics.Tool/Models/LyricFileExt.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using Newtonsoft.Json;
 
 namespace BeatLyrics.Tool.Models
 {
@@ -6,5 +7,9 @@ namespace BeatLyrics.Tool.Models
     {
         public List<LyricDetailExt> Main { get; set; }
         public List<LyricDetailExt> Subtitle { get; set; }
+        [JsonIgnore]
+        public string MainText { get; set; }
+        [JsonIgnore]
+        public string SubtitleText { get; set; }
     }
 }

+ 42 - 16
BeatLyrics.Tool/UserControls/TextArrangeUserControl.Designer.cs

@@ -57,6 +57,8 @@
             this.PosLabel = new System.Windows.Forms.Label();
             this.PlayPosTrackBar = new System.Windows.Forms.TrackBar();
             this.UpdateTimer = new System.Windows.Forms.Timer(this.components);
+            this.LeftTextButton = new System.Windows.Forms.Button();
+            this.RightTextButton = new System.Windows.Forms.Button();
             splitContainer1 = new System.Windows.Forms.SplitContainer();
             ((System.ComponentModel.ISupportInitialize)(splitContainer1)).BeginInit();
             splitContainer1.Panel1.SuspendLayout();
@@ -170,9 +172,9 @@
             // RightSelectAllButton
             // 
             this.RightSelectAllButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.RightSelectAllButton.Location = new System.Drawing.Point(753, 98);
+            this.RightSelectAllButton.Location = new System.Drawing.Point(774, 98);
             this.RightSelectAllButton.Name = "RightSelectAllButton";
-            this.RightSelectAllButton.Size = new System.Drawing.Size(31, 21);
+            this.RightSelectAllButton.Size = new System.Drawing.Size(20, 21);
             this.RightSelectAllButton.TabIndex = 30;
             this.RightSelectAllButton.Text = "✔";
             this.RightSelectAllButton.UseVisualStyleBackColor = true;
@@ -205,9 +207,9 @@
             // RightDeleteButton
             // 
             this.RightDeleteButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.RightDeleteButton.Location = new System.Drawing.Point(790, 98);
+            this.RightDeleteButton.Location = new System.Drawing.Point(800, 98);
             this.RightDeleteButton.Name = "RightDeleteButton";
-            this.RightDeleteButton.Size = new System.Drawing.Size(31, 21);
+            this.RightDeleteButton.Size = new System.Drawing.Size(21, 21);
             this.RightDeleteButton.TabIndex = 27;
             this.RightDeleteButton.Text = "🗑︎";
             this.RightDeleteButton.UseVisualStyleBackColor = true;
@@ -215,9 +217,9 @@
             // 
             // LeftDeleteButton
             // 
-            this.LeftDeleteButton.Location = new System.Drawing.Point(40, 98);
+            this.LeftDeleteButton.Location = new System.Drawing.Point(31, 98);
             this.LeftDeleteButton.Name = "LeftDeleteButton";
-            this.LeftDeleteButton.Size = new System.Drawing.Size(31, 21);
+            this.LeftDeleteButton.Size = new System.Drawing.Size(22, 21);
             this.LeftDeleteButton.TabIndex = 26;
             this.LeftDeleteButton.Text = "🗑︎";
             this.LeftDeleteButton.UseVisualStyleBackColor = true;
@@ -227,7 +229,7 @@
             // 
             this.LeftSelectAllButton.Location = new System.Drawing.Point(3, 98);
             this.LeftSelectAllButton.Name = "LeftSelectAllButton";
-            this.LeftSelectAllButton.Size = new System.Drawing.Size(31, 21);
+            this.LeftSelectAllButton.Size = new System.Drawing.Size(22, 21);
             this.LeftSelectAllButton.TabIndex = 25;
             this.LeftSelectAllButton.Text = "✔";
             this.LeftSelectAllButton.UseVisualStyleBackColor = true;
@@ -235,29 +237,29 @@
             // 
             // RemoveRubyButton
             // 
-            this.RemoveRubyButton.Location = new System.Drawing.Point(78, 98);
+            this.RemoveRubyButton.Location = new System.Drawing.Point(59, 98);
             this.RemoveRubyButton.Name = "RemoveRubyButton";
-            this.RemoveRubyButton.Size = new System.Drawing.Size(81, 21);
+            this.RemoveRubyButton.Size = new System.Drawing.Size(46, 21);
             this.RemoveRubyButton.TabIndex = 24;
-            this.RemoveRubyButton.Text = "Remove Ruby";
+            this.RemoveRubyButton.Text = "-Ruby";
             this.RemoveRubyButton.UseVisualStyleBackColor = true;
             this.RemoveRubyButton.Click += new System.EventHandler(this.RemoveRubyButton_Click);
             // 
             // ZhCnButton
             // 
             this.ZhCnButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.ZhCnButton.Location = new System.Drawing.Point(665, 98);
+            this.ZhCnButton.Location = new System.Drawing.Point(719, 98);
             this.ZhCnButton.Name = "ZhCnButton";
             this.ZhCnButton.Size = new System.Drawing.Size(49, 21);
             this.ZhCnButton.TabIndex = 23;
-            this.ZhCnButton.Text = "Zh_CN";
+            this.ZhCnButton.Text = "ChiSim";
             this.ZhCnButton.UseVisualStyleBackColor = true;
             this.ZhCnButton.Click += new System.EventHandler(this.ZhCnButton_Click);
             // 
             // TrimButton
             // 
             this.TrimButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.TrimButton.Location = new System.Drawing.Point(608, 98);
+            this.TrimButton.Location = new System.Drawing.Point(662, 98);
             this.TrimButton.Name = "TrimButton";
             this.TrimButton.Size = new System.Drawing.Size(51, 21);
             this.TrimButton.TabIndex = 22;
@@ -311,11 +313,11 @@
             // 
             // InsertRubyButton
             // 
-            this.InsertRubyButton.Location = new System.Drawing.Point(165, 98);
+            this.InsertRubyButton.Location = new System.Drawing.Point(111, 98);
             this.InsertRubyButton.Name = "InsertRubyButton";
-            this.InsertRubyButton.Size = new System.Drawing.Size(83, 21);
+            this.InsertRubyButton.Size = new System.Drawing.Size(53, 21);
             this.InsertRubyButton.TabIndex = 18;
-            this.InsertRubyButton.Text = "Insert Ruby";
+            this.InsertRubyButton.Text = "+Ruby";
             this.InsertRubyButton.UseVisualStyleBackColor = true;
             this.InsertRubyButton.Click += new System.EventHandler(this.InsertRubyButton_Click);
             // 
@@ -381,6 +383,26 @@
             this.UpdateTimer.Interval = 20;
             this.UpdateTimer.Tick += new System.EventHandler(this.UpdateTimer_Tick);
             // 
+            // LeftTextButton
+            // 
+            this.LeftTextButton.Location = new System.Drawing.Point(198, 98);
+            this.LeftTextButton.Name = "LeftTextButton";
+            this.LeftTextButton.Size = new System.Drawing.Size(53, 21);
+            this.LeftTextButton.TabIndex = 18;
+            this.LeftTextButton.Text = "Text";
+            this.LeftTextButton.Click += new System.EventHandler(this.LeftTextButton_Click);
+            // 
+            // RightTextButton
+            // 
+            this.RightTextButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.RightTextButton.Location = new System.Drawing.Point(583, 98);
+            this.RightTextButton.Name = "RightTextButton";
+            this.RightTextButton.Size = new System.Drawing.Size(51, 21);
+            this.RightTextButton.TabIndex = 22;
+            this.RightTextButton.Text = "Text";
+            this.RightTextButton.UseVisualStyleBackColor = true;
+            this.RightTextButton.Click += new System.EventHandler(this.RightTextButton_Click);
+            // 
             // TextArrangeUserControl
             // 
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
@@ -392,11 +414,13 @@
             this.Controls.Add(this.LeftSelectAllButton);
             this.Controls.Add(this.RemoveRubyButton);
             this.Controls.Add(this.ZhCnButton);
+            this.Controls.Add(this.RightTextButton);
             this.Controls.Add(this.TrimButton);
             this.Controls.Add(this.SetEndButton);
             this.Controls.Add(this.SetBeginButton);
             this.Controls.Add(this.MoveForwardButton);
             this.Controls.Add(this.MoveBackwardButton);
+            this.Controls.Add(this.LeftTextButton);
             this.Controls.Add(this.InsertRubyButton);
             this.Controls.Add(splitContainer1);
             this.Controls.Add(this.CurrentTranslatedLabel);
@@ -449,5 +473,7 @@
         private System.Windows.Forms.Label PosLabel;
         private System.Windows.Forms.TrackBar PlayPosTrackBar;
         private System.Windows.Forms.Timer UpdateTimer;
+        private System.Windows.Forms.Button LeftTextButton;
+        private System.Windows.Forms.Button RightTextButton;
     }
 }

+ 52 - 30
BeatLyrics.Tool/UserControls/TextArrangeUserControl.cs

@@ -5,6 +5,7 @@ using Microsoft.International.Converters.TraditionalChineseToSimplifiedConverter
 using System;
 using System.Linq;
 using System.Windows.Forms;
+using BeatLyrics.Tool.Dialogs;
 
 namespace BeatLyrics.Tool.UserControls
 {
@@ -26,6 +27,9 @@ namespace BeatLyrics.Tool.UserControls
             LeftLyricsObjectListView.SetObjects(file.Main);
             RightLyricsObjectListView.SetObjects(file.Subtitle);
 
+            LeftTextButton.Enabled = !string.IsNullOrWhiteSpace(file.MainText);
+            RightTextButton.Enabled = !string.IsNullOrWhiteSpace(file.SubtitleText);
+
             UpdateTimer.Start();
         }
 
@@ -90,7 +94,6 @@ namespace BeatLyrics.Tool.UserControls
         {
             if (false == ActiveControl is TextBox && keyData == Keys.Space)
             {
-
                 if (OggAudioPlayer.IsPlaying) OggAudioPlayer.Stop();
                 else OggAudioPlayer.Play();
                 return true;
@@ -102,7 +105,6 @@ namespace BeatLyrics.Tool.UserControls
         {
             if (false == ActiveControl is TextBox && e.KeyCode == Keys.Space)
             {
-
             }
         }
 
@@ -137,6 +139,22 @@ namespace BeatLyrics.Tool.UserControls
             OggAudioPlayer.Position = TimeSpan.FromMilliseconds(PlayPosTrackBar.Value);
         }
 
+        private void PlayPosTrackBar_MouseDown(object sender, MouseEventArgs e)
+        {
+            if (OggAudioPlayer.IsPlaying) return;
+            OggAudioPlayer.Play();
+            PlayPosTrackBar.Tag = "Stop";
+        }
+
+        private void PlayPosTrackBar_MouseUp(object sender, MouseEventArgs e)
+        {
+            if (PlayPosTrackBar.Tag is string tag && tag == "Stop")
+            {
+                OggAudioPlayer.Stop();
+                PlayPosTrackBar.Tag = null;
+            }
+        }
+
         private void LeftSelectAllButton_Click(object sender, EventArgs e) => LeftLyricsObjectListView.SelectAll();
 
         private void LeftDeleteButton_Click(object sender, EventArgs e)
@@ -150,12 +168,27 @@ namespace BeatLyrics.Tool.UserControls
 
         private void RemoveRubyButton_Click(object sender, EventArgs e)
         {
-            //TODO: Ruby
+            var reg = new System.Text.RegularExpressions.Regex("(\\(\\p{IsHiragana}+\\))", System.Text.RegularExpressions.RegexOptions.Compiled);
+            foreach (LyricDetailExt item in LeftLyricsObjectListView.SelectedObjects)
+            {
+                item.Text = reg.Replace(item.Text, "");
+                LeftLyricsObjectListView.UpdateObject(item);
+            }
         }
 
         private void InsertRubyButton_Click(object sender, EventArgs e)
         {
-            //TODO: Ruby
+            foreach (LyricDetailExt item in LeftLyricsObjectListView.SelectedObjects)
+            {
+                var arr = Utils.JapanesePhonetic.GetWords(item.Text);
+                item.Text = string.Join("", arr.Select(p => p.ToString()));
+                LeftLyricsObjectListView.UpdateObject(item);
+            }
+        }
+
+        private void LeftTextButton_Click(object sender, EventArgs e)
+        {
+            new TextDialog { TextRichTextBox = { Text = _file.MainText } }.ShowDialog(this);
         }
 
         private void SetBeginButton_Click(object sender, EventArgs e) => SetPos(PlayPosTrackBar.Value);
@@ -166,6 +199,11 @@ namespace BeatLyrics.Tool.UserControls
 
         private void SetEndButton_Click(object sender, EventArgs e) => SetEnd(PlayPosTrackBar.Value);
 
+        private void RightTextButton_Click(object sender, EventArgs e)
+        {
+            new TextDialog { TextRichTextBox = { Text = _file.SubtitleText } }.ShowDialog(this);
+        }
+
         private void TrimButton_Click(object sender, EventArgs e)
         {
             if (_file.Subtitle == null) return;
@@ -178,6 +216,16 @@ namespace BeatLyrics.Tool.UserControls
             RightLyricsObjectListView.UpdateObjects(_file.Subtitle);
         }
 
+        private void ZhCnButton_Click(object sender, EventArgs e)
+        {
+            foreach (LyricDetailExt item in RightLyricsObjectListView.SelectedObjects)
+            {
+                item.Text = ChineseConverter.Convert(item.Text, ChineseConversionDirection.TraditionalToSimplified);
+            }
+
+            RightLyricsObjectListView.UpdateObjects(RightLyricsObjectListView.SelectedObjects);
+        }
+
         private void RightSelectAllButton_Click(object sender, EventArgs e) => RightLyricsObjectListView.SelectAll();
 
         private void RightDeleteButton_Click(object sender, EventArgs e)
@@ -200,31 +248,5 @@ namespace BeatLyrics.Tool.UserControls
             if (RightLyricsObjectListView.SelectedObject != null)
                 OggAudioPlayer.Position = TimeSpan.FromMilliseconds(((LyricDetail)RightLyricsObjectListView.SelectedObject).TimeMs);
         }
-
-        private void PlayPosTrackBar_MouseDown(object sender, MouseEventArgs e)
-        {
-            if (OggAudioPlayer.IsPlaying) return;
-            OggAudioPlayer.Play();
-            PlayPosTrackBar.Tag = "Stop";
-        }
-
-        private void PlayPosTrackBar_MouseUp(object sender, MouseEventArgs e)
-        {
-            if (PlayPosTrackBar.Tag is string tag && tag == "Stop")
-            {
-                OggAudioPlayer.Stop();
-                PlayPosTrackBar.Tag = null;
-            }
-        }
-
-        private void ZhCnButton_Click(object sender, EventArgs e)
-        {
-            foreach (LyricDetailExt item in RightLyricsObjectListView.SelectedObjects)
-            {
-                item.Text = ChineseConverter.Convert(item.Text, ChineseConversionDirection.TraditionalToSimplified);
-            }
-
-            RightLyricsObjectListView.UpdateObjects(RightLyricsObjectListView.SelectedObjects);
-        }
     }
 }

+ 14 - 1
BeatLyrics.Tool/Utils/OggAudioPlayer.cs

@@ -40,7 +40,20 @@ namespace BeatLyrics.Tool.Utils
             }
         }
 
-        public static TimeSpan Position { get => _waveSource.GetPosition(); set => _waveSource.SetPosition(value); }
+        public static TimeSpan Position
+        {
+            get => _waveSource.GetPosition();
+            set
+            {
+                if (value <= TimeSpan.Zero) _waveSource.SetPosition(TimeSpan.Zero);
+                else
+                {
+                    var len = _waveSource.GetLength();
+                    _waveSource.SetPosition(value > len ? len.Add(TimeSpan.FromMilliseconds(-1)) : value.Add(TimeSpan.FromMilliseconds(-1)));
+                }
+            }
+        }
+
         public static TimeSpan Length => _waveSource.GetLength();
 
         public static bool IsPlaying => SoundOut.PlaybackState == PlaybackState.Playing;

+ 13 - 6
BeatLyrics/Plugin.cs

@@ -75,6 +75,9 @@ namespace BeatLyrics
         private static readonly Vector3 Pos = new Vector3(0, 1.5f, 15f);
         private static readonly float FontSize = 20;
         private static readonly Color FontColor = new Color(1.0f, 1.0f, 1.0f, 0.4f);
+        private static readonly float OutlineWidth = 1;
+        private static readonly Color OutlineColor = new Color(1.0f, 1.0f, 1.0f, 0.8f);
+
 
         private readonly AudioTimeSyncController _audioTime;
 
@@ -111,6 +114,8 @@ namespace BeatLyrics
             _text.fontSize = FontSize;
             _text.enableWordWrapping = false;
             _text.color = FontColor;
+            _text.outlineColor = OutlineColor;
+            _text.outlineWidth = OutlineWidth;
 
             _text.text = "BeatLyrics";
 
@@ -124,7 +129,7 @@ namespace BeatLyrics
             _data = (GameplayCoreSceneSetupData)sceneSetup?.GetField("_sceneSetupData");
 
             //HACK: NJS Cheat
-            if (_data != null) //TODO: Create New Plugin and Load from Config
+            if (_data != null) //TODO: Create New Plugin  - OR -  Load from Config
             {
                 try
                 {
@@ -156,7 +161,7 @@ namespace BeatLyrics
 
                 if (beatLyricsDir != null)
                 {
-                    var path = GetDefaultFile(_data.difficultyBeatmap.level.levelID);
+                    var path = GetSetFilePath(_data.difficultyBeatmap.level.levelID);
                     if (File.Exists(path))
                     {
                         var jsonFileName = File.ReadAllText(path);
@@ -174,7 +179,7 @@ namespace BeatLyrics
                     }
                     else
                     {
-                        _debugInfo += "default file not found";
+                        _debugInfo += "set.txt file not found";
                     }
                 }
             }
@@ -200,7 +205,9 @@ namespace BeatLyrics
             else if (milliseconds < 1)
             {
                 var level = _data?.difficultyBeatmap.level;
-                _text.text = $"此处应有歌词,但是未能加载,{level?.levelID},{_debugInfo}";
+                _text.text = "此处应有歌词,但是未能加载" +
+                             $"{Environment.NewLine}{level?.levelID}" +
+                             $"{Environment.NewLine}{_debugInfo}";
             }
             else
             {
@@ -208,9 +215,9 @@ namespace BeatLyrics
             }
         }
 
-        public static string GetDefaultFile(string levelId)
+        public static string GetSetFilePath(string levelId)
         {
-            return Path.Combine(DirProvider.BeatLyricsDir, levelId + ".default.txt");
+            return Path.Combine(DirProvider.BeatLyricsDir, levelId + DirProvider.SuffixSet);
         }
 
     }