Browse Source

Merge branch 'v2_develop' into v2_menu-can-execute-false-fix_2787

Tig 1 year ago
parent
commit
6ffcaf46db

+ 36 - 43
.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: Create Test Coverage Badge
-    #  uses: simon-k/[email protected]
-    #  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: 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/[email protected]
+    #   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 }} 

+ 74 - 33
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: {}

+ 1 - 3
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -360,7 +360,6 @@ public abstract class ConsoleDriver {
 
 	#region Color Handling
 
-	
 	/// <summary>
 	/// Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.
 	/// </summary>
@@ -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.
 	/// </summary>
 	public abstract void End ();
-	
+
 	/// <summary>
 	/// Returns the name of the driver and relevant library version information.
 	/// </summary>

+ 55 - 22
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<ManagementBaseObject> ().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<ManagementBaseObject> ().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<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> 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;
 			}
 		}

+ 4 - 4
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 ();

+ 63 - 18
Terminal.Gui/Resources/Strings.Designer.cs

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

+ 13 - 4
Terminal.Gui/Resources/Strings.fr-FR.resx

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

+ 99 - 12
Terminal.Gui/Resources/Strings.ja-JP.resx

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

+ 9 - 0
Terminal.Gui/Resources/Strings.pt-PT.resx

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

+ 17 - 2
Terminal.Gui/Resources/Strings.resx

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

+ 258 - 0
Terminal.Gui/Resources/Strings.zh-Hans.resx

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

+ 24 - 12
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 ();
 		}
+		/// <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)
 		{

+ 3 - 4
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.
 	/// </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;

+ 20 - 1
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<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);
+		}
 	}
 }

+ 41 - 5
Terminal.Gui/Views/StatusBar.cs

@@ -27,11 +27,13 @@ namespace Terminal.Gui {
 		/// <param name="shortcut">Shortcut to activate the <see cref="StatusItem"/>.</param>
 		/// <param name="title">Title for the <see cref="StatusItem"/>.</param>
 		/// <param name="action">Action to invoke when the <see cref="StatusItem"/> is activated.</param>
-		public StatusItem (Key shortcut, string title, Action action)
+		/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
+		public StatusItem (Key shortcut, string title, Action action, Func<bool> canExecute = null)
 		{
 			Title = title ?? "";
 			Shortcut = shortcut;
 			Action = action;
+			CanExecute = canExecute;
 		}
 
 		/// <summary>
@@ -54,7 +56,22 @@ namespace Terminal.Gui {
 		/// Gets or sets the action to be invoked when the statusbar item is triggered
 		/// </summary>
 		/// <value>Action to invoke.</value>
-		public Action Action { get; }
+		public Action Action { get; set; }
+
+		/// <summary>
+		/// Gets or sets the action to be invoked to determine if the <see cref="StatusItem"/> can be triggered. 
+		/// If <see cref="CanExecute"/> returns <see langword="true"/> the status item will be enabled. Otherwise, it will be disabled.
+		/// </summary>
+		/// <value>Function to determine if the action is can be executed or not.</value>
+		public Func<bool> CanExecute { get; set; }
+
+		/// <summary>
+		/// Returns <see langword="true"/> if the status item is enabled. This method is a wrapper around <see cref="CanExecute"/>.
+		/// </summary>
+		public bool IsEnabled ()
+		{
+			return CanExecute == null ? true : CanExecute ();
+		}
 
 		/// <summary>
 		/// 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 ();
+		}
+
 		///<inheritdoc/>
 		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;

+ 1 - 0
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

+ 181 - 0
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<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 ();
+		}
+	}
+}

+ 4 - 0
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;
 		}
 	}
 }

+ 39 - 1
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 ());
+		}
 	}
 }

+ 12 - 0
nuget.config

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