diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 514957577..db34f23bb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,42 +1,42 @@ -name: Publish Terminal.Gui v2 +name: Publish Terminal.Gui + on: push: + branches: [ main, develop, v2_release, v2_develop ] tags: - - v2.0.0-alpha.* + - v* + paths-ignore: + - '**.md' jobs: publish: - name: Build and Publish v2 to Nuget.org + name: Build and Publish to Nuget.org runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: - fetch-depth: 0 #fetch-depth is needed for GitVersion + fetch-depth: 0 # fetch-depth is needed for GitVersion - - name: Install and calculate the new version with GitVersion + - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0 with: - versionSpec: '6.x' + versionSpec: '5.x' includePrerelease: true - name: Determine Version uses: gittools/actions/gitversion/execute@v0 with: - useConfigFile: true + useConfigFile: true + #additionalArguments: /b develop id: gitversion # step id used as reference for output values - - name: Display GitVersion outputs - run: | - echo "Version: ${{ steps.gitversion.outputs.SemVer }}" - echo "CommitsSinceVersionSource: ${{ steps.gitversion.outputs.CommitsSinceVersionSource }}" - - name: Setup dotnet uses: actions/setup-dotnet@v3 with: dotnet-version: 7.0 dotnet-quality: 'ga' - + - name: Install dependencies run: dotnet restore @@ -48,34 +48,27 @@ jobs: - name: Pack run: dotnet pack -c Release --include-symbols -p:Version='${{ steps.gitversion.outputs.SemVer }}' - #- name: Test to generate Code Coverage Report - # run: | - # sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json - # dotnet test --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings - # mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/ + # - name: Test to generate Code Coverage Report + # run: | + # sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json + # dotnet test --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings + # mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/ - #- name: Create Test Coverage Badge - # uses: simon-k/dotnet-code-coverage-badge@v1.0.0 - # id: create_coverage_badge - # with: - # label: Unit Test Coverage - # color: brightgreen - # path: UnitTests/TestResults/coverage.opencover.xml - # gist-filename: code-coverage.json - # # https://gist.github.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27 - # gist-id: 90ef67a684cb71db1817921a970f8d27 - # gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }} - - #- name: Print Code Coverage - # run: | - # echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%" - # echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}" + # - name: Create Test Coverage Badge + # uses: simon-k/dotnet-code-coverage-badge@v1.0.0 + # id: create_coverage_badge + # with: + # label: Unit Test Coverage + # color: brightgreen + # path: UnitTests/TestResults/coverage.opencover.xml + # gist-filename: code-coverage.json + # gist-id: 90ef67a684cb71db1817921a970f8d27 + # gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }} + # - name: Print Code Coverage + # run: | + # echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%" + # echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}" + - name: Publish to NuGet.org - run: dotnet nuget push Terminal.Gui/bin/Release/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json - - - name: Unlist from NuGet.org if it's an alpha - run: dotnet nuget delete --non-interactive Terminal.Gui ${{ steps.gitversion.outputs.SemVer }} --api-key ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json - if: contains(steps.gitversion.outputs.SemVer, 'alpha') - - + run: dotnet nuget push Terminal.Gui/bin/Release/Terminal.Gui.${{ steps.gitversion.outputs.SemVer }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} diff --git a/GitVersion.yml b/GitVersion.yml index f77b10ac2..d5f370a83 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,21 +1,14 @@ mode: ContinuousDeployment tag-prefix: '[vV]' -continuous-delivery-fallback-tag: 'pre' +continuous-delivery-fallback-tag: pre branches: - # v1_develop: - # mode: ContinuousDeployment - # tag: pre - # regex: ^v1_develop?[/-] - # is-release-branch: false - # source-branches: - # - v1 - # v1: - # tag: rc - # increment: Patch - # regex: ^v2?[/-] - # is-release-branch: false - # source-branches: [] - # is-mainline: true + develop: + mode: ContinuousDeployment + tag: pre + regex: develop + source-branches: + - main + pre-release-weight: 100 v2_develop: mode: ContinuousDeployment @@ -23,28 +16,76 @@ branches: regex: ^v2_develop?[/-] is-release-branch: true tracks-release-branches: true - is-source-branch-for: ['v2'] + #is-source-branch-for: ['v2'] source-branches: [] - v2: - mode: ContinuousDeployment - is-release-branch: false - tag: alpha - increment: Patch - regex: ^v2?[/-] - source-branches: ['v2_develop'] - # feature: - # tag: useBranchName - # regex: ^features?[/-] - # source-branches: - # - v1 - # - v1_develop - # - v2 - # - v2_develop - + main: + tag: rc + increment: Patch + source-branches: + - develop + - main + feature: + tag: useBranchName + regex: ^features?[/-] + source-branches: + - develop + - main pull-request: tag: PullRequest.{BranchName} increment: Inherit ignore: sha: [] -merge-message-formats: {} + + +# next-version: 2.0.0 +# mode: ContinuousDeployment +# tag-prefix: '[vV]' +# continuous-delivery-fallback-tag: 'pre' +# branches: +# # v1_develop: +# # mode: ContinuousDeployment +# # tag: pre +# # regex: ^v1_develop?[/-] +# # is-release-branch: false +# # source-branches: +# # - v1 +# # v1: +# # tag: rc +# # increment: Patch +# # regex: ^v2?[/-] +# # is-release-branch: false +# # source-branches: [] +# # is-mainline: true + +# v2_develop: +# mode: ContinuousDeployment +# tag: pre +# regex: ^v2_develop?[/-] +# is-release-branch: true +# tracks-release-branches: true +# is-source-branch-for: ['v2'] +# source-branches: [] +# v2: +# mode: ContinuousDeployment +# is-release-branch: false +# tag: alpha +# increment: Patch +# regex: ^v2?[/-] +# source-branches: ['v2_develop'] + +# # feature: +# # tag: useBranchName +# # regex: ^features?[/-] +# # source-branches: +# # - v1 +# # - v1_develop +# # - v2 +# # - v2_develop + +# pull-request: +# tag: PullRequest.{BranchName} +# increment: Inherit +# ignore: +# sha: [] +# merge-message-formats: {} diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index cbf26d6ef..37f4447e8 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -360,7 +360,6 @@ public abstract class ConsoleDriver { #region Color Handling - /// /// Gets whether the supports TrueColor output. /// @@ -380,7 +379,6 @@ public abstract class ConsoleDriver { get => _force16Colors || !SupportsTrueColor; set { _force16Colors = (value || !SupportsTrueColor); - Refresh (); } } @@ -533,7 +531,7 @@ public abstract class ConsoleDriver { /// Ends the execution of the console driver. /// public abstract void End (); - + /// /// Returns the name of the driver and relevant library version information. /// diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index f1f0646dc..e84cc408f 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -777,21 +777,11 @@ internal class WindowsDriver : ConsoleDriver { public WindowsConsole WinConsole { get; private set; } - public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931); - - public override bool Force16Colors { - get => base.Force16Colors; - set { - base.Force16Colors = value; - // BUGBUG: This is a hack until we fully support VirtualTerminalSequences - if (WinConsole != null) { - WinConsole = new WindowsConsole (); - } - Refresh (); - } - } + public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931 + && (_isWindowsTerminal || _parentProcessName == "devenv")); readonly bool _isWindowsTerminal = false; + readonly string _parentProcessName = "WindowsTerminal"; public WindowsDriver () { @@ -803,8 +793,52 @@ internal class WindowsDriver : ConsoleDriver { Clipboard = new FakeDriver.FakeClipboard (); } - _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") != null; + if (!RunningUnitTests) { + _parentProcessName = GetParentProcessName (); + _isWindowsTerminal = _parentProcessName == "WindowsTerminal"; + if (!_isWindowsTerminal && _parentProcessName != "devenv") { + Force16Colors = true; + } + } + } + private static string GetParentProcessName () + { +#pragma warning disable CA1416 // Validate platform compatibility + var myId = Process.GetCurrentProcess ().Id; + var query = string.Format ($"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {myId}"); + var search = new ManagementObjectSearcher ("root\\CIMV2", query); + var queryObj = search.Get ().OfType ().FirstOrDefault (); + if (queryObj == null) { + return null; + } + var parentId = (uint)queryObj ["ParentProcessId"]; + var parent = Process.GetProcessById ((int)parentId); + var prevParent = parent; + + // Check if the parent is from other parent + while (queryObj != null) { + query = string.Format ($"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {parentId}"); + search = new ManagementObjectSearcher ("root\\CIMV2", query); + queryObj = search.Get ().OfType ().FirstOrDefault (); + if (queryObj == null) { + return parent.ProcessName; + } + parentId = (uint)queryObj ["ParentProcessId"]; + try { + parent = Process.GetProcessById ((int)parentId); + if (string.Equals (parent.ProcessName, "explorer", StringComparison.InvariantCultureIgnoreCase)) { + return prevParent.ProcessName; + } + prevParent = parent; + } catch (ArgumentException) { + + return prevParent.ProcessName; + } + } + + return parent.ProcessName; +#pragma warning restore CA1416 // Validate platform compatibility } public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) @@ -1454,13 +1488,13 @@ internal class WindowsDriver : ConsoleDriver { if (RunningUnitTests) { return; } - + try { if (WinConsole != null) { var winSize = WinConsole.GetConsoleOutputWindow (out Point pos); Cols = winSize.Width; Rows = winSize.Height; - } + } WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); // Needed for Windows Terminal @@ -1511,8 +1545,7 @@ internal class WindowsDriver : ConsoleDriver { WinConsole?.ForceRefreshCursorVisibility (); } - - + public override void UpdateScreen () { var windowSize = WinConsole?.GetConsoleBufferWindow (out _) ?? new Size (Cols, Rows); @@ -1709,9 +1742,9 @@ internal class WindowsDriver : ConsoleDriver { WinConsole?.Cleanup (); WinConsole = null; - if (!RunningUnitTests && _isWindowsTerminal) { + if (!RunningUnitTests && (_isWindowsTerminal || _parentProcessName == "devenv")) { // Disable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_RestoreAltBufferWithBackscroll); + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndActivateAltBufferWithBackscroll); } } @@ -1797,8 +1830,8 @@ internal class WindowsMainLoop : IMainLoopDriver { while (true) { Task.Delay (500).Wait (); _windowSize = _winConsole.GetConsoleBufferWindow (out _); - if (_windowSize != Size.Empty && _windowSize.Width != _consoleDriver.Cols - || _windowSize.Height != _consoleDriver.Rows) { + if (_windowSize != Size.Empty && (_windowSize.Width != _consoleDriver.Cols + || _windowSize.Height != _consoleDriver.Rows)) { return; } } diff --git a/Terminal.Gui/FileServices/DefaultFileOperations.cs b/Terminal.Gui/FileServices/DefaultFileOperations.cs index b2512cea6..c626dfaec 100644 --- a/Terminal.Gui/FileServices/DefaultFileOperations.cs +++ b/Terminal.Gui/FileServices/DefaultFileOperations.cs @@ -24,7 +24,7 @@ namespace Terminal.Gui { int result = MessageBox.Query ( string.Format (Strings.fdDeleteTitle, adjective), string.Format (Strings.fdDeleteBody, adjective), - Strings.fdYes, Strings.fdNo); + Strings.btnYes, Strings.btnNo); try { if (result == 0) { @@ -37,7 +37,7 @@ namespace Terminal.Gui { return true; } } catch (Exception ex) { - MessageBox.ErrorQuery (Strings.fdDeleteFailedTitle, ex.Message, "Ok"); + MessageBox.ErrorQuery (Strings.fdDeleteFailedTitle, ex.Message, Strings.btnOk); } return false; @@ -47,14 +47,14 @@ namespace Terminal.Gui { { bool confirm = false; - var btnOk = new Button ("Ok") { + var btnOk = new Button (Strings.btnOk) { IsDefault = true, }; btnOk.Clicked += (s, e) => { confirm = true; Application.RequestStop (); }; - var btnCancel = new Button ("Cancel"); + var btnCancel = new Button (Strings.btnCancel); btnCancel.Clicked += (s, e) => { confirm = false; Application.RequestStop (); diff --git a/Terminal.Gui/Resources/Strings.Designer.cs b/Terminal.Gui/Resources/Strings.Designer.cs index 00d978a76..a71f357ce 100644 --- a/Terminal.Gui/Resources/Strings.Designer.cs +++ b/Terminal.Gui/Resources/Strings.Designer.cs @@ -60,6 +60,69 @@ namespace Terminal.Gui.Resources { } } + /// + /// Looks up a localized string similar to Cancel. + /// + internal static string btnCancel { + get { + return ResourceManager.GetString("btnCancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No. + /// + internal static string btnNo { + get { + return ResourceManager.GetString("btnNo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to OK. + /// + internal static string btnOk { + get { + return ResourceManager.GetString("btnOk", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open. + /// + internal static string btnOpen { + get { + return ResourceManager.GetString("btnOpen", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save. + /// + internal static string btnSave { + get { + return ResourceManager.GetString("btnSave", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save as. + /// + internal static string btnSaveAs { + get { + return ResourceManager.GetString("btnSaveAs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Yes. + /// + internal static string btnYes { + get { + return ResourceManager.GetString("btnYes", resourceCulture); + } + } + /// /// Looks up a localized string similar to _Copy. /// @@ -267,15 +330,6 @@ namespace Terminal.Gui.Resources { } } - /// - /// Looks up a localized string similar to No. - /// - internal static string fdNo { - get { - return ResourceManager.GetString("fdNo", resourceCulture); - } - } - /// /// Looks up a localized string similar to Open. /// @@ -393,15 +447,6 @@ namespace Terminal.Gui.Resources { } } - /// - /// Looks up a localized string similar to Yes. - /// - internal static string fdYes { - get { - return ResourceManager.GetString("fdYes", resourceCulture); - } - } - /// /// Looks up a localized string similar to _Back. /// diff --git a/Terminal.Gui/Resources/Strings.fr-FR.resx b/Terminal.Gui/Resources/Strings.fr-FR.resx index 28a48dc1d..e6dcfcaea 100644 --- a/Terminal.Gui/Resources/Strings.fr-FR.resx +++ b/Terminal.Gui/Resources/Strings.fr-FR.resx @@ -142,16 +142,16 @@ _Dossier - _Ficher + Ficher - _Enregistrer + Enregistrer - E_nregistrer sous + Enregistrer sous - _Ouvrir + Ouvrir Sélection de _dossier @@ -168,4 +168,13 @@ Prochai_n... + + Enregistrer + + + E_nregistrer sous + + + Ouvrir + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.ja-JP.resx b/Terminal.Gui/Resources/Strings.ja-JP.resx index 68b15dda8..35d0e2bf8 100644 --- a/Terminal.Gui/Resources/Strings.ja-JP.resx +++ b/Terminal.Gui/Resources/Strings.ja-JP.resx @@ -118,25 +118,25 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - コピー (C) + コピー (_C) - 切り取り (T) + 切り取り (_T) - 全て削除 (D) + 全て削除 (_D) - 貼り付け (P) + 貼り付け (_P) - やり直し (R) + やり直し (_R) - 全て選択 (S) + 全て選択 (_S) - 元に戻す (U) + 元に戻す (_U) ディレクトリ @@ -154,18 +154,105 @@ 開く - フォルダーを選択 + フォルダーを選択 (_S) - 混在選択 + 混在選択 (_S) - 戻る + 戻る (_B) - 終える + 終わる (_N) - 次に + 次に (_N)... + + + 同じ名前のディレクトリはすでに存在しました + + + “{0}”を削除もよろしいですか?この操作は元に戻りません + + + タイプ + + + サイズ + + + パスを入力 + + + ファイル名 + + + 新規ディレクトリ + + + いいえ (_N) + + + はい (_Y) + + + 変更日時 + + + すでに存在したファイルまたはディレクトリを選択してください + + + すでに存在したディレクトリを選択してください + + + すでに存在したファイルを選択してください + + + 名前: + + + {0} を削除 + + + 新規失敗 + + + 既存 + + + 名前を変更 + + + 変更失敗 + + + 削除失敗 + + + 同じ名前のファイルはすでに存在しました + + + 検索を入力 + + + ファイルタイプが間違っでいます + + + 任意ファイル + + + キャンセル (_C) + + + OK (_O) + + + 開く (_O) + + + 保存 (_S) + + + 名前を付けて保存 (_S) \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.pt-PT.resx b/Terminal.Gui/Resources/Strings.pt-PT.resx index a8385c556..bfd60ce15 100644 --- a/Terminal.Gui/Resources/Strings.pt-PT.resx +++ b/Terminal.Gui/Resources/Strings.pt-PT.resx @@ -168,4 +168,13 @@ S_eguir + + Guardar como + + + Guardar + + + Abrir + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.resx b/Terminal.Gui/Resources/Strings.resx index 873c6184d..887431f43 100644 --- a/Terminal.Gui/Resources/Strings.resx +++ b/Terminal.Gui/Resources/Strings.resx @@ -226,7 +226,7 @@ New Folder - + No @@ -238,10 +238,25 @@ Rename - + Yes Existing + + Open + + + Save + + + Save as + + + OK + + + Cancel + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.zh-Hans.resx b/Terminal.Gui/Resources/Strings.zh-Hans.resx new file mode 100644 index 000000000..b0358523f --- /dev/null +++ b/Terminal.Gui/Resources/Strings.zh-Hans.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 全选 (_S) + + + 清空 (_D) + + + 复制 (_C) + + + 剪切 (_T) + + + 粘贴 (_P) + + + 撤销 (_U) + + + 重做 (_R) + + + 目录 + + + 文件 + + + 保存 + + + 另存为 + + + 打开 + + + 下一步 (_N)... + + + 选择文件夹 (_S) + + + 混合选择 (_S) + + + 返回 (_B) + + + 结束 (_N) + + + 已存在相同名称的目录 + + + 必须选择已有的目录 + + + 已存在相同名称的文件 + + + 必须选择已有的文件 + + + 文件名 + + + 必须选择已有的文件或目录 + + + 修改时间 + + + 请输入路径 + + + 输入以搜索 + + + 大小 + + + 类型 + + + 文件类型有误 + + + 任意文件 + + + 您是否要删除“{0}”?此操作不可撤销 + + + 删除失败 + + + 删除 {0} + + + 新建失败 + + + 新建文件夹 + + + 否 (_N) + + + 重命名失败 + + + 名称: + + + 重命名 + + + 是 (_Y) + + + 已有 + + + 确定 (_O) + + + 打开 (_O) + + + 保存 (_S) + + + 另存为 (_S) + + + 取消 (_C) + + \ No newline at end of file diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index bf5b9adf2..e71fdf9c6 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -154,7 +154,7 @@ namespace Terminal.Gui { this.NavigateIf (k, Key.CursorUp, this.tableView); }; - this.btnCancel = new Button ("Cancel") { + this.btnCancel = new Button (Strings.btnCancel) { Y = Pos.AnchorEnd (1), X = Pos.Function (() => this.Bounds.Width @@ -710,20 +710,32 @@ namespace Terminal.Gui { this.tbPath.SelectAll (); if (string.IsNullOrEmpty (Title)) { - switch (OpenMode) { - case OpenMode.File: - this.Title = $"{Strings.fdOpen} {(MustExist ? Strings.fdExisting + " " : "")}{Strings.fdFile}"; - break; - case OpenMode.Directory: - this.Title = $"{Strings.fdOpen} {(MustExist ? Strings.fdExisting + " " : "")}{Strings.fdDirectory}"; - break; - case OpenMode.Mixed: - this.Title = $"{Strings.fdOpen} {(MustExist ? Strings.fdExisting : "")}"; - break; - } + this.Title = GetDefaultTitle (); } this.LayoutSubviews (); } + /// + /// Gets a default dialog title, when is not set or empty, + /// result of the function will be shown. + /// + protected virtual string GetDefaultTitle () + { + List titleParts = new () { + Strings.fdOpen + }; + if (MustExist) { + titleParts.Add (Strings.fdExisting); + } + switch (OpenMode) { + case OpenMode.File: + titleParts.Add (Strings.fdFile); + break; + case OpenMode.Directory: + titleParts.Add (Strings.fdDirectory); + break; + } + return string.Join (' ', titleParts); + } private void AllowedTypeMenuClicked (int idx) { diff --git a/Terminal.Gui/Views/OpenDialog.cs b/Terminal.Gui/Views/OpenDialog.cs index 4e16831fe..674a585ee 100644 --- a/Terminal.Gui/Views/OpenDialog.cs +++ b/Terminal.Gui/Views/OpenDialog.cs @@ -55,8 +55,7 @@ namespace Terminal.Gui { /// To select more than one file, users can use the spacebar, or control-t. /// /// - public class OpenDialog : FileDialog { - + public class OpenDialog : FileDialog { /// /// Initializes a new . /// @@ -70,9 +69,9 @@ namespace Terminal.Gui { /// The open mode. public OpenDialog (string title, List allowedTypes = null, OpenMode openMode = OpenMode.File) { - this.OpenMode = openMode; + OpenMode = openMode; Title = title; - Style.OkButtonText = openMode == OpenMode.File ? Strings.fdOpen : openMode == OpenMode.Directory ? Strings.fdSelectFolder : Strings.fdSelectMixed; + Style.OkButtonText = openMode == OpenMode.File ? Strings.btnOpen : openMode == OpenMode.Directory ? Strings.fdSelectFolder : Strings.fdSelectMixed; if (allowedTypes != null) { AllowedTypes = allowedTypes; diff --git a/Terminal.Gui/Views/SaveDialog.cs b/Terminal.Gui/Views/SaveDialog.cs index b5d689311..e357e704f 100644 --- a/Terminal.Gui/Views/SaveDialog.cs +++ b/Terminal.Gui/Views/SaveDialog.cs @@ -42,7 +42,7 @@ namespace Terminal.Gui { { //: base (title, prompt: Strings.fdSave, nameFieldLabel: $"{Strings.fdSaveAs}:", message: message, allowedTypes) { } Title = title; - Style.OkButtonText = Strings.fdSave; + Style.OkButtonText = Strings.btnSave; if(allowedTypes != null) { AllowedTypes = allowedTypes; @@ -62,5 +62,24 @@ namespace Terminal.Gui { return Path; } } + + protected override string GetDefaultTitle () + { + List titleParts = new () { + Strings.fdSave + }; + if (MustExist) { + titleParts.Add (Strings.fdExisting); + } + switch (OpenMode) { + case OpenMode.File: + titleParts.Add (Strings.fdFile); + break; + case OpenMode.Directory: + titleParts.Add (Strings.fdDirectory); + break; + } + return string.Join (' ', titleParts); + } } } diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 8fb9a2156..1d66b853d 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -27,11 +27,13 @@ namespace Terminal.Gui { /// Shortcut to activate the . /// Title for the . /// Action to invoke when the is activated. - public StatusItem (Key shortcut, string title, Action action) + /// Function to determine if the action can currently be executed. + public StatusItem (Key shortcut, string title, Action action, Func canExecute = null) { Title = title ?? ""; Shortcut = shortcut; Action = action; + CanExecute = canExecute; } /// @@ -54,7 +56,22 @@ namespace Terminal.Gui { /// Gets or sets the action to be invoked when the statusbar item is triggered /// /// Action to invoke. - public Action Action { get; } + public Action Action { get; set; } + + /// + /// Gets or sets the action to be invoked to determine if the can be triggered. + /// If returns the status item will be enabled. Otherwise, it will be disabled. + /// + /// Function to determine if the action is can be executed or not. + public Func CanExecute { get; set; } + + /// + /// Returns if the status item is enabled. This method is a wrapper around . + /// + public bool IsEnabled () + { + return CanExecute == null ? true : CanExecute (); + } /// /// Gets or sets arbitrary data for the status item. @@ -116,6 +133,17 @@ namespace Terminal.Gui { return result; } + Attribute DetermineColorSchemeFor (StatusItem item) + { + if (item != null) { + if (item.IsEnabled ()) { + return GetNormalColor (); + } + return ColorScheme.Disabled; + } + return GetNormalColor (); + } + /// public override void OnDrawContent (Rect contentArea) { @@ -130,9 +158,12 @@ namespace Terminal.Gui { Driver.SetAttribute (scheme); for (int i = 0; i < Items.Length; i++) { var title = Items [i].Title; + Driver.SetAttribute (DetermineColorSchemeFor (Items [i])); for (int n = 0; n < Items [i].Title.GetRuneCount (); n++) { if (title [n] == '~') { - scheme = ToggleScheme (scheme); + if (Items [i].IsEnabled ()) { + scheme = ToggleScheme (scheme); + } continue; } Driver.AddRune ((Rune)title [n]); @@ -150,7 +181,9 @@ namespace Terminal.Gui { { foreach (var item in Items) { if (kb.Key == item.Shortcut) { - Run (item.Action); + if (item.IsEnabled ()) { + Run (item.Action); + } return true; } } @@ -166,7 +199,10 @@ namespace Terminal.Gui { int pos = 1; for (int i = 0; i < Items.Length; i++) { if (me.X >= pos && me.X < pos + GetItemTitleLength (Items [i].Title)) { - Run (Items [i].Action); + var item = Items [i]; + if (item.IsEnabled ()) { + Run (item.Action); + } break; } pos += GetItemTitleLength (Items [i].Title) + 3; diff --git a/Terminal.sln b/Terminal.sln index 2474410eb..990626580 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\dotnet-core.yml = .github\workflows\dotnet-core.yml GitVersion.yml = GitVersion.yml global.json = global.json + nuget.config = nuget.config .github\workflows\publish.yml = .github\workflows\publish.yml README.md = README.md Terminal.sln.DotSettings = Terminal.sln.DotSettings diff --git a/UICatalog/Scenarios/Localization.cs b/UICatalog/Scenarios/Localization.cs new file mode 100644 index 000000000..fe2fca3ac --- /dev/null +++ b/UICatalog/Scenarios/Localization.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using Terminal.Gui; + +namespace UICatalog.Scenarios { + [ScenarioMetadata (Name: "Localization", Description: "Test for localization resources.")] + [ScenarioCategory ("Text and Formatting")] + [ScenarioCategory ("Tests")] + public class Localization : Scenario { + public CultureInfo CurrentCulture { get; private set; } = Thread.CurrentThread.CurrentUICulture; + + private CultureInfo [] _cultureInfoSource; + private string [] _cultureInfoNameSource; + private ComboBox _languageComboBox; + private CheckBox _allowAnyCheckBox; + private OpenMode _currentOpenMode = OpenMode.File; + + public override void Setup () + { + base.Setup (); + _cultureInfoSource = Application.SupportedCultures.Append (CultureInfo.InvariantCulture).ToArray (); + _cultureInfoNameSource = Application.SupportedCultures.Select (c => $"{c.NativeName} ({c.Name})").Append ("Invariant").ToArray (); + var languageMenus = Application.SupportedCultures + .Select (c => new MenuItem ($"{c.NativeName} ({c.Name})", "", () => SetCulture (c))) + .Concat ( + new MenuItem [] { + null, + new MenuItem ("Invariant", "", () => SetCulture (CultureInfo.InvariantCulture)) + } + ) + .ToArray (); + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_File", new MenuItem [] { + new MenuBarItem("_Language", languageMenus), + null, + new MenuItem ("_Quit", "", Quit), + }), + }); + Application.Top.Add (menu); + + var selectLanguageLabel = new Label ("Please select a language.") { + X = 2, + Y = 1, + Width = Dim.Fill (2), + AutoSize = true + }; + Win.Add (selectLanguageLabel); + + _languageComboBox = new ComboBox (_cultureInfoNameSource) { + X = 2, + Y = Pos.Bottom (selectLanguageLabel) + 1, + Width = _cultureInfoNameSource.Select (cn => cn.Length + 3).Max (), + Height = _cultureInfoNameSource.Length + 1, + HideDropdownListOnClick = true, + AutoSize = true, + SelectedItem = _cultureInfoNameSource.Length - 1 + }; + _languageComboBox.SelectedItemChanged += LanguageComboBox_SelectChanged; + Win.Add (_languageComboBox); + + var textAndFileDialogLabel = new Label ("Right click on the text field to open a context menu, click the button to open a file dialog.\r\nOpen mode will loop through 'File', 'Directory' and 'Mixed' as 'Open' or 'Save' button clicked.") { + X = 2, + Y = Pos.Top (_languageComboBox) + 3, + Width = Dim.Fill (2), + AutoSize = true + }; + Win.Add (textAndFileDialogLabel); + + var textField = new TextView { + X = 2, + Y = Pos.Bottom (textAndFileDialogLabel) + 1, + Width = Dim.Fill (32), + Height = 1 + }; + Win.Add (textField); + + _allowAnyCheckBox = new CheckBox { + X = Pos.Right (textField) + 1, + Y = Pos.Bottom (textAndFileDialogLabel) + 1, + Checked = false, + Text = "Allow any" + }; + Win.Add (_allowAnyCheckBox); + + var openDialogButton = new Button ("Open") { + X = Pos.Right (_allowAnyCheckBox) + 1, + Y = Pos.Bottom (textAndFileDialogLabel) + 1 + }; + openDialogButton.Clicked += (sender, e) => ShowFileDialog (false); + Win.Add (openDialogButton); + + var saveDialogButton = new Button ("Save") { + X = Pos.Right (openDialogButton) + 1, + Y = Pos.Bottom (textAndFileDialogLabel) + 1 + }; + saveDialogButton.Clicked += (sender, e) => ShowFileDialog (true); + Win.Add (saveDialogButton); + + var wizardLabel = new Label ("Click the button to open a wizard.") { + X = 2, + Y = Pos.Bottom (textField) + 1, + Width = Dim.Fill (2), + AutoSize = true + }; + Win.Add (wizardLabel); + + var wizardButton = new Button ("Open _wizard") { + X = 2, + Y = Pos.Bottom (wizardLabel) + 1 + }; + wizardButton.Clicked += (sender, e) => ShowWizard (); + Win.Add (wizardButton); + + Win.Unloaded += (sender, e) => Quit (); + } + + public void SetCulture (CultureInfo culture) + { + if (_cultureInfoSource [_languageComboBox.SelectedItem] != culture) { + _languageComboBox.SelectedItem = Array.IndexOf (_cultureInfoSource, culture); + } + if (this.CurrentCulture == culture) return; + this.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; + Application.Refresh (); + } + private void LanguageComboBox_SelectChanged (object sender, ListViewItemEventArgs e) + { + if (e.Value is string cultureName) { + var index = Array.IndexOf (_cultureInfoNameSource, cultureName); + if (index >= 0) { + SetCulture (_cultureInfoSource [index]); + } + } + } + public void ShowFileDialog (bool isSaveFile) + { + FileDialog dialog = isSaveFile ? new SaveDialog () : new OpenDialog ("", null, _currentOpenMode); + dialog.AllowedTypes = new List () { + (_allowAnyCheckBox.Checked ?? false) ? new AllowedTypeAny() : new AllowedType("Dynamic link library", ".dll"), + new AllowedType("Json", ".json"), + new AllowedType("Text", ".txt"), + new AllowedType("Yaml", ".yml", ".yaml") + }; + dialog.MustExist = !isSaveFile; + dialog.AllowsMultipleSelection = !isSaveFile; + _currentOpenMode++; + if (_currentOpenMode > OpenMode.Mixed) { + _currentOpenMode = OpenMode.File; + } + Application.Run (dialog); + } + public void ShowWizard () + { + Wizard wizard = new Wizard { + Height = 8, + Width = 36, + Title = "The wizard" + }; + wizard.AddStep (new WizardStep () { + HelpText = "Wizard first step", + }); + wizard.AddStep (new WizardStep () { + HelpText = "Wizard step 2", + NextButtonText = ">>> (_N)" + }); + wizard.AddStep (new WizardStep () { + HelpText = "Wizard last step" + }); + Application.Run (wizard); + } + public void Quit () + { + SetCulture (CultureInfo.InvariantCulture); + Application.RequestStop (); + } + } +} \ No newline at end of file diff --git a/UnitTests/Views/DateFieldTests.cs b/UnitTests/Views/DateFieldTests.cs index e32897a77..26774ed88 100644 --- a/UnitTests/Views/DateFieldTests.cs +++ b/UnitTests/Views/DateFieldTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -59,6 +60,8 @@ namespace Terminal.Gui.ViewsTests { [Fact] public void KeyBindings_Command () { + CultureInfo cultureBackup = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; DateField df = new DateField (DateTime.Parse ("12/12/1971")); df.ReadOnly = true; Assert.True (df.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ()))); @@ -93,6 +96,7 @@ namespace Terminal.Gui.ViewsTests { df.ReadOnly = false; Assert.True (df.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers ()))); Assert.Equal (" 12/02/1971", df.Text); + CultureInfo.CurrentCulture = cultureBackup; } } } diff --git a/UnitTests/Views/StatusBarTests.cs b/UnitTests/Views/StatusBarTests.cs index 06ef9d597..d2e81ead1 100644 --- a/UnitTests/Views/StatusBarTests.cs +++ b/UnitTests/Views/StatusBarTests.cs @@ -23,7 +23,7 @@ namespace Terminal.Gui.ViewsTests { } [Fact] - public void StatusBar_Contructor_Default () + public void StatusBar_Constructor_Default () { var sb = new StatusBar (); @@ -160,5 +160,43 @@ CTRL-O Open {CM.Glyphs.VLine} CTRL-Q Quit Assert.Equal ("~^A~ Save As", sb.Items [1].Title); Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title); } + + [Fact, AutoInitShutdown] + public void CanExecute_ProcessHotKey () + { + Window win = null; + var statusBar = new StatusBar (new StatusItem [] { + new StatusItem (Key.CtrlMask | Key.N, "~^N~ New", New, CanExecuteNew), + new StatusItem (Key.CtrlMask | Key.C, "~^C~ Close", Close, CanExecuteClose) + }); + var top = Application.Top; + top.Add (statusBar); + + bool CanExecuteNew () => win == null; + + void New () + { + win = new Window (); + } + + bool CanExecuteClose () => win != null; + + void Close () + { + win = null; + } + + Application.Begin (top); + + Assert.Null (win); + Assert.True (CanExecuteNew ()); + Assert.False (CanExecuteClose ()); + + Assert.True (top.ProcessHotKey (new KeyEvent (Key.N | Key.CtrlMask, new KeyModifiers () { Alt = true }))); + Application.MainLoop.RunIteration (); + Assert.NotNull (win); + Assert.False (CanExecuteNew ()); + Assert.True (CanExecuteClose ()); + } } } diff --git a/nuget.config b/nuget.config new file mode 100644 index 000000000..7d1098866 --- /dev/null +++ b/nuget.config @@ -0,0 +1,12 @@ + + + + + + + + + + + +