From fe181ca914aea8911c8046ea3e40321fe43ecc59 Mon Sep 17 00:00:00 2001 From: nsnail Date: Sat, 17 Dec 2022 11:29:42 +0800 Subject: [PATCH] .. --- dot.sln | 1 - dot.sln.DotSettings | 4 + src/FilesTool.cs | 1 - src/Git/Main.cs | 1 - src/GlobalUsings.cs | 5 +- src/Guid/Main.cs | 1 - src/Lang/Str.resx | 3 + src/OptionBase.cs | 3 + src/Program.cs | 4 +- src/Time/Main.cs | 1 - src/Tran/FrmMain.cs | 233 ++++++++++++++++++++++++++++++++++++++++++++ src/Tran/Main.cs | 20 ++++ src/Tran/Option.cs | 3 + src/Win32.cs | 14 ++- src/dot.csproj | 12 +-- 15 files changed, 289 insertions(+), 17 deletions(-) create mode 100644 src/Tran/FrmMain.cs create mode 100644 src/Tran/Main.cs create mode 100644 src/Tran/Option.cs diff --git a/dot.sln b/dot.sln index b89461e..d73e0c9 100644 --- a/dot.sln +++ b/dot.sln @@ -18,7 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{AD79881E-7 CodeQuality.props = CodeQuality.props Directory.Build.props = Directory.Build.props dot.sln.DotSettings = dot.sln.DotSettings - dot.sln.DotSettings.user = dot.sln.DotSettings.user dotnet-tools.json = dotnet-tools.json GenerateResx.targets = GenerateResx.targets git-clean.cmd = git-clean.cmd diff --git a/dot.sln.DotSettings b/dot.sln.DotSettings index aebd72b..24a4bdd 100644 --- a/dot.sln.DotSettings +++ b/dot.sln.DotSettings @@ -1,4 +1,8 @@ + True + True + True + True False 1 1 diff --git a/src/FilesTool.cs b/src/FilesTool.cs index cf9317d..b512d81 100644 --- a/src/FilesTool.cs +++ b/src/FilesTool.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using System.Globalization; using System.Text.RegularExpressions; // ReSharper disable once RedundantUsingDirective diff --git a/src/Git/Main.cs b/src/Git/Main.cs index c741bc6..8c5133f 100644 --- a/src/Git/Main.cs +++ b/src/Git/Main.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.Diagnostics; -using System.Globalization; using System.Text; using NSExt.Extensions; diff --git a/src/GlobalUsings.cs b/src/GlobalUsings.cs index 4c66c3e..06f8c4b 100644 --- a/src/GlobalUsings.cs +++ b/src/GlobalUsings.cs @@ -1,4 +1,3 @@ global using System.ComponentModel; -global using Dot.Lang; -global using Spectre.Console; -global using Spectre.Console.Cli; \ No newline at end of file +global using System.Globalization; +global using Dot.Lang; \ No newline at end of file diff --git a/src/Guid/Main.cs b/src/Guid/Main.cs index fe95cbf..d17b251 100644 --- a/src/Guid/Main.cs +++ b/src/Guid/Main.cs @@ -1,6 +1,5 @@ // ReSharper disable ClassNeverInstantiated.Global -using System.Globalization; #if NET7_0_WINDOWS using TextCopy; #endif diff --git a/src/Lang/Str.resx b/src/Lang/Str.resx index cc960be..9562803 100644 --- a/src/Lang/Str.resx +++ b/src/Lang/Str.resx @@ -264,4 +264,7 @@ 缓冲区大小(千字节) + + 翻译中... + \ No newline at end of file diff --git a/src/OptionBase.cs b/src/OptionBase.cs index 0b6f2b7..8ded1f3 100644 --- a/src/OptionBase.cs +++ b/src/OptionBase.cs @@ -1,5 +1,8 @@ // ReSharper disable UnusedAutoPropertyAccessor.Global +global using Spectre.Console; +global using Spectre.Console.Cli; + namespace Dot; internal abstract class OptionBase : CommandSettings, IOption diff --git a/src/Program.cs b/src/Program.cs index 66126a5..8f6f518 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,4 +1,3 @@ -using System.Globalization; using System.Text; using Dot.Git; #if NET7_0_WINDOWS @@ -7,7 +6,7 @@ using System.Runtime.InteropServices; namespace Dot; -internal sealed class Program +internal static class Program { public static int Main(string[] args) { @@ -21,6 +20,7 @@ internal sealed class Program #if NET7_0_WINDOWS if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { config.AddCommand(nameof(Color).ToLower(CultureInfo.InvariantCulture)); + config.AddCommand(nameof(Tran).ToLower(CultureInfo.InvariantCulture)); } #endif config.AddCommand(nameof(Guid).ToLower(CultureInfo.InvariantCulture)); diff --git a/src/Time/Main.cs b/src/Time/Main.cs index 39b2baa..dace916 100644 --- a/src/Time/Main.cs +++ b/src/Time/Main.cs @@ -1,6 +1,5 @@ // ReSharper disable ClassNeverInstantiated.Global -using System.Globalization; using System.Net.Sockets; namespace Dot.Time; diff --git a/src/Tran/FrmMain.cs b/src/Tran/FrmMain.cs new file mode 100644 index 0000000..b164407 --- /dev/null +++ b/src/Tran/FrmMain.cs @@ -0,0 +1,233 @@ +#if NET7_0_WINDOWS +using NSExt.Extensions; +using Colors = System.Drawing.Color; +using Padding = System.Windows.Forms.Padding; +using Size = System.Drawing.Size; + +namespace Dot.Tran; + +internal sealed class FrmMain : Form +{ + private const int _HIDING_MIL_SECS = 1000; // 鼠标离开超过此时间后隐藏窗体 + private const string _TOKEN_URL = "https://fanyi.qq.com/api/reauth12f"; + private const string _URL = "https://fanyi.qq.com/api/translate"; + private static ManualResetEvent _mre = new(false); // 隐藏窗体侦测线程信号 + + private readonly Colors _bgColor // 背景颜色 + = Colors.FromArgb(0xff, 0x1e, 0x1e, 0x1e); + + private readonly Colors _foreColor = Colors.White; // 字体颜色 + + private readonly HttpClient _httpClient = new(); + private readonly RichTextBox _richTextSource = new(); // 显示剪贴板内容的富文本框 + private readonly TextBox _textDest = new(); // 显示翻译内容的文本框 + private readonly Padding _windowPadding = new(10, 10, 10, 10); // 窗体边距 + private readonly Size _windowSize = new(640, 360); // 窗体大小 + private DateTime? _mouseLeaveTime; // 鼠标离开窗体时间 + private nint _nextHwnd; // 下一个剪贴板监视链对象句柄 + + public FrmMain() + { + InitForm(); + + InitTextSource(); + InitTextDest(); + + _nextHwnd = Win32.SetClipboardViewer(Handle); + + Task.Run(HideWindow); + } + + ~FrmMain() + { + _mre?.Dispose(); + _richTextSource?.Dispose(); + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + + // 结束定时隐藏窗体线程 + _mre = null; + + // 从剪贴板监视链移除本窗体 + Win32.ChangeClipboardChain(Handle, _nextHwnd); + } + + protected override void OnMouseEnter(EventArgs e) + { + base.OnMouseEnter(e); + _mouseLeaveTime = null; + } + + protected override void OnMouseLeave(EventArgs e) + { + base.OnMouseLeave(e); + if (ClientRectangle.Contains(PointToClient(MousePosition))) { + return; + } + + _mouseLeaveTime = DateTime.Now; + } + + protected override void WndProc(ref Message m) + { + void SendToNext(Message message) + { + _ = Win32.SendMessageA(_nextHwnd, (uint)message.Msg, message.WParam, message.LParam); + } + + switch (m.Msg) { + case Win32.WM_DRAWCLIPBOARD: + DisplayClipboardData(); + SendToNext(m); + break; + case Win32.WM_CHANGECBCHAIN: + if (m.WParam == _nextHwnd) { + _nextHwnd = m.LParam; + } + else { + SendToNext(m); + } + + break; + default: + base.WndProc(ref m); + break; + } + } + + /// + /// 显示剪贴板内容. + /// + private void DisplayClipboardData() + { + var clipData = Clipboard.GetDataObject(); + if (clipData is null) { + return; + } + + if (clipData.GetDataPresent(DataFormats.Rtf)) { + _richTextSource.Rtf = clipData.GetData(DataFormats.Rtf) as string; + } + else if (clipData.GetDataPresent(DataFormats.Text)) { + _richTextSource.Text = clipData.GetData(DataFormats.Text) as string; + } + else { + return; + } + + var mousePos = Cursor.Position; + mousePos.Offset(-_windowPadding.Left, -_windowPadding.Top); + Location = mousePos; + Visible = true; + _mouseLeaveTime = null; + _mre.Set(); + } + + private TokenStruct GetTranslateToken() + { + using var rsp = _httpClient.PostAsync(_TOKEN_URL, new StringContent(string.Empty)).Result; + var rspStr = rsp.Content.ReadAsStringAsync().Result; + return rspStr.Object(); + } + + private int HalfHeight() + { + return (_windowSize.Height - _windowPadding.Top - _windowPadding.Bottom) / 2 - _windowPadding.Top / 2; + } + + private async Task HideWindow() + { + while (_mre is not null) { + while (Visible) { + await Task.Delay(100); + if (_mouseLeaveTime is null || + !((DateTime.Now - _mouseLeaveTime.Value).TotalMilliseconds > _HIDING_MIL_SECS)) { + continue; + } + + Invoke(() => { + Visible = false; + _richTextSource.Clear(); + _textDest.Clear(); + }); + } + + _mre.WaitOne(); + } + } + + private void InitForm() + { + BackColor = _bgColor; + FormBorderStyle = FormBorderStyle.None; + Padding = _windowPadding; + Size = _windowSize; + TopMost = true; + Visible = false; + } + + private void InitTextDest() + { + _textDest.BackColor = _bgColor; + _textDest.BorderStyle = BorderStyle.None; + _textDest.Dock = DockStyle.Bottom; + _textDest.ForeColor = _foreColor; + _textDest.Height = HalfHeight(); + _textDest.Margin = new Padding(0, _windowPadding.Top / 2, 0, 0); + _textDest.MouseEnter += (_, e) => OnMouseEnter(e); + _textDest.Multiline = true; + _textDest.ScrollBars = ScrollBars.None; + Controls.Add(_textDest); + } + + private void InitTextSource() + { + _richTextSource.TextChanged += (sender, e) => { + if (_richTextSource.Text.NullOrWhiteSpace()) { + return; + } + + _textDest.Text = Str.Translating; + _textDest.Text = TranslateText(_richTextSource.Text); + }; + _richTextSource.BackColor = _bgColor; + _richTextSource.BorderStyle = BorderStyle.None; + _richTextSource.Dock = DockStyle.Top; + _richTextSource.ForeColor = _foreColor; + _richTextSource.Height = HalfHeight(); + _richTextSource.Margin = new Padding(0, 0, 0, _windowPadding.Top / 2); + _richTextSource.MouseEnter += (_, e) => OnMouseEnter(e); + _richTextSource.ScrollBars = RichTextBoxScrollBars.None; + Controls.Add(_richTextSource); + } + + private string TranslateText(string sourceText) + { + var token = GetTranslateToken(); + var content = new FormUrlEncodedContent(new List> { + new("source", "auto") + , new("target", "zh") + , new("qtv", token.Qtv.Url()) + , new("qtk", token.Qtk.Url()) + , new("sourceText", sourceText.Url()) + }); + var rsp = _httpClient.PostAsync(_URL, content).Result; + var ret = rsp.Content.ReadAsStringAsync().Result; + + return ret; + } + + private readonly struct TokenStruct + { + // ReSharper disable UnusedAutoPropertyAccessor.Local + public string Qtk { get; init; } + + public string Qtv { get; init; } + + // ReSharper restore UnusedAutoPropertyAccessor.Local + } +} +#endif \ No newline at end of file diff --git a/src/Tran/Main.cs b/src/Tran/Main.cs new file mode 100644 index 0000000..e4713e5 --- /dev/null +++ b/src/Tran/Main.cs @@ -0,0 +1,20 @@ +#if NET7_0_WINDOWS +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Dot.Tran; + +internal sealed class Main : ToolBase