diff --git a/SafeExamBrowser.I18n.Contracts/TextKey.cs b/SafeExamBrowser.I18n.Contracts/TextKey.cs index d9487b8f..88b97412 100644 --- a/SafeExamBrowser.I18n.Contracts/TextKey.cs +++ b/SafeExamBrowser.I18n.Contracts/TextKey.cs @@ -37,7 +37,10 @@ namespace SafeExamBrowser.I18n.Contracts BrowserWindow_MenuButton, BrowserWindow_ReloadButton, BrowserWindow_UrlTextBox, + BrowserWindow_ZoomLevelReset, BrowserWindow_ZoomMenuItem, + BrowserWindow_ZoomMenuMinus, + BrowserWindow_ZoomMenuPlus, Build, ExamSelectionDialog_Cancel, ExamSelectionDialog_Message, diff --git a/SafeExamBrowser.I18n/Data/de.xml b/SafeExamBrowser.I18n/Data/de.xml index 31b9d6e6..048c31d9 100644 --- a/SafeExamBrowser.I18n/Data/de.xml +++ b/SafeExamBrowser.I18n/Data/de.xml @@ -69,9 +69,18 @@ URL eingeben + + Seiten-Zoom bei %%ZOOM%% %. Klicken zum Zurücksetzen. + Seiten-Zoom + + Zoom vergrössern + + + Zoom verkleinern + Build diff --git a/SafeExamBrowser.I18n/Data/en.xml b/SafeExamBrowser.I18n/Data/en.xml index 12fc3ff5..99604379 100644 --- a/SafeExamBrowser.I18n/Data/en.xml +++ b/SafeExamBrowser.I18n/Data/en.xml @@ -69,9 +69,18 @@ Enter URL + + Page Zoom at %%ZOOM%% %. Click to Reset. + Page Zoom + + Increase Page Zoom + + + Decrease Page Zoom + Build diff --git a/SafeExamBrowser.I18n/Data/fr.xml b/SafeExamBrowser.I18n/Data/fr.xml index 3d46b4d1..ea2fcb68 100644 --- a/SafeExamBrowser.I18n/Data/fr.xml +++ b/SafeExamBrowser.I18n/Data/fr.xml @@ -69,9 +69,18 @@ Entrer l'URL + + Zoom de la page à %%ZOOM%% %. Cliquer pour réinitialiser. + Zoom de la page + + Augmenter zoom + + + Diminuer zoom + Build diff --git a/SafeExamBrowser.I18n/Data/it.xml b/SafeExamBrowser.I18n/Data/it.xml index 02ef95b2..3a080e4a 100644 --- a/SafeExamBrowser.I18n/Data/it.xml +++ b/SafeExamBrowser.I18n/Data/it.xml @@ -69,9 +69,18 @@ Inserisci URL + + Zoom della pagina al %%ZOOM%% %. Fare clic per ripristinare. + Zoom della pagina + + Aumentare zoom + + + Diminuire zoom + Build diff --git a/SafeExamBrowser.I18n/Data/zh.xml b/SafeExamBrowser.I18n/Data/zh.xml index 5c4521b2..cc0e9580 100644 --- a/SafeExamBrowser.I18n/Data/zh.xml +++ b/SafeExamBrowser.I18n/Data/zh.xml @@ -66,9 +66,18 @@ 输入网址 + + 页面缩放为 %%ZOOM%% %。点击重置。 + 页面缩放 + + 增加页面缩放 + + + 减少页面缩放 + 生成 diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/NetworkControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/NetworkControl.xaml.cs index 92c3c950..4a5beafd 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/NetworkControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/NetworkControl.xaml.cs @@ -121,6 +121,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter { Button.ToolTip = text; Text.Text = text; + Button.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, text); } private UIElement GetWirelessIcon(int signalStrength) diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/PowerSupplyControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/PowerSupplyControl.xaml.cs index 16241d6c..32dfdd95 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/PowerSupplyControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenter/PowerSupplyControl.xaml.cs @@ -87,6 +87,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter PowerPlug.Visibility = status.IsOnline ? Visibility.Visible : Visibility.Collapsed; Text.Text = tooltip; Warning.Visibility = status.BatteryChargeStatus == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed; + this.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, tooltip); } private void RenderCharge(double charge, BatteryChargeStatus status) diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/NetworkControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/NetworkControl.xaml.cs index aede61e4..3832f57e 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/NetworkControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/NetworkControl.xaml.cs @@ -88,6 +88,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar { WirelessIcon.Child = GetWirelessIcon(network.SignalStrength); Button.ToolTip = text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name); + Button.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, Button.ToolTip as string); } WirelessNetworksStackPanel.Children.Add(button); diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml.cs index 227a287d..7c80bb12 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/Taskbar/PowerSupplyControl.xaml.cs @@ -8,7 +8,6 @@ using System; using System.Windows; -using System.Windows.Automation; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; @@ -102,7 +101,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar Button.ToolTip = tooltip; PowerPlug.Visibility = status.IsOnline ? Visibility.Visible : Visibility.Collapsed; Warning.Visibility = status.BatteryChargeStatus == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed; - AutomationProperties.SetHelpText(this, Button.ToolTip as string); + this.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, tooltip); } private void RenderCharge(double charge, BatteryChargeStatus status) diff --git a/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs index 45d07e82..ddf32010 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Windows/BrowserWindow.xaml.cs @@ -11,6 +11,7 @@ using System.ComponentModel; using System.Reflection; using System.Threading.Tasks; using System.Windows; +using System.Windows.Automation; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Interop; @@ -196,7 +197,12 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows public void UpdateZoomLevel(double value) { - Dispatcher.Invoke(() => ZoomLevel.Text = $"{value}%"); + Dispatcher.Invoke(() => + { + ZoomLevel.Text = $"{value}%"; + var zoomButtonHelpText = this.text.Get(TextKey.BrowserWindow_ZoomLevelReset).Replace("%%ZOOM%%", value.ToString("0")); + ZoomResetButton.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, zoomButtonHelpText); + }); } private void BrowserWindow_Closing(object sender, CancelEventArgs e) @@ -297,6 +303,12 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows } } } + + if (e.Key == Key.Escape && MenuPopup.IsOpen) + { + MenuPopup.IsOpen = false; + MenuButton.Focus(); + } } /// @@ -586,9 +598,13 @@ if (typeof __SEB_focusElement === 'undefined') { private void LoadText() { DeveloperConsoleText.Text = text.Get(TextKey.BrowserWindow_DeveloperConsoleMenuItem); + DeveloperConsoleButton.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, text.Get(TextKey.BrowserWindow_DeveloperConsoleMenuItem)); FindCaseSensitiveCheckBox.Content = text.Get(TextKey.BrowserWindow_FindCaseSensitive); FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem); + FindMenuButton.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, text.Get(TextKey.BrowserWindow_FindMenuItem)); ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem); + ZoomInButton.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, text.Get(TextKey.BrowserWindow_ZoomMenuPlus)); + ZoomOutButton.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, text.Get(TextKey.BrowserWindow_ZoomMenuMinus)); ReloadButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_ReloadButton)); BackwardButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_BackwardButton)); ForwardButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_ForwardButton)); diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutButton.xaml index b48391c9..b6078b25 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutButton.xaml +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutButton.xaml @@ -5,7 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:fa="http://schemas.fontawesome.io/icons/" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Controls" - mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="250"> + mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="250" IsTabStop="True"> diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutControl.xaml index 5e6f62a2..e540cfa7 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutControl.xaml +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutControl.xaml @@ -16,7 +16,7 @@ - + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutControl.xaml.cs index 8ff5c7e1..eeed1afd 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/KeyboardLayoutControl.xaml.cs @@ -41,7 +41,14 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter InitializeLayouts(); keyboard.LayoutChanged += Keyboard_LayoutChanged; - Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; + Button.Click += (o, args) => + { + Popup.IsOpen = !Popup.IsOpen; + this.Dispatcher.BeginInvoke((System.Action) (() => + { + LayoutsStackPanel.Children[0].Focus(); + })); + }; Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver)); Popup.Opened += (o, args) => Grid.Background = Brushes.Gray; @@ -90,5 +97,14 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter Text.Text = layout.CultureName; Button.ToolTip = tooltip; } + + private void Popup_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == System.Windows.Input.Key.Enter || e.Key == System.Windows.Input.Key.Escape) + { + Popup.IsOpen = false; + Button.Focus(); + } + } } } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/NetworkControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/NetworkControl.xaml.cs index f1b1352f..4b50365a 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/NetworkControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/NetworkControl.xaml.cs @@ -121,6 +121,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter { Button.ToolTip = text; Text.Text = text; + Button.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, text); } private UIElement GetWirelessIcon(int signalStrength) diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/PowerSupplyControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/PowerSupplyControl.xaml.cs index c2a5ecfa..4985f0bb 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/PowerSupplyControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenter/PowerSupplyControl.xaml.cs @@ -87,6 +87,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter PowerPlug.Visibility = status.IsOnline ? Visibility.Visible : Visibility.Collapsed; Text.Text = tooltip; Warning.Visibility = status.BatteryChargeStatus == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed; + this.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, tooltip); } private void RenderCharge(double charge, BatteryChargeStatus status) diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/AudioControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/AudioControl.xaml index 12d82ce2..a17d3fe5 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/AudioControl.xaml +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/AudioControl.xaml @@ -16,7 +16,7 @@ - + @@ -24,7 +24,7 @@ - @@ -37,7 +37,7 @@ diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/AudioControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/AudioControl.xaml.cs index 0059ef1c..350f94d5 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/AudioControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/AudioControl.xaml.cs @@ -60,6 +60,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar { Background = Brushes.LightGray; Button.Background = Brushes.LightGray; + Volume.Focus(); }; Popup.Closed += (o, args) => @@ -167,5 +168,23 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar return IconResourceLoader.Load(resource); } + + private void Popup_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == System.Windows.Input.Key.Escape) + { + Popup.IsOpen = false; + Button.Focus(); + } + } + + private void Volume_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == System.Windows.Input.Key.Enter) + { + Popup.IsOpen = false; + Button.Focus(); + } + } } } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/Clock.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/Clock.xaml index 50c52b81..97aa83ba 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/Clock.xaml +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/Clock.xaml @@ -11,7 +11,7 @@ - - + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutButton.xaml index 48f7ba96..7732cc79 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutButton.xaml +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutButton.xaml @@ -14,24 +14,22 @@ - - - + + + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutButton.xaml.cs index 2b0c827a..89f7f336 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutButton.xaml.cs @@ -43,6 +43,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar CultureCodeTextBlock.Text = layout.CultureCode; CultureNameTextBlock.Text = layout.CultureName; LayoutNameTextBlock.Text = layout.LayoutName; + System.Windows.Automation.AutomationProperties.SetHelpText(Button, layout.LayoutName); } } } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutControl.xaml index e54cae82..d2c1b3c9 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutControl.xaml +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutControl.xaml @@ -16,7 +16,7 @@ - + diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutControl.xaml.cs index 2c9ab85a..091ce98d 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/KeyboardLayoutControl.xaml.cs @@ -45,7 +45,14 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar InitializeLayouts(); keyboard.LayoutChanged += Keyboard_LayoutChanged; - Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen; + Button.Click += (o, args) => + { + Popup.IsOpen = !Popup.IsOpen; + Task.Delay(200).ContinueWith(_ => this.Dispatcher.BeginInvoke((System.Action) (() => + { + ((LayoutsStackPanel.Children[0] as ContentControl).Content as UIElement).Focus(); + }))); + }; Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver)); Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback); Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver)); @@ -114,5 +121,14 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar LayoutCultureCode.Text = layout.CultureCode; Button.ToolTip = tooltip; } + + private void Popup_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == System.Windows.Input.Key.Enter || e.Key == System.Windows.Input.Key.Escape) + { + Popup.IsOpen = false; + Button.Focus(); + } + } } } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/NetworkControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/NetworkControl.xaml.cs index 04c04b7e..0c065270 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/NetworkControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/NetworkControl.xaml.cs @@ -88,6 +88,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar { WirelessIcon.Child = GetWirelessIcon(network.SignalStrength); Button.ToolTip = text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name); + Button.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, Button.ToolTip as string); } WirelessNetworksStackPanel.Children.Add(button); diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/PowerSupplyControl.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/PowerSupplyControl.xaml index 62973263..957dcc88 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/PowerSupplyControl.xaml +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/PowerSupplyControl.xaml @@ -4,7 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Controls" - mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40"> + mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40" Focusable="True" IsTabStop="True"> diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/PowerSupplyControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/PowerSupplyControl.xaml.cs index cb9045b8..a0eae5f1 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/PowerSupplyControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/PowerSupplyControl.xaml.cs @@ -101,6 +101,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar Button.ToolTip = tooltip; PowerPlug.Visibility = status.IsOnline ? Visibility.Visible : Visibility.Collapsed; Warning.Visibility = status.BatteryChargeStatus == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed; + this.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, tooltip); } private void RenderCharge(double charge, BatteryChargeStatus status) diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/QuitButton.xaml b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/QuitButton.xaml index b9ecf231..7dc1878f 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/QuitButton.xaml +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/Taskbar/QuitButton.xaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Controls" + x:Name="root" mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40"> @@ -15,6 +16,7 @@ - - @@ -80,7 +80,7 @@ - @@ -90,7 +90,7 @@ - diff --git a/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs index 9efbbc72..2c038b57 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs @@ -8,6 +8,7 @@ using System; using System.ComponentModel; +using System.Reflection; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls.Primitives; @@ -35,12 +36,15 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows private const string CLEAR_FIND_TERM = "thisisahacktoclearthesearchresultsasitappearsthatthereisnosuchfunctionalityincef"; private readonly bool isMainWindow; - private readonly ILogger logger; private readonly BrowserSettings settings; private readonly IText text; + private readonly ILogger logger; + private readonly IBrowserControl browserControl; private WindowClosedEventHandler closed; private WindowClosingEventHandler closing; + private bool browserControlGetsFocusFromTaskbar = false; + private IInputElement tabKeyDownFocusElement = null; private WindowSettings WindowSettings { @@ -56,12 +60,12 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows public event ActionRequestedEventHandler DeveloperConsoleRequested; public event FindRequestedEventHandler FindRequested; public event ActionRequestedEventHandler ForwardNavigationRequested; - public event LoseFocusRequestedEventHandler LoseFocusRequested { add { } remove { } } public event ActionRequestedEventHandler HomeNavigationRequested; public event ActionRequestedEventHandler ReloadRequested; public event ActionRequestedEventHandler ZoomInRequested; public event ActionRequestedEventHandler ZoomOutRequested; public event ActionRequestedEventHandler ZoomResetRequested; + public event LoseFocusRequestedEventHandler LoseFocusRequested; event WindowClosedEventHandler IWindow.Closed { @@ -78,9 +82,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text, ILogger logger) { this.isMainWindow = isMainWindow; - this.logger = logger; this.settings = settings; this.text = text; + this.logger = logger; + this.browserControl = browserControl; InitializeComponent(); InitializeBrowserWindow(browserControl); @@ -191,7 +196,12 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows public void UpdateZoomLevel(double value) { - Dispatcher.Invoke(() => ZoomLevel.Text = $"{value}%"); + Dispatcher.Invoke(() => + { + ZoomLevel.Text = $"{value}%"; + var zoomButtonHelpText = this.text.Get(TextKey.BrowserWindow_ZoomLevelReset).Replace("%%ZOOM%%", value.ToString("0")); + ZoomResetButton.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, zoomButtonHelpText); + }); } private void BrowserWindow_Closing(object sender, CancelEventArgs e) @@ -206,6 +216,33 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows } } + private void BrowserWindow_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Tab) + { + var hasShift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift; + if (Toolbar.IsKeyboardFocusWithin && hasShift) + { + var firstActiveElementInToolbar = Toolbar.PredictFocus(FocusNavigationDirection.Right); + if (firstActiveElementInToolbar is System.Windows.UIElement) + { + var control = firstActiveElementInToolbar as System.Windows.UIElement; + if (control.IsKeyboardFocusWithin) + { + this.LoseFocusRequested?.Invoke(false); + e.Handled = true; + } + } + } + + tabKeyDownFocusElement = FocusManager.GetFocusedElement(this); + } + else + { + tabKeyDownFocusElement = null; + } + } + private void BrowserWindow_KeyUp(object sender, KeyEventArgs e) { if (e.Key == Key.F5) @@ -222,6 +259,71 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows { ShowFindbar(); } + + if (e.Key == Key.Tab) + { + var hasCtrl = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control; + var hasShift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift; + if (BrowserControlHost.IsFocused && hasCtrl) + { + if (Findbar.Visibility == Visibility.Hidden || hasShift) + { + Toolbar.Focus(); + } + else if (Toolbar.Visibility == Visibility.Hidden) + { + Findbar.Focus(); + } + } + else if (MenuPopup.IsKeyboardFocusWithin) + { + var focusedElement = FocusManager.GetFocusedElement(this); + var focusedControl = focusedElement as System.Windows.Controls.Control; + var prevFocusedControl = tabKeyDownFocusElement as System.Windows.Controls.Control; + if (focusedControl != null && prevFocusedControl != null) + { + //var commonAncestor = focusedControl.FindCommonVisualAncestor(prevFocusedControl); + //var nextTab = GetNextTab(MenuPopup, this, true); + if (!hasShift && focusedControl.TabIndex < prevFocusedControl.TabIndex) + { + MenuPopup.IsOpen = false; + FocusBrowser(); + } + else if (hasShift && focusedControl.TabIndex > prevFocusedControl.TabIndex) + { + MenuPopup.IsOpen = false; + MenuButton.Focus(); + } + } + } + } + + if (e.Key == Key.Escape && MenuPopup.IsOpen) + { + MenuPopup.IsOpen = false; + MenuButton.Focus(); + } + } + + /// + /// Get next tab order element. Copied from https://stackoverflow.com/questions/5756448/in-wpf-how-can-i-get-the-next-control-in-the-tab-order + /// + /// The element to get next tab order + /// The container element owning 'e'. Make sure this is a container of 'e'. + /// True if search only itself and inside of 'container'; otherwise false. + /// If true and next tab order element is outside of 'container', result in null. + /// Next tab order element or null if not found + public DependencyObject GetNextTab(DependencyObject e, DependencyObject container, bool goDownOnly) + { + var navigation = typeof(FrameworkElement) + .GetProperty("KeyboardNavigation", BindingFlags.NonPublic | BindingFlags.Static) + .GetValue(null); + + var method = navigation + .GetType() + .GetMethod("GetNextTab", BindingFlags.NonPublic | BindingFlags.Instance); + + return method.Invoke(navigation, new object[] { e, container, goDownOnly }) as DependencyObject; } private void BrowserWindow_Loaded(object sender, RoutedEventArgs e) @@ -327,10 +429,11 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke(); HomeButton.Click += (o, args) => HomeNavigationRequested?.Invoke(); Loaded += BrowserWindow_Loaded; - MenuButton.Click += (o, args) => MenuPopup.IsOpen = !MenuPopup.IsOpen; + MenuButton.Click += MenuButton_Click; MenuButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver)); MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback); MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver)); + KeyDown += BrowserWindow_KeyDown; KeyUp += BrowserWindow_KeyUp; LocationChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; }; ReloadButton.Click += (o, args) => ReloadRequested?.Invoke(); @@ -345,6 +448,48 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows ZoomInButton.Click += (o, args) => ZoomInRequested?.Invoke(); ZoomOutButton.Click += (o, args) => ZoomOutRequested?.Invoke(); ZoomResetButton.Click += (o, args) => ZoomResetRequested?.Invoke(); + BrowserControlHost.GotKeyboardFocus += BrowserControlHost_GotKeyboardFocus; + } + + private void MenuButton_Click(object sender, RoutedEventArgs e) + { + MenuPopup.IsOpen = !MenuPopup.IsOpen; + ZoomInButton.Focus(); + } + + private void BrowserControlHost_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) + { + var forward = !this.browserControlGetsFocusFromTaskbar; + + // focus the first / last element on the page + var javascript = @" +if (typeof __SEB_focusElement === 'undefined') { + __SEB_focusElement = function (forward) { + var items = [].map + .call(document.body.querySelectorAll(['input', 'select', 'a[href]', 'textarea', 'button', '[tabindex]']), function(el, i) { return { el, i } }) + .filter(function(e) { return e.el.tabIndex >= 0 && !e.el.disabled && e.el.offsetParent; }) + .sort(function(a,b) { return a.el.tabIndex === b.el.tabIndex ? a.i - b.i : (a.el.tabIndex || 9E9) - (b.el.tabIndex || 9E9); }) + var item = items[forward ? 1 : items.length - 1]; + if (item && item.focus && typeof item.focus !== 'function') + throw ('item.focus is not a function, ' + typeof item.focus) + setTimeout(function () { item && item.focus && item.focus(); }, 20); + } +}"; + this.browserControl.ExecuteJavascript(javascript, result => + { + if (!result.Success) + { + logger.Error($"Javascript error {result.Message}!"); + } + }); + + this.browserControl.ExecuteJavascript("__SEB_focusElement(" + forward.ToString().ToLower() + ")", result => + { + if (!result.Success) + { + logger.Error($"Javascript error {result.Message}!"); + } + }); } private void ApplySettings() @@ -457,29 +602,65 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows private void LoadText() { DeveloperConsoleText.Text = text.Get(TextKey.BrowserWindow_DeveloperConsoleMenuItem); + DeveloperConsoleButton.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, text.Get(TextKey.BrowserWindow_DeveloperConsoleMenuItem)); FindCaseSensitiveCheckBox.Content = text.Get(TextKey.BrowserWindow_FindCaseSensitive); FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem); + FindMenuButton.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, text.Get(TextKey.BrowserWindow_FindMenuItem)); ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem); + ZoomInButton.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, text.Get(TextKey.BrowserWindow_ZoomMenuPlus)); + ZoomOutButton.SetValue(System.Windows.Automation.AutomationProperties.HelpTextProperty, text.Get(TextKey.BrowserWindow_ZoomMenuMinus)); + ReloadButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_ReloadButton)); + BackwardButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_BackwardButton)); + ForwardButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_ForwardButton)); + DownloadsButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_DownloadsButton)); + HomeButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_HomeButton)); + MenuButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_MenuButton)); + UrlTextBox.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_UrlTextBox)); } public void FocusToolbar(bool forward) { - throw new NotImplementedException(); + this.Dispatcher.BeginInvoke((Action) (async () => + { + this.Activate(); + await Task.Delay(50); + + // focus all elements in the toolbar, such that the last element that is enabled gets focus + var buttons = new System.Windows.Controls.Control[] { ForwardButton, BackwardButton, ReloadButton, UrlTextBox, MenuButton, }; + for (var i = forward ? 0 : buttons.Length - 1; i >= 0 && i < buttons.Length; i += forward ? 1 : -1) + { + if (buttons[i].IsEnabled && buttons[i].Visibility == Visibility.Visible) + { + buttons[i].Focus(); + break; + } + } + })); } public void FocusBrowser() { - throw new NotImplementedException(); - } + this.Dispatcher.BeginInvoke((Action) (async () => + { + this.FocusToolbar(false); + await Task.Delay(100); - public void Debug() - { - throw new NotImplementedException(); + this.browserControlGetsFocusFromTaskbar = true; + + var focusedElement = FocusManager.GetFocusedElement(this) as UIElement; + focusedElement.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right)); + + await Task.Delay(150); + this.browserControlGetsFocusFromTaskbar = false; + })); } public void FocusAddressBar() { - this.UrlTextBox.Focus(); + this.Dispatcher.BeginInvoke((Action) (async () => + { + this.UrlTextBox.Focus(); + })); } } } diff --git a/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml b/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml index 2ce32ed0..df16cf87 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml +++ b/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml @@ -6,6 +6,7 @@ xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar" xmlns:s="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" Title="Taskbar" Background="{DynamicResource BackgroundBrush}" Height="60" FontSize="16" Width="750" WindowStyle="None" + KeyDown="Window_KeyDown" KeyUp="Window_KeyUp" Topmost="True" ResizeMode="NoResize" Icon="../Images/SafeExamBrowser.ico"> diff --git a/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs index beeaff73..24040923 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using System.ComponentModel; using System.Windows; using SafeExamBrowser.Browser.Contracts.Events; @@ -19,8 +20,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows { internal partial class Taskbar : Window, ITaskbar { - private readonly ILogger logger; private bool allowClose; + private readonly ILogger logger; + private bool isQuitButtonFocusedAtKeyDown; + private bool isFirstChildFocusedAtKeyDown; public bool ShowClock { @@ -32,7 +35,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); } } - public event LoseFocusRequestedEventHandler LoseFocusRequested { add { } remove { } } + public event LoseFocusRequestedEventHandler LoseFocusRequested; public event QuitButtonClickedEventHandler QuitButtonClicked; internal Taskbar(ILogger logger) @@ -81,16 +84,19 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows public void Focus(bool fromTop) { - Activate(); + Dispatcher.BeginInvoke((Action) (() => + { + Activate(); - if (fromTop) - { - ApplicationStackPanel.Children[0].Focus(); - } - else - { - QuitButton.Focus(); - } + if (fromTop) + { + ApplicationStackPanel.Children[0].Focus(); + } + else + { + QuitButton.Focus(); + } + })); } public int GetAbsoluteHeight() @@ -143,7 +149,9 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows { Dispatcher.Invoke(() => { - QuitButton.ToolTip = text.Get(TextKey.Shell_QuitButton); + var txt = text.Get(TextKey.Shell_QuitButton); + QuitButton.ToolTip = txt; + QuitButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, txt); }); } @@ -185,5 +193,82 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows e.Cancel = true; } } + + private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + this.isQuitButtonFocusedAtKeyDown = this.QuitButton.IsKeyboardFocusWithin; + this.isFirstChildFocusedAtKeyDown = this.ApplicationStackPanel.Children[0].IsKeyboardFocusWithin; + } + + private void Window_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == System.Windows.Input.Key.Tab) + { + var shift = System.Windows.Input.Keyboard.IsKeyDown(System.Windows.Input.Key.LeftShift); + if (!shift && this.ApplicationStackPanel.Children[0].IsKeyboardFocusWithin && this.isQuitButtonFocusedAtKeyDown) + { + this.LoseFocusRequested?.Invoke(true); + e.Handled = true; + } + else if (shift && this.QuitButton.IsKeyboardFocusWithin && this.isFirstChildFocusedAtKeyDown) + { + this.LoseFocusRequested?.Invoke(false); + e.Handled = true; + } + } + + this.isQuitButtonFocusedAtKeyDown = false; + this.isFirstChildFocusedAtKeyDown = false; + } + + void ITaskbar.Focus(bool forward) + { + this.Dispatcher.BeginInvoke((Action) (() => + { + base.Activate(); + if (forward) + { + this.SetFocusWithin(this.ApplicationStackPanel.Children[0]); + } + else + { + this.QuitButton.Focus(); + } + })); + } + + private bool SetFocusWithin(UIElement uIElement) + { + if (uIElement.Focusable) + { + uIElement.Focus(); + return true; + } + + if (uIElement is System.Windows.Controls.Panel) + { + var panel = uIElement as System.Windows.Controls.Panel; + for (var i = 0; i < panel.Children.Count; i++) + { + if (this.SetFocusWithin(panel.Children[i])) + { + return true; + } + } + + return false; + } + else if (uIElement is System.Windows.Controls.ContentControl) + { + var control = uIElement as System.Windows.Controls.ContentControl; + var content = control.Content as UIElement; + if (content != null) + { + return this.SetFocusWithin(content); + } + } + + return false; + } } }