Merge branch 'v2_develop' into v2_develop

This commit is contained in:
Tig
2023-09-29 11:11:57 -06:00
committed by GitHub
18 changed files with 962 additions and 165 deletions

View File

@@ -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 }}

View File

@@ -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: {}

View File

@@ -103,15 +103,17 @@ class NetWinVTConsole {
static extern uint GetLastError ();
}
internal class NetEvents {
internal class NetEvents : IDisposable {
ManualResetEventSlim _inputReady = new ManualResetEventSlim (false);
ManualResetEventSlim _waitForStart = new ManualResetEventSlim (false);
ManualResetEventSlim _winChange = new ManualResetEventSlim (false);
Queue<InputResult?> _inputResultQueue = new Queue<InputResult?> ();
ConsoleDriver _consoleDriver;
volatile ConsoleKeyInfo [] _cki = null;
volatile static bool _isEscSeq;
bool _stopTasks;
ConsoleKeyInfo [] _cki;
bool _isEscSeq;
CancellationTokenSource _cancellationTokenSource;
CancellationToken _cancellationToken;
#if PROCESS_REQUEST
bool _neededProcessRequest;
#endif
@@ -120,19 +122,16 @@ internal class NetEvents {
public NetEvents (ConsoleDriver consoleDriver)
{
_consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
_cancellationTokenSource = new CancellationTokenSource ();
_cancellationToken = _cancellationTokenSource.Token;
Task.Run (ProcessInputResultQueue);
Task.Run (CheckWindowSizeChange);
}
internal void StopTasks ()
{
_stopTasks = true;
}
public InputResult? ReadConsoleInput ()
{
while (true) {
if (_stopTasks) {
if (_cancellationToken.IsCancellationRequested) {
return null;
}
_waitForStart.Set ();
@@ -151,6 +150,24 @@ internal class NetEvents {
}
}
static ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
{
// if there is a key available, return it without waiting
// (or dispatching work to the thread queue)
if (Console.KeyAvailable) {
return Console.ReadKey (intercept);
}
while (!cancellationToken.IsCancellationRequested) {
Task.Delay (100);
if (Console.KeyAvailable) {
return Console.ReadKey (intercept);
}
}
cancellationToken.ThrowIfCancellationRequested ();
return default;
}
void ProcessInputResultQueue ()
{
while (true) {
@@ -163,9 +180,18 @@ internal class NetEvents {
ConsoleKeyInfo newConsoleKeyInfo = default;
while (true) {
ConsoleKeyInfo consoleKeyInfo = Console.ReadKey (true);
if (_cancellationToken.IsCancellationRequested) {
return;
}
ConsoleKeyInfo consoleKeyInfo;
try {
consoleKeyInfo = ReadConsoleKeyInfo (_cancellationToken, true);
} catch (OperationCanceledException) {
return;
}
if ((consoleKeyInfo.KeyChar == (char)Key.Esc && !_isEscSeq)
|| (consoleKeyInfo.KeyChar != (char)Key.Esc && _isEscSeq)) {
|| (consoleKeyInfo.KeyChar != (char)Key.Esc && _isEscSeq)) {
if (_cki == null && consoleKeyInfo.KeyChar != (char)Key.Esc && _isEscSeq) {
_cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0,
false, false, false), _cki);
@@ -179,17 +205,16 @@ internal class NetEvents {
_isEscSeq = false;
break;
} else if (consoleKeyInfo.KeyChar == (char)Key.Esc && _isEscSeq && _cki != null) {
if (_cki != null) {
ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
_cki = null;
ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
_cki = null;
if (Console.KeyAvailable) {
_cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
} else {
ProcessMapConsoleKeyInfo (consoleKeyInfo);
}
break;
} else {
_inputResultQueue.Enqueue (new InputResult {
EventType = EventType.Key,
ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
});
_isEscSeq = false;
ProcessMapConsoleKeyInfo (consoleKeyInfo);
break;
}
}
@@ -197,19 +222,25 @@ internal class NetEvents {
_inputReady.Set ();
}
void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
{
_inputResultQueue.Enqueue (new InputResult {
EventType = EventType.Key,
ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
});
_isEscSeq = false;
}
}
void CheckWindowSizeChange ()
{
void RequestWindowSize ()
void RequestWindowSize (CancellationToken cancellationToken)
{
while (true) {
while (!cancellationToken.IsCancellationRequested) {
// Wait for a while then check if screen has changed sizes
Task.Delay (500).Wait ();
Task.Delay (500, cancellationToken);
if (_stopTasks) {
return;
}
int buffHeight, buffWidth;
if (((NetDriver)_consoleDriver).IsWinPlatform) {
buffHeight = Math.Max (Console.BufferHeight, 0);
@@ -227,15 +258,20 @@ internal class NetEvents {
return;
}
}
cancellationToken.ThrowIfCancellationRequested ();
}
while (true) {
if (_stopTasks) {
if (_cancellationToken.IsCancellationRequested) {
return;
}
_winChange.Wait ();
_winChange.Reset ();
RequestWindowSize ();
try {
RequestWindowSize (_cancellationToken);
} catch (OperationCanceledException) {
return;
}
_inputReady.Set ();
}
}
@@ -536,6 +572,23 @@ internal class NetEvents {
_inputResultQueue.Enqueue (inputResult);
}
public void Dispose ()
{
_cancellationTokenSource.Cancel ();
_cancellationTokenSource.Dispose ();
_cancellationTokenSource = null;
FlushIn ();
}
void FlushIn ()
{
// throws away any typeahead that has been typed by
// the user and has not yet been read by the program.
while (Console.KeyAvailable) {
Console.ReadKey (true);
}
}
}
internal class NetDriver : ConsoleDriver {
@@ -563,7 +616,7 @@ internal class NetDriver : ConsoleDriver {
public override void End ()
{
_mainLoop?._netEvents.StopTasks ();
_mainLoop?._netEvents.Dispose ();
if (IsWinPlatform) {
NetWinConsole?.Cleanup ();
@@ -611,7 +664,7 @@ internal class NetDriver : ConsoleDriver {
if (!RunningUnitTests) {
Console.TreatControlCAsInput = true;
Cols = Console.WindowWidth;
Rows = Console.WindowHeight;

View File

@@ -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 ();

View File

@@ -60,6 +60,69 @@ namespace Terminal.Gui.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Cancel.
/// </summary>
internal static string btnCancel {
get {
return ResourceManager.GetString("btnCancel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No.
/// </summary>
internal static string btnNo {
get {
return ResourceManager.GetString("btnNo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to OK.
/// </summary>
internal static string btnOk {
get {
return ResourceManager.GetString("btnOk", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open.
/// </summary>
internal static string btnOpen {
get {
return ResourceManager.GetString("btnOpen", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save.
/// </summary>
internal static string btnSave {
get {
return ResourceManager.GetString("btnSave", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save as.
/// </summary>
internal static string btnSaveAs {
get {
return ResourceManager.GetString("btnSaveAs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Yes.
/// </summary>
internal static string btnYes {
get {
return ResourceManager.GetString("btnYes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Copy.
/// </summary>
@@ -267,15 +330,6 @@ namespace Terminal.Gui.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to No.
/// </summary>
internal static string fdNo {
get {
return ResourceManager.GetString("fdNo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open.
/// </summary>
@@ -393,15 +447,6 @@ namespace Terminal.Gui.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Yes.
/// </summary>
internal static string fdYes {
get {
return ResourceManager.GetString("fdYes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Back.
/// </summary>

View File

@@ -142,16 +142,16 @@
<value>_Dossier</value>
</data>
<data name="fdFile" xml:space="preserve">
<value>_Ficher</value>
<value>Ficher</value>
</data>
<data name="fdSave" xml:space="preserve">
<value>_Enregistrer</value>
<value>Enregistrer</value>
</data>
<data name="fdSaveAs" xml:space="preserve">
<value>E_nregistrer sous</value>
<value>Enregistrer sous</value>
</data>
<data name="fdOpen" xml:space="preserve">
<value>_Ouvrir</value>
<value>Ouvrir</value>
</data>
<data name="fdSelectFolder" xml:space="preserve">
<value>Sélection de _dossier</value>
@@ -168,4 +168,13 @@
<data name="wzNext" xml:space="preserve">
<value>Prochai_n...</value>
</data>
<data name="btnSave" xml:space="preserve">
<value>Enregistrer</value>
</data>
<data name="btnSaveAs" xml:space="preserve">
<value>E_nregistrer sous</value>
</data>
<data name="btnOpen" xml:space="preserve">
<value>Ouvrir</value>
</data>
</root>

View File

@@ -118,25 +118,25 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ctxCopy" xml:space="preserve">
<value>コピー (C)</value>
<value>コピー (_C)</value>
</data>
<data name="ctxCut" xml:space="preserve">
<value>切り取り (T)</value>
<value>切り取り (_T)</value>
</data>
<data name="ctxDeleteAll" xml:space="preserve">
<value>全て削除 (D)</value>
<value>全て削除 (_D)</value>
</data>
<data name="ctxPaste" xml:space="preserve">
<value>貼り付け (P)</value>
<value>貼り付け (_P)</value>
</data>
<data name="ctxRedo" xml:space="preserve">
<value>やり直し (R)</value>
<value>やり直し (_R)</value>
</data>
<data name="ctxSelectAll" xml:space="preserve">
<value>全て選択 (S)</value>
<value>全て選択 (_S)</value>
</data>
<data name="ctxUndo" xml:space="preserve">
<value>元に戻す (U)</value>
<value>元に戻す (_U)</value>
</data>
<data name="fdDirectory" xml:space="preserve">
<value>ディレクトリ</value>
@@ -154,18 +154,105 @@
<value>開く</value>
</data>
<data name="fdSelectFolder" xml:space="preserve">
<value>フォルダーを選択</value>
<value>フォルダーを選択 (_S)</value>
</data>
<data name="fdSelectMixed" xml:space="preserve">
<value>混在選択</value>
<value>混在選択 (_S)</value>
</data>
<data name="wzBack" xml:space="preserve">
<value>戻る</value>
<value>戻る (_B)</value>
</data>
<data name="wzFinish" xml:space="preserve">
<value>終える</value>
<value>終わる (_N)</value>
</data>
<data name="wzNext" xml:space="preserve">
<value>次に</value>
<value>次に (_N)...</value>
</data>
<data name="fdDirectoryAlreadyExistsFeedback" xml:space="preserve">
<value>同じ名前のディレクトリはすでに存在しました</value>
</data>
<data name="fdDeleteBody" xml:space="preserve">
<value>“{0}”を削除もよろしいですか?この操作は元に戻りません</value>
</data>
<data name="fdType" xml:space="preserve">
<value>タイプ</value>
</data>
<data name="fdSize" xml:space="preserve">
<value>サイズ</value>
</data>
<data name="fdPathCaption" xml:space="preserve">
<value>パスを入力</value>
</data>
<data name="fdFilename" xml:space="preserve">
<value>ファイル名</value>
</data>
<data name="fdNewTitle" xml:space="preserve">
<value>新規ディレクトリ</value>
</data>
<data name="btnNo" xml:space="preserve">
<value>いいえ (_N)</value>
</data>
<data name="btnYes" xml:space="preserve">
<value>はい (_Y)</value>
</data>
<data name="fdModified" xml:space="preserve">
<value>変更日時</value>
</data>
<data name="fdFileOrDirectoryMustExistFeedback" xml:space="preserve">
<value>すでに存在したファイルまたはディレクトリを選択してください</value>
</data>
<data name="fdDirectoryMustExistFeedback" xml:space="preserve">
<value>すでに存在したディレクトリを選択してください</value>
</data>
<data name="fdFileMustExistFeedback" xml:space="preserve">
<value>すでに存在したファイルを選択してください</value>
</data>
<data name="fdRenamePrompt" xml:space="preserve">
<value>名前:</value>
</data>
<data name="fdDeleteTitle" xml:space="preserve">
<value>{0} を削除</value>
</data>
<data name="fdNewFailed" xml:space="preserve">
<value>新規失敗</value>
</data>
<data name="fdExisting" xml:space="preserve">
<value>既存</value>
</data>
<data name="fdRenameTitle" xml:space="preserve">
<value>名前を変更</value>
</data>
<data name="fdRenameFailedTitle" xml:space="preserve">
<value>変更失敗</value>
</data>
<data name="fdDeleteFailedTitle" xml:space="preserve">
<value>削除失敗</value>
</data>
<data name="fdFileAlreadyExistsFeedback" xml:space="preserve">
<value>同じ名前のファイルはすでに存在しました</value>
</data>
<data name="fdSearchCaption" xml:space="preserve">
<value>検索を入力</value>
</data>
<data name="fdWrongFileTypeFeedback" xml:space="preserve">
<value>ファイルタイプが間違っでいます</value>
</data>
<data name="fdAnyFiles" xml:space="preserve">
<value>任意ファイル</value>
</data>
<data name="btnCancel" xml:space="preserve">
<value>キャンセル (_C)</value>
</data>
<data name="btnOk" xml:space="preserve">
<value>OK (_O)</value>
</data>
<data name="btnOpen" xml:space="preserve">
<value>開く (_O)</value>
</data>
<data name="btnSave" xml:space="preserve">
<value>保存 (_S)</value>
</data>
<data name="btnSaveAs" xml:space="preserve">
<value>名前を付けて保存 (_S)</value>
</data>
</root>

View File

@@ -168,4 +168,13 @@
<data name="wzNext" xml:space="preserve">
<value>S_eguir</value>
</data>
<data name="btnSaveAs" xml:space="preserve">
<value>Guardar como</value>
</data>
<data name="btnSave" xml:space="preserve">
<value>Guardar</value>
</data>
<data name="btnOpen" xml:space="preserve">
<value>Abrir</value>
</data>
</root>

View File

@@ -226,7 +226,7 @@
<data name="fdNewTitle" xml:space="preserve">
<value>New Folder</value>
</data>
<data name="fdNo" xml:space="preserve">
<data name="btnNo" xml:space="preserve">
<value>No</value>
</data>
<data name="fdRenameFailedTitle" xml:space="preserve">
@@ -238,10 +238,25 @@
<data name="fdRenameTitle" xml:space="preserve">
<value>Rename</value>
</data>
<data name="fdYes" xml:space="preserve">
<data name="btnYes" xml:space="preserve">
<value>Yes</value>
</data>
<data name="fdExisting" xml:space="preserve">
<value>Existing</value>
</data>
<data name="btnOpen" xml:space="preserve">
<value>Open</value>
</data>
<data name="btnSave" xml:space="preserve">
<value>Save</value>
</data>
<data name="btnSaveAs" xml:space="preserve">
<value>Save as</value>
</data>
<data name="btnOk" xml:space="preserve">
<value>OK</value>
</data>
<data name="btnCancel" xml:space="preserve">
<value>Cancel</value>
</data>
</root>

View File

@@ -0,0 +1,258 @@
<?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>
<data name="ctxSelectAll" xml:space="preserve">
<value>全选 (_S)</value>
</data>
<data name="ctxDeleteAll" xml:space="preserve">
<value>清空 (_D)</value>
</data>
<data name="ctxCopy" xml:space="preserve">
<value>复制 (_C)</value>
</data>
<data name="ctxCut" xml:space="preserve">
<value>剪切 (_T)</value>
</data>
<data name="ctxPaste" xml:space="preserve">
<value>粘贴 (_P)</value>
</data>
<data name="ctxUndo" xml:space="preserve">
<value>撤销 (_U)</value>
</data>
<data name="ctxRedo" xml:space="preserve">
<value>重做 (_R)</value>
</data>
<data name="fdDirectory" xml:space="preserve">
<value>目录</value>
</data>
<data name="fdFile" xml:space="preserve">
<value>文件</value>
</data>
<data name="fdSave" xml:space="preserve">
<value>保存</value>
</data>
<data name="fdSaveAs" xml:space="preserve">
<value>另存为</value>
</data>
<data name="fdOpen" xml:space="preserve">
<value>打开</value>
</data>
<data name="wzNext" xml:space="preserve">
<value>下一步 (_N)...</value>
</data>
<data name="fdSelectFolder" xml:space="preserve">
<value>选择文件夹 (_S)</value>
</data>
<data name="fdSelectMixed" xml:space="preserve">
<value>混合选择 (_S)</value>
</data>
<data name="wzBack" xml:space="preserve">
<value>返回 (_B)</value>
</data>
<data name="wzFinish" xml:space="preserve">
<value>结束 (_N)</value>
</data>
<data name="fdDirectoryAlreadyExistsFeedback" xml:space="preserve">
<value>已存在相同名称的目录</value>
</data>
<data name="fdDirectoryMustExistFeedback" xml:space="preserve">
<value>必须选择已有的目录</value>
</data>
<data name="fdFileAlreadyExistsFeedback" xml:space="preserve">
<value>已存在相同名称的文件</value>
</data>
<data name="fdFileMustExistFeedback" xml:space="preserve">
<value>必须选择已有的文件</value>
</data>
<data name="fdFilename" xml:space="preserve">
<value>文件名</value>
</data>
<data name="fdFileOrDirectoryMustExistFeedback" xml:space="preserve">
<value>必须选择已有的文件或目录</value>
</data>
<data name="fdModified" xml:space="preserve">
<value>修改时间</value>
</data>
<data name="fdPathCaption" xml:space="preserve">
<value>请输入路径</value>
</data>
<data name="fdSearchCaption" xml:space="preserve">
<value>输入以搜索</value>
</data>
<data name="fdSize" xml:space="preserve">
<value>大小</value>
</data>
<data name="fdType" xml:space="preserve">
<value>类型</value>
</data>
<data name="fdWrongFileTypeFeedback" xml:space="preserve">
<value>文件类型有误</value>
</data>
<data name="fdAnyFiles" xml:space="preserve">
<value>任意文件</value>
</data>
<data name="fdDeleteBody" xml:space="preserve">
<value>您是否要删除“{0}”?此操作不可撤销</value>
</data>
<data name="fdDeleteFailedTitle" xml:space="preserve">
<value>删除失败</value>
</data>
<data name="fdDeleteTitle" xml:space="preserve">
<value>删除 {0}</value>
</data>
<data name="fdNewFailed" xml:space="preserve">
<value>新建失败</value>
</data>
<data name="fdNewTitle" xml:space="preserve">
<value>新建文件夹</value>
</data>
<data name="btnNo" xml:space="preserve">
<value>否 (_N)</value>
</data>
<data name="fdRenameFailedTitle" xml:space="preserve">
<value>重命名失败</value>
</data>
<data name="fdRenamePrompt" xml:space="preserve">
<value>名称:</value>
</data>
<data name="fdRenameTitle" xml:space="preserve">
<value>重命名</value>
</data>
<data name="btnYes" xml:space="preserve">
<value>是 (_Y)</value>
</data>
<data name="fdExisting" xml:space="preserve">
<value>已有</value>
</data>
<data name="btnOk" xml:space="preserve">
<value>确定 (_O)</value>
</data>
<data name="btnOpen" xml:space="preserve">
<value>打开 (_O)</value>
</data>
<data name="btnSave" xml:space="preserve">
<value>保存 (_S)</value>
</data>
<data name="btnSaveAs" xml:space="preserve">
<value>另存为 (_S)</value>
</data>
<data name="btnCancel" xml:space="preserve">
<value>取消 (_C)</value>
</data>
</root>

View File

@@ -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 ();
}
/// <summary>
/// Gets a default dialog title, when <see cref="Title"/> is not set or empty,
/// result of the function will be shown.
/// </summary>
protected virtual string GetDefaultTitle ()
{
List<string> 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)
{

View File

@@ -55,8 +55,7 @@ namespace Terminal.Gui {
/// To select more than one file, users can use the spacebar, or control-t.
/// </para>
/// </remarks>
public class OpenDialog : FileDialog {
public class OpenDialog : FileDialog {
/// <summary>
/// Initializes a new <see cref="OpenDialog"/>.
/// </summary>
@@ -70,9 +69,9 @@ namespace Terminal.Gui {
/// <param name="openMode">The open mode.</param>
public OpenDialog (string title, List<IAllowedType> 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;

View File

@@ -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<string> 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);
}
}
}

View File

@@ -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

View File

@@ -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<IAllowedType> () {
(_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 ();
}
}
}

View File

@@ -50,16 +50,21 @@ namespace Terminal.Gui.ViewsTests {
Assert.NotNull (cm.Host);
}
[Fact]
[AutoInitShutdown]
public void Show_Hide_IsShow ()
private ContextMenu Create_ContextMenu_With_Two_MenuItem (int x, int y)
{
var cm = new ContextMenu (10, 5,
return new ContextMenu (x, y,
new MenuBarItem (new MenuItem [] {
new MenuItem ("One", "", null),
new MenuItem ("Two", "", null)
})
);
}
[Fact]
[AutoInitShutdown]
public void Show_Hide_IsShow ()
{
var cm = Create_ContextMenu_With_Two_MenuItem (10, 5);
cm.Show ();
Assert.True (ContextMenu.IsShow);
@@ -1140,5 +1145,59 @@ namespace Terminal.Gui.ViewsTests {
Application.End (rs);
}
[Fact, AutoInitShutdown]
public void RequestStop_While_ContextMenu_Is_Open_Does_Not_Throws ()
{
var cm = Create_ContextMenu_With_Two_MenuItem (10, 5);
var top = Application.Top;
var isMenuAllClosed = false;
MenuBarItem mi = null;
var iterations = -1;
Application.Iteration += () => {
iterations++;
if (iterations == 0) {
cm.Show ();
Assert.True (ContextMenu.IsShow);
mi = cm.MenuBar.Menus [0];
mi.Action = () => {
var dialog1 = new Dialog ();
Application.Run (dialog1);
Assert.False (ContextMenu.IsShow);
Assert.True (isMenuAllClosed);
};
cm.MenuBar.MenuAllClosed += (_, _) => isMenuAllClosed = true;
} else if (iterations == 1) {
mi.Action ();
} else if (iterations == 2) {
Application.RequestStop ();
} else if (iterations == 3) {
isMenuAllClosed = false;
cm.Show ();
Assert.True (ContextMenu.IsShow);
cm.MenuBar.MenuAllClosed += (_, _) => isMenuAllClosed = true;
} else if (iterations == 4) {
var exception = Record.Exception (() => Application.RequestStop ());
Assert.Null (exception);
} else {
Application.RequestStop ();
}
};
var isTopClosed = false;
top.Closing += (_, _) => {
var dialog2 = new Dialog ();
Application.Run (dialog2);
Assert.False (ContextMenu.IsShow);
Assert.True (isMenuAllClosed);
isTopClosed = true;
};
Application.Run ();
Assert.True (isTopClosed);
Assert.False (ContextMenu.IsShow);
Assert.True (isMenuAllClosed);
}
}
}

View File

@@ -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;
}
}
}

12
nuget.config Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="defaultPushSource" value="https://api.nuget.org/v3/index.json" />
</config>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>