Merge pull request #278 from yolpsoftware/master

Proof-of-concept desktop keyboard navigation
This commit is contained in:
Damian Büchel 2022-05-03 19:33:32 +02:00 committed by GitHub
commit 043187f4ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 764 additions and 46 deletions

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Browser.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that the user wants to move the focus away from the item.
/// </summary>
public delegate void LoseFocusRequestedEventHandler(bool forward);
}

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Browser.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that the user pressed the tab key to move the focus forward or backward.
/// </summary>
public delegate void TabPressedEventHandler(bool forward);
}

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Browser.Contracts.Events; using SafeExamBrowser.Browser.Contracts.Events;
@ -30,5 +31,16 @@ namespace SafeExamBrowser.Browser.Contracts
/// Event fired when the browser application detects a request to terminate SEB. /// Event fired when the browser application detects a request to terminate SEB.
/// </summary> /// </summary>
event TerminationRequestedEventHandler TerminationRequested; event TerminationRequestedEventHandler TerminationRequested;
/// <summary>
/// Event fired when the user tries to focus the taskbar.
/// </summary>
event LoseFocusRequestedEventHandler LoseFocusRequested;
/// <summary>
/// Transfers the focus to the browser window.
/// <paramref name="forward">If true, the first focusable element in the browser window receives focus (passing forward of focus). Otherwise, the last element.</paramref>
/// </summary>
void Focus(bool forward);
} }
} }

View file

@ -57,6 +57,8 @@
<Compile Include="Events\DownloadEventArgs.cs" /> <Compile Include="Events\DownloadEventArgs.cs" />
<Compile Include="Events\DownloadFinishedCallback.cs" /> <Compile Include="Events\DownloadFinishedCallback.cs" />
<Compile Include="Events\DownloadRequestedEventHandler.cs" /> <Compile Include="Events\DownloadRequestedEventHandler.cs" />
<Compile Include="Events\TabPressedEventHandler.cs" />
<Compile Include="Events\LoseFocusRequestedEventHandler.cs" />
<Compile Include="Events\SessionIdentifierDetectedEventHandler.cs" /> <Compile Include="Events\SessionIdentifierDetectedEventHandler.cs" />
<Compile Include="Events\TerminationRequestedEventHandler.cs" /> <Compile Include="Events\TerminationRequestedEventHandler.cs" />
<Compile Include="Filters\IRequestFilter.cs" /> <Compile Include="Filters\IRequestFilter.cs" />

View file

@ -58,6 +58,7 @@ namespace SafeExamBrowser.Browser
public event DownloadRequestedEventHandler ConfigurationDownloadRequested; public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected; public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event TerminationRequestedEventHandler TerminationRequested; public event TerminationRequestedEventHandler TerminationRequested;
public event WindowsChangedEventHandler WindowsChanged; public event WindowsChangedEventHandler WindowsChanged;
@ -195,6 +196,7 @@ namespace SafeExamBrowser.Browser
window.ResetRequested += Window_ResetRequested; window.ResetRequested += Window_ResetRequested;
window.SessionIdentifierDetected += (i) => SessionIdentifierDetected?.Invoke(i); window.SessionIdentifierDetected += (i) => SessionIdentifierDetected?.Invoke(i);
window.TerminationRequested += () => TerminationRequested?.Invoke(); window.TerminationRequested += () => TerminationRequested?.Invoke();
window.LoseFocusRequested += (forward) => LoseFocusRequested?.Invoke(forward);
window.InitializeControl(); window.InitializeControl();
windows.Add(window); windows.Add(window);
@ -457,5 +459,13 @@ namespace SafeExamBrowser.Browser
CreateNewWindow(); CreateNewWindow();
logger.Info("Successfully reset browser."); logger.Info("Successfully reset browser.");
} }
public void Focus(bool forward)
{
windows.ForEach(window =>
{
window.Focus(forward);
});
}
} }
} }

View file

@ -15,6 +15,7 @@ using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography; using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
namespace SafeExamBrowser.Browser namespace SafeExamBrowser.Browser
@ -143,5 +144,19 @@ namespace SafeExamBrowser.Browser
{ {
control.BrowserCore.SetZoomLevel(level); control.BrowserCore.SetZoomLevel(level);
} }
/// <summary>
/// Executes the given Javascript code in the browser.
/// </summary>
public async void ExecuteJavascript(string javascript, Action<JavascriptResult> callback)
{
var result = await this.control.EvaluateScriptAsync(javascript);
callback(new JavascriptResult()
{
Message = result.Message,
Result = result.Result,
Success = result.Success
});
}
} }
} }

View file

@ -80,6 +80,7 @@ namespace SafeExamBrowser.Browser
internal event PopupRequestedEventHandler PopupRequested; internal event PopupRequestedEventHandler PopupRequested;
internal event ResetRequestedEventHandler ResetRequested; internal event ResetRequestedEventHandler ResetRequested;
internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected; internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
internal event LoseFocusRequestedEventHandler LoseFocusRequested;
internal event TerminationRequestedEventHandler TerminationRequested; internal event TerminationRequestedEventHandler TerminationRequested;
public event IconChangedEventHandler IconChanged; public event IconChangedEventHandler IconChanged;
@ -162,6 +163,8 @@ namespace SafeExamBrowser.Browser
keyboardHandler.ZoomInRequested += ZoomInRequested; keyboardHandler.ZoomInRequested += ZoomInRequested;
keyboardHandler.ZoomOutRequested += ZoomOutRequested; keyboardHandler.ZoomOutRequested += ZoomOutRequested;
keyboardHandler.ZoomResetRequested += ZoomResetRequested; keyboardHandler.ZoomResetRequested += ZoomResetRequested;
keyboardHandler.TabPressed += TabPressed;
keyboardHandler.FocusAddressBarRequested += FocusAddressBarRequested;
resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id); resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id);
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited; requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
requestHandler.RequestBlocked += RequestHandler_RequestBlocked; requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
@ -189,7 +192,7 @@ namespace SafeExamBrowser.Browser
internal void InitializeWindow() internal void InitializeWindow()
{ {
window = uiFactory.CreateBrowserWindow(Control, settings, isMainWindow); window = uiFactory.CreateBrowserWindow(Control, settings, isMainWindow, this.logger);
window.AddressChanged += Window_AddressChanged; window.AddressChanged += Window_AddressChanged;
window.BackwardNavigationRequested += Window_BackwardNavigationRequested; window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
window.Closed += Window_Closed; window.Closed += Window_Closed;
@ -198,6 +201,7 @@ namespace SafeExamBrowser.Browser
window.FindRequested += Window_FindRequested; window.FindRequested += Window_FindRequested;
window.ForwardNavigationRequested += Window_ForwardNavigationRequested; window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
window.HomeNavigationRequested += HomeNavigationRequested; window.HomeNavigationRequested += HomeNavigationRequested;
window.LoseFocusRequested += Window_LoseFocusRequested;
window.ReloadRequested += ReloadRequested; window.ReloadRequested += ReloadRequested;
window.ZoomInRequested += ZoomInRequested; window.ZoomInRequested += ZoomInRequested;
window.ZoomOutRequested += ZoomOutRequested; window.ZoomOutRequested += ZoomOutRequested;
@ -454,6 +458,11 @@ namespace SafeExamBrowser.Browser
} }
} }
private void FocusAddressBarRequested()
{
window.FocusAddressBar();
}
private ChromiumHostControl LifeSpanHandler_CreatePopup() private ChromiumHostControl LifeSpanHandler_CreatePopup()
{ {
var args = new PopupRequestedEventArgs(); var args = new PopupRequestedEventArgs();
@ -655,6 +664,11 @@ namespace SafeExamBrowser.Browser
Control.NavigateForwards(); Control.NavigateForwards();
} }
private void Window_LoseFocusRequested(bool forward)
{
LoseFocusRequested?.Invoke(forward);
}
private void ZoomInRequested() private void ZoomInRequested()
{ {
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300) if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
@ -688,6 +702,43 @@ namespace SafeExamBrowser.Browser
} }
} }
private void TabPressed(bool shiftPressed)
{
this.Control.ExecuteJavascript("document.activeElement.tagName", result =>
{
var tagName = result.Result as string;
if (tagName != null)
{
if (tagName.ToUpper() == "BODY")
{
// this means the user is now at the start of the focus / tabIndex chain in the website
if (shiftPressed)
{
window.FocusToolbar(!shiftPressed);
}
else
{
this.LoseFocusRequested?.Invoke(true);
}
}
}
});
}
internal void Focus(bool forward)
{
if (forward)
{
window.FocusToolbar(forward);
}
else
{
window.FocusBrowser();
this.Activate();
}
}
private double CalculateZoomPercentage() private double CalculateZoomPercentage()
{ {
return (zoomLevel * 25.0) + 100.0; return (zoomLevel * 25.0) + 100.0;

View file

@ -8,6 +8,7 @@
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using CefSharp;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
namespace SafeExamBrowser.Browser.Handlers namespace SafeExamBrowser.Browser.Handlers
@ -20,6 +21,10 @@ namespace SafeExamBrowser.Browser.Handlers
internal event ActionRequestedEventHandler ZoomInRequested; internal event ActionRequestedEventHandler ZoomInRequested;
internal event ActionRequestedEventHandler ZoomOutRequested; internal event ActionRequestedEventHandler ZoomOutRequested;
internal event ActionRequestedEventHandler ZoomResetRequested; internal event ActionRequestedEventHandler ZoomResetRequested;
internal event ActionRequestedEventHandler FocusAddressBarRequested;
internal event TabPressedEventHandler TabPressed;
private int? currentKeyDown = null;
public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int keyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int keyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
{ {
@ -38,6 +43,11 @@ namespace SafeExamBrowser.Browser.Handlers
HomeNavigationRequested?.Invoke(); HomeNavigationRequested?.Invoke();
} }
if (ctrl && keyCode == (int) Keys.L)
{
FocusAddressBarRequested?.Invoke();
}
if ((ctrl && keyCode == (int) Keys.Add) || (ctrl && keyCode == (int) Keys.Oemplus) || (ctrl && shift && keyCode == (int) Keys.D1)) if ((ctrl && keyCode == (int) Keys.Add) || (ctrl && keyCode == (int) Keys.Oemplus) || (ctrl && shift && keyCode == (int) Keys.D1))
{ {
ZoomInRequested?.Invoke(); ZoomInRequested?.Invoke();
@ -52,8 +62,14 @@ namespace SafeExamBrowser.Browser.Handlers
{ {
ZoomResetRequested?.Invoke(); ZoomResetRequested?.Invoke();
} }
if (keyCode == (int)Keys.Tab && keyCode == currentKeyDown)
{
TabPressed?.Invoke(shift);
}
} }
currentKeyDown = null;
return false; return false;
} }
@ -66,6 +82,11 @@ namespace SafeExamBrowser.Browser.Handlers
return true; return true;
} }
if (type == KeyType.RawKeyDown || type == KeyType.KeyDown)
{
currentKeyDown = keyCode;
}
return false; return false;
} }
} }

View file

@ -188,6 +188,7 @@ namespace SafeExamBrowser.Client
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested; Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
Browser.SessionIdentifierDetected += Browser_SessionIdentifierDetected; Browser.SessionIdentifierDetected += Browser_SessionIdentifierDetected;
Browser.TerminationRequested += Browser_TerminationRequested; Browser.TerminationRequested += Browser_TerminationRequested;
Browser.LoseFocusRequested += Browser_LoseFocusRequested;
ClientHost.ExamSelectionRequested += ClientHost_ExamSelectionRequested; ClientHost.ExamSelectionRequested += ClientHost_ExamSelectionRequested;
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested; ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested += ClientHost_PasswordRequested; ClientHost.PasswordRequested += ClientHost_PasswordRequested;
@ -198,6 +199,7 @@ namespace SafeExamBrowser.Client
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged; displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
runtime.ConnectionLost += Runtime_ConnectionLost; runtime.ConnectionLost += Runtime_ConnectionLost;
systemMonitor.SessionSwitched += SystemMonitor_SessionSwitched; systemMonitor.SessionSwitched += SystemMonitor_SessionSwitched;
taskbar.LoseFocusRequested += Taskbar_LoseFocusRequested;
taskbar.QuitButtonClicked += Shell_QuitButtonClicked; taskbar.QuitButtonClicked += Shell_QuitButtonClicked;
foreach (var activator in context.Activators.OfType<ITerminationActivator>()) foreach (var activator in context.Activators.OfType<ITerminationActivator>())
@ -211,6 +213,16 @@ namespace SafeExamBrowser.Client
} }
} }
private void Taskbar_LoseFocusRequested(bool forward)
{
this.Browser.Focus(forward);
}
private void Browser_LoseFocusRequested(bool forward)
{
this.taskbar.Focus(forward);
}
private void DeregisterEvents() private void DeregisterEvents()
{ {
actionCenter.QuitButtonClicked -= Shell_QuitButtonClicked; actionCenter.QuitButtonClicked -= Shell_QuitButtonClicked;
@ -226,6 +238,7 @@ namespace SafeExamBrowser.Client
Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested; Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested;
Browser.SessionIdentifierDetected -= Browser_SessionIdentifierDetected; Browser.SessionIdentifierDetected -= Browser_SessionIdentifierDetected;
Browser.TerminationRequested -= Browser_TerminationRequested; Browser.TerminationRequested -= Browser_TerminationRequested;
Browser.LoseFocusRequested -= Browser_LoseFocusRequested;
} }
if (ClientHost != null) if (ClientHost != null)

View file

@ -24,12 +24,19 @@ namespace SafeExamBrowser.I18n.Contracts
Browser_LoadErrorTitle, Browser_LoadErrorTitle,
Browser_Name, Browser_Name,
Browser_Tooltip, Browser_Tooltip,
BrowserWindow_BackwardButton,
BrowserWindow_DeveloperConsoleMenuItem, BrowserWindow_DeveloperConsoleMenuItem,
BrowserWindow_Downloading, BrowserWindow_Downloading,
BrowserWindow_DownloadCancelled, BrowserWindow_DownloadCancelled,
BrowserWindow_DownloadComplete, BrowserWindow_DownloadComplete,
BrowserWindow_DownloadsButton,
BrowserWindow_FindCaseSensitive, BrowserWindow_FindCaseSensitive,
BrowserWindow_FindMenuItem, BrowserWindow_FindMenuItem,
BrowserWindow_ForwardButton,
BrowserWindow_HomeButton,
BrowserWindow_MenuButton,
BrowserWindow_ReloadButton,
BrowserWindow_UrlTextBox,
BrowserWindow_ZoomMenuItem, BrowserWindow_ZoomMenuItem,
Build, Build,
ExamSelectionDialog_Cancel, ExamSelectionDialog_Cancel,

View file

@ -48,6 +48,27 @@
<Entry key="BrowserWindow_FindMenuItem"> <Entry key="BrowserWindow_FindMenuItem">
Seite durchsuchen... Seite durchsuchen...
</Entry> </Entry>
<Entry key="BrowserWindow_ReloadButton">
Neu laden
</Entry>
<Entry key="BrowserWindow_BackwardButton">
Rückwärts
</Entry>
<Entry key="BrowserWindow_ForwardButton">
Vorwärts
</Entry>
<Entry key="BrowserWindow_DownloadsButton">
Download
</Entry>
<Entry key="BrowserWindow_HomeButton">
Home
</Entry>
<Entry key="BrowserWindow_MenuButton">
Menü
</Entry>
<Entry key="BrowserWindow_UrlTextBox">
URL eingeben
</Entry>
<Entry key="BrowserWindow_ZoomMenuItem"> <Entry key="BrowserWindow_ZoomMenuItem">
Seiten-Zoom Seiten-Zoom
</Entry> </Entry>

View file

@ -48,6 +48,27 @@
<Entry key="BrowserWindow_FindMenuItem"> <Entry key="BrowserWindow_FindMenuItem">
Search page... Search page...
</Entry> </Entry>
<Entry key="BrowserWindow_ReloadButton">
Reload
</Entry>
<Entry key="BrowserWindow_BackwardButton">
Backward
</Entry>
<Entry key="BrowserWindow_ForwardButton">
Forward
</Entry>
<Entry key="BrowserWindow_DownloadsButton">
Download
</Entry>
<Entry key="BrowserWindow_HomeButton">
Home
</Entry>
<Entry key="BrowserWindow_MenuButton">
Menu
</Entry>
<Entry key="BrowserWindow_UrlTextBox">
Enter URL
</Entry>
<Entry key="BrowserWindow_ZoomMenuItem"> <Entry key="BrowserWindow_ZoomMenuItem">
Page Zoom Page Zoom
</Entry> </Entry>

View file

@ -48,6 +48,27 @@
<Entry key="BrowserWindow_FindMenuItem"> <Entry key="BrowserWindow_FindMenuItem">
Rechercher dans la page... Rechercher dans la page...
</Entry> </Entry>
<Entry key="BrowserWindow_ReloadButton">
Recharger
</Entry>
<Entry key="BrowserWindow_BackwardButton">
En arrière
</Entry>
<Entry key="BrowserWindow_ForwardButton">
Avant
</Entry>
<Entry key="BrowserWindow_DownloadsButton">
Télécharger
</Entry>
<Entry key="BrowserWindow_HomeButton">
Accueil
</Entry>
<Entry key="BrowserWindow_MenuButton">
Menu
</Entry>
<Entry key="BrowserWindow_UrlTextBox">
Entrer l'URL
</Entry>
<Entry key="BrowserWindow_ZoomMenuItem"> <Entry key="BrowserWindow_ZoomMenuItem">
Zoom de la page Zoom de la page
</Entry> </Entry>

View file

@ -48,6 +48,27 @@
<Entry key="BrowserWindow_FindMenuItem"> <Entry key="BrowserWindow_FindMenuItem">
Pagina di ricerca ... Pagina di ricerca ...
</Entry> </Entry>
<Entry key="BrowserWindow_ReloadButton">
Ricarica
</Entry>
<Entry key="BrowserWindow_BackwardButton">
Indietro
</Entry>
<Entry key="BrowserWindow_ForwardButton">
Avanti
</Entry>
<Entry key="BrowserWindow_DownloadsButton">
Scaricare
</Entry>
<Entry key="BrowserWindow_HomeButton">
Home
</Entry>
<Entry key="BrowserWindow_MenuButton">
Menu
</Entry>
<Entry key="BrowserWindow_UrlTextBox">
Inserisci URL
</Entry>
<Entry key="BrowserWindow_ZoomMenuItem"> <Entry key="BrowserWindow_ZoomMenuItem">
Zoom della pagina Zoom della pagina
</Entry> </Entry>

View file

@ -45,6 +45,27 @@
<Entry key="BrowserWindow_DownloadComplete"> <Entry key="BrowserWindow_DownloadComplete">
下载完成 下载完成
</Entry> </Entry>
<Entry key="BrowserWindow_ReloadButton">
重新加载
</Entry>
<Entry key="BrowserWindow_BackwardButton">
向后
</Entry>
<Entry key="BrowserWindow_ForwardButton">
前进
</Entry>
<Entry key="BrowserWindow_DownloadsButton">
下载
</Entry>
<Entry key="BrowserWindow_HomeButton">
首页
</Entry>
<Entry key="BrowserWindow_MenuButton">
菜单
</Entry>
<Entry key="BrowserWindow_UrlTextBox">
输入网址
</Entry>
<Entry key="BrowserWindow_ZoomMenuItem"> <Entry key="BrowserWindow_ZoomMenuItem">
页面缩放 页面缩放
</Entry> </Entry>

View file

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SafeExamBrowser.UserInterface.Contracts.Browser.Data
{
/// <summary>
/// The data resulting from a Javascript expression evaluation.
/// </summary>
public class JavascriptResult
{
/// <summary>
/// Indicates if the Javascript was evaluated successfully or not.
/// </summary>
public bool Success { get; set; }
/// <summary>
/// The error message, in case of an unsuccessful evaluation of the Javascript expression.
/// </summary>
public string Message { get; set; }
/// <summary>
/// The data item returned by the Javascript expression.
/// </summary>
public dynamic Result { get; set; }
}
}

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
namespace SafeExamBrowser.UserInterface.Contracts.Browser namespace SafeExamBrowser.UserInterface.Contracts.Browser
@ -61,6 +62,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
/// </summary> /// </summary>
void Destroy(); void Destroy();
/// <summary>
/// Executes the given Javascript code in the browser.
/// </summary>
void ExecuteJavascript(string javascript, System.Action<JavascriptResult> callback);
/// <summary> /// <summary>
/// Attempts to find the given term on the current page according to the specified parameters. /// Attempts to find the given term on the current page according to the specified parameters.
/// </summary> /// </summary>

View file

@ -7,6 +7,7 @@
*/ */
using System; using System;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons; using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data; using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
@ -64,6 +65,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
/// </summary> /// </summary>
event ActionRequestedEventHandler HomeNavigationRequested; event ActionRequestedEventHandler HomeNavigationRequested;
/// <summary>
/// Event fired when the browser window wants to lose focus to the taskbar.
/// </summary>
event LoseFocusRequestedEventHandler LoseFocusRequested;
/// <summary> /// <summary>
/// Event fired when the user would like to reload the current page. /// Event fired when the user would like to reload the current page.
/// </summary> /// </summary>
@ -123,5 +129,18 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
/// Updates the display value of the current page zoom. Value is expected to be in percentage. /// Updates the display value of the current page zoom. Value is expected to be in percentage.
/// </summary> /// </summary>
void UpdateZoomLevel(double value); void UpdateZoomLevel(double value);
/// <summary>
/// Sets the focus to the toolbar.
/// </summary>
/// <param name="forward">If true, the first focusable control on the toolbar gets focused. If false, the last one.</param>
void FocusToolbar(bool forward);
/// <summary>
/// Sets the focus to the browser.
/// </summary>
void FocusBrowser();
void FocusAddressBar();
} }
} }

View file

@ -56,7 +56,7 @@ namespace SafeExamBrowser.UserInterface.Contracts
/// <summary> /// <summary>
/// Creates a new browser window loaded with the given browser control and settings. /// Creates a new browser window loaded with the given browser control and settings.
/// </summary> /// </summary>
IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow); IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow, ILogger logger);
/// <summary> /// <summary>
/// Creates an exam selection dialog for the given exams. /// Creates an exam selection dialog for the given exams.

View file

@ -55,6 +55,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Browser\Data\DownloadItemState.cs" /> <Compile Include="Browser\Data\DownloadItemState.cs" />
<Compile Include="Browser\Data\JavascriptResult.cs" />
<Compile Include="Browser\Events\AddressChangedEventHandler.cs" /> <Compile Include="Browser\Events\AddressChangedEventHandler.cs" />
<Compile Include="Browser\Events\FindRequestedEventHandler.cs" /> <Compile Include="Browser\Events\FindRequestedEventHandler.cs" />
<Compile Include="Browser\Events\LoadFailedEventHandler.cs" /> <Compile Include="Browser\Events\LoadFailedEventHandler.cs" />
@ -110,6 +111,10 @@
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project> <Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
<Name>SafeExamBrowser.Applications.Contracts</Name> <Name>SafeExamBrowser.Applications.Contracts</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Browser.Contracts\SafeExamBrowser.Browser.Contracts.csproj">
<Project>{5fb5273d-277c-41dd-8593-a25ce1aff2e9}</Project>
<Name>SafeExamBrowser.Browser.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj"> <ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project> <Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
<Name>SafeExamBrowser.Configuration.Contracts</Name> <Name>SafeExamBrowser.Configuration.Contracts</Name>

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell.Events; using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
@ -31,6 +32,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
/// </summary> /// </summary>
event QuitButtonClickedEventHandler QuitButtonClicked; event QuitButtonClickedEventHandler QuitButtonClicked;
/// <summary>
/// Event fired when the Taskbar wants to lose focus.
/// </summary>
event LoseFocusRequestedEventHandler LoseFocusRequested;
/// <summary> /// <summary>
/// Adds the given application control to the taskbar. /// Adds the given application control to the taskbar.
/// </summary> /// </summary>
@ -70,5 +76,10 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
/// Shows the taskbar. /// Shows the taskbar.
/// </summary> /// </summary>
void Show(); void Show();
/// <summary>
/// Puts the focus on the taskbar.
/// </summary>
void Focus(bool forward = true);
} }
} }

View file

@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fa="http://schemas.fontawesome.io/icons/" xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="250"> mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="250" IsTabStop="True">
<UserControl.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View file

@ -16,7 +16,7 @@
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid x:Name="Grid" Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2"> <Grid x:Name="Grid" Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2">
<Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}"> <Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}" KeyUp="Popup_KeyUp">
<Border Background="Gray"> <Border Background="Gray">
<ScrollViewer MaxHeight="250" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}"> <ScrollViewer MaxHeight="250" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
<StackPanel x:Name="LayoutsStackPanel" /> <StackPanel x:Name="LayoutsStackPanel" />

View file

@ -41,7 +41,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
InitializeLayouts(); InitializeLayouts();
keyboard.LayoutChanged += Keyboard_LayoutChanged; 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)); 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.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray; Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
@ -90,5 +97,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
Text.Text = layout.CultureName; Text.Text = layout.CultureName;
Button.ToolTip = tooltip; 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();
}
}
} }
} }

View file

@ -16,7 +16,7 @@
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}"> <Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}" KeyDown="Popup_KeyDown">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0"> <Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0">
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<TextBlock x:Name="AudioDeviceName" Margin="5" TextAlignment="Center" /> <TextBlock x:Name="AudioDeviceName" Margin="5" TextAlignment="Center" />
@ -24,7 +24,7 @@
<Button x:Name="MuteButton" Background="Transparent" Padding="5" Template="{StaticResource TaskbarButton}" Width="40"> <Button x:Name="MuteButton" Background="Transparent" Padding="5" Template="{StaticResource TaskbarButton}" Width="40">
<ContentControl x:Name="PopupIcon" /> <ContentControl x:Name="PopupIcon" />
</Button> </Button>
<Slider x:Name="Volume" Grid.Column="1" Orientation="Horizontal" TickFrequency="1" Maximum="100" IsSnapToTickEnabled="True" <Slider x:Name="Volume" Grid.Column="1" Orientation="Horizontal" TickFrequency="1" Maximum="100" IsSnapToTickEnabled="True" KeyDown="Volume_KeyDown"
IsMoveToPointEnabled="True" VerticalAlignment="Center" Width="250" Thumb.DragStarted="Volume_DragStarted" Thumb.DragCompleted="Volume_DragCompleted" /> IsMoveToPointEnabled="True" VerticalAlignment="Center" Width="250" Thumb.DragStarted="Volume_DragStarted" Thumb.DragCompleted="Volume_DragCompleted" />
<TextBlock Grid.Column="2" FontWeight="DemiBold" FontSize="16" Text="{Binding ElementName=Volume, Path=Value}" <TextBlock Grid.Column="2" FontWeight="DemiBold" FontSize="16" Text="{Binding ElementName=Volume, Path=Value}"
TextAlignment="Center" VerticalAlignment="Center" Width="40" /> TextAlignment="Center" VerticalAlignment="Center" Width="40" />
@ -33,7 +33,7 @@
</Border> </Border>
</Popup> </Popup>
<Button x:Name="Button" Background="Transparent" Padding="5" Template="{StaticResource TaskbarButton}" ToolTipService.ShowOnDisabled="True" Width="40"> <Button x:Name="Button" Background="Transparent" Padding="5" Template="{StaticResource TaskbarButton}" ToolTipService.ShowOnDisabled="True" Width="40">
<ContentControl x:Name="ButtonIcon" /> <ContentControl x:Name="ButtonIcon" Focusable="False" />
</Button> </Button>
</Grid> </Grid>
</UserControl> </UserControl>

View file

@ -60,6 +60,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
{ {
Background = Brushes.LightGray; Background = Brushes.LightGray;
Button.Background = Brushes.LightGray; Button.Background = Brushes.LightGray;
Volume.Focus();
}; };
Popup.Closed += (o, args) => Popup.Closed += (o, args) =>
@ -167,5 +168,23 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
return IconResourceLoader.Load(resource); 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();
}
}
} }
} }

View file

@ -11,7 +11,7 @@
<RowDefinition Height="1*" /> <RowDefinition Height="1*" />
<RowDefinition Height="1*" /> <RowDefinition Height="1*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock x:Name="TimeTextBlock" Grid.Row="0" Text="{Binding Path=Time}" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Bottom" /> <TextBlock x:Name="TimeTextBlock" Grid.Row="0" Text="{Binding Path=Time}" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Bottom" Focusable="True" AutomationProperties.HelpText="{Binding Path=Time}" />
<TextBlock x:Name="DateTextBlock" Grid.Row="1" Text="{Binding Path=Date}" HorizontalAlignment="Center" VerticalAlignment="Top" /> <TextBlock x:Name="DateTextBlock" Grid.Row="1" Text="{Binding Path=Date}" HorizontalAlignment="Center" VerticalAlignment="Top" Focusable="True" AutomationProperties.HelpText="{Binding Path=Date}" />
</Grid> </Grid>
</UserControl> </UserControl>

View file

@ -14,7 +14,6 @@
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid>
<Button x:Name="Button" Background="Transparent" Height="40" Padding="10,0" Template="{StaticResource TaskbarButton}"> <Button x:Name="Button" Background="Transparent" Height="40" Padding="10,0" Template="{StaticResource TaskbarButton}">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@ -33,5 +32,4 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
</Button> </Button>
</Grid>
</UserControl> </UserControl>

View file

@ -43,6 +43,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
CultureCodeTextBlock.Text = layout.CultureCode; CultureCodeTextBlock.Text = layout.CultureCode;
CultureNameTextBlock.Text = layout.CultureName; CultureNameTextBlock.Text = layout.CultureName;
LayoutNameTextBlock.Text = layout.LayoutName; LayoutNameTextBlock.Text = layout.LayoutName;
System.Windows.Automation.AutomationProperties.SetHelpText(Button, layout.LayoutName);
} }
} }
} }

View file

@ -16,7 +16,7 @@
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}"> <Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}" KeyUp="Popup_KeyUp">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0"> <Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0">
<ScrollViewer x:Name="LayoutsScrollViewer" MaxHeight="250" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}"> <ScrollViewer x:Name="LayoutsScrollViewer" MaxHeight="250" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
<StackPanel x:Name="LayoutsStackPanel" /> <StackPanel x:Name="LayoutsStackPanel" />

View file

@ -45,7 +45,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
InitializeLayouts(); InitializeLayouts();
keyboard.LayoutChanged += Keyboard_LayoutChanged; 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)); Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback); Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver)); Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
@ -114,5 +121,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
LayoutCultureCode.Text = layout.CultureCode; LayoutCultureCode.Text = layout.CultureCode;
Button.ToolTip = tooltip; 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();
}
}
} }
} }

View file

@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40"> mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40" Focusable="true" IsTabStop="True">
<UserControl.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View file

@ -8,6 +8,7 @@
using System; using System;
using System.Windows; using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Media; using System.Windows.Media;
@ -101,6 +102,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
Button.ToolTip = tooltip; Button.ToolTip = tooltip;
PowerPlug.Visibility = status.IsOnline ? Visibility.Visible : Visibility.Collapsed; PowerPlug.Visibility = status.IsOnline ? Visibility.Visible : Visibility.Collapsed;
Warning.Visibility = status.BatteryChargeStatus == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed; Warning.Visibility = status.BatteryChargeStatus == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed;
AutomationProperties.SetHelpText(this, Button.ToolTip as string);
} }
private void RenderCharge(double charge, BatteryChargeStatus status) private void RenderCharge(double charge, BatteryChargeStatus status)

View file

@ -4,6 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
x:Name="root"
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40"> mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
<UserControl.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
@ -15,6 +16,7 @@
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<Button x:Name="Button" Click="Button_Click" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch" <Button x:Name="Button" Click="Button_Click" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch"
Template="{StaticResource TaskbarButton}" VerticalAlignment="Stretch" /> Template="{StaticResource TaskbarButton}" VerticalAlignment="Stretch"
AutomationProperties.Name="{Binding Path=(AutomationProperties.Name), ElementName=root}"/>
</Grid> </Grid>
</UserControl> </UserControl>

View file

@ -509,6 +509,14 @@
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project> <Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
<Name>SafeExamBrowser.Applications.Contracts</Name> <Name>SafeExamBrowser.Applications.Contracts</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Browser.Contracts\SafeExamBrowser.Browser.Contracts.csproj">
<Project>{5fb5273d-277c-41dd-8593-a25ce1aff2e9}</Project>
<Name>SafeExamBrowser.Browser.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Browser\SafeExamBrowser.Browser.csproj">
<Project>{04e653f1-98e6-4e34-9dd7-7f2bc1a8b767}</Project>
<Name>SafeExamBrowser.Browser</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj"> <ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project> <Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
<Name>SafeExamBrowser.Configuration.Contracts</Name> <Name>SafeExamBrowser.Configuration.Contracts</Name>

View file

@ -80,9 +80,9 @@ namespace SafeExamBrowser.UserInterface.Desktop
} }
} }
public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow) public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow, ILogger logger)
{ {
return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text)); return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text, logger));
} }
public IExamSelectionDialog CreateExamSelectionDialog(IEnumerable<Exam> exams) public IExamSelectionDialog CreateExamSelectionDialog(IEnumerable<Exam> exams)

View file

@ -62,15 +62,15 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Name="ZoomText" HorizontalAlignment="Left" Margin="10,0" VerticalAlignment="Center" /> <TextBlock Grid.Column="0" x:Name="ZoomText" HorizontalAlignment="Left" Margin="10,0" VerticalAlignment="Center" />
<Button Grid.Column="1" x:Name="ZoomInButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}"> <Button Grid.Column="1" x:Name="ZoomInButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" TabIndex="30">
<fa:ImageAwesome Icon="SearchPlus" /> <fa:ImageAwesome Icon="SearchPlus" />
</Button> </Button>
<Button Grid.Column="2" x:Name="ZoomResetButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}"> <Button Grid.Column="2" x:Name="ZoomResetButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" TabIndex="31">
<Viewbox Stretch="Uniform"> <Viewbox Stretch="Uniform">
<TextBlock x:Name="ZoomLevel" /> <TextBlock x:Name="ZoomLevel" />
</Viewbox> </Viewbox>
</Button> </Button>
<Button Grid.Column="3" x:Name="ZoomOutButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}"> <Button Grid.Column="3" x:Name="ZoomOutButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" TabIndex="32">
<fa:ImageAwesome Icon="SearchMinus" /> <fa:ImageAwesome Icon="SearchMinus" />
</Button> </Button>
</Grid> </Grid>
@ -80,7 +80,7 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Name="FindMenuText" HorizontalAlignment="Left" Margin="10,0" VerticalAlignment="Center" /> <TextBlock Grid.Column="0" x:Name="FindMenuText" HorizontalAlignment="Left" Margin="10,0" VerticalAlignment="Center" />
<Button Grid.Column="1" x:Name="FindMenuButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}"> <Button Grid.Column="1" x:Name="FindMenuButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" TabIndex="33">
<fa:ImageAwesome Icon="Search" /> <fa:ImageAwesome Icon="Search" />
</Button> </Button>
</Grid> </Grid>
@ -90,7 +90,7 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Name="DeveloperConsoleText" HorizontalAlignment="Left" Margin="10,0" VerticalAlignment="Center" /> <TextBlock Grid.Column="0" x:Name="DeveloperConsoleText" HorizontalAlignment="Left" Margin="10,0" VerticalAlignment="Center" />
<Button Grid.Column="1" x:Name="DeveloperConsoleButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}"> <Button Grid.Column="1" x:Name="DeveloperConsoleButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" TabIndex="34">
<fa:ImageAwesome Icon="Wrench" /> <fa:ImageAwesome Icon="Wrench" />
</Button> </Button>
</Grid> </Grid>

View file

@ -8,14 +8,17 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons; using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
@ -33,9 +36,13 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
private readonly bool isMainWindow; private readonly bool isMainWindow;
private readonly BrowserSettings settings; private readonly BrowserSettings settings;
private readonly IText text; private readonly IText text;
private readonly ILogger logger;
private readonly IBrowserControl browserControl;
private WindowClosedEventHandler closed; private WindowClosedEventHandler closed;
private WindowClosingEventHandler closing; private WindowClosingEventHandler closing;
private bool browserControlGetsFocusFromTaskbar = false;
private IInputElement tabKeyDownFocusElement = null;
private WindowSettings WindowSettings private WindowSettings WindowSettings
{ {
@ -56,6 +63,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
public event ActionRequestedEventHandler ZoomInRequested; public event ActionRequestedEventHandler ZoomInRequested;
public event ActionRequestedEventHandler ZoomOutRequested; public event ActionRequestedEventHandler ZoomOutRequested;
public event ActionRequestedEventHandler ZoomResetRequested; public event ActionRequestedEventHandler ZoomResetRequested;
public event LoseFocusRequestedEventHandler LoseFocusRequested;
event WindowClosedEventHandler IWindow.Closed event WindowClosedEventHandler IWindow.Closed
{ {
@ -69,11 +77,13 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
remove { closing -= value; } remove { closing -= value; }
} }
internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text) internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text, ILogger logger)
{ {
this.isMainWindow = isMainWindow; this.isMainWindow = isMainWindow;
this.settings = settings; this.settings = settings;
this.text = text; this.text = text;
this.logger = logger;
this.browserControl = browserControl;
InitializeComponent(); InitializeComponent();
InitializeBrowserWindow(browserControl); InitializeBrowserWindow(browserControl);
@ -199,6 +209,33 @@ namespace SafeExamBrowser.UserInterface.Desktop.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) private void BrowserWindow_KeyUp(object sender, KeyEventArgs e)
{ {
if (e.Key == Key.F5) if (e.Key == Key.F5)
@ -215,6 +252,65 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{ {
ShowFindbar(); 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();
}
}
}
}
}
/// <summary>
/// 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
/// </summary>
/// <param name="e">The element to get next tab order</param>
/// <param name="container">The container element owning 'e'. Make sure this is a container of 'e'.</param>
/// <param name="goDownOnly">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.</param>
/// <returns>Next tab order element or null if not found</returns>
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) private void BrowserWindow_Loaded(object sender, RoutedEventArgs e)
@ -320,10 +416,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke(); ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke();
HomeButton.Click += (o, args) => HomeNavigationRequested?.Invoke(); HomeButton.Click += (o, args) => HomeNavigationRequested?.Invoke();
Loaded += BrowserWindow_Loaded; 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)); MenuButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver));
MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback); MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver)); MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver));
KeyDown += BrowserWindow_KeyDown;
KeyUp += BrowserWindow_KeyUp; KeyUp += BrowserWindow_KeyUp;
LocationChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; }; LocationChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; };
ReloadButton.Click += (o, args) => ReloadRequested?.Invoke(); ReloadButton.Click += (o, args) => ReloadRequested?.Invoke();
@ -338,6 +435,48 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
ZoomInButton.Click += (o, args) => ZoomInRequested?.Invoke(); ZoomInButton.Click += (o, args) => ZoomInRequested?.Invoke();
ZoomOutButton.Click += (o, args) => ZoomOutRequested?.Invoke(); ZoomOutButton.Click += (o, args) => ZoomOutRequested?.Invoke();
ZoomResetButton.Click += (o, args) => ZoomResetRequested?.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() private void ApplySettings()
@ -443,6 +582,58 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
FindCaseSensitiveCheckBox.Content = text.Get(TextKey.BrowserWindow_FindCaseSensitive); FindCaseSensitiveCheckBox.Content = text.Get(TextKey.BrowserWindow_FindCaseSensitive);
FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem); FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem);
ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem); ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem);
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)
{
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()
{
this.Dispatcher.BeginInvoke((Action)(async () =>
{
this.FocusToolbar(false);
await Task.Delay(100);
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.Dispatcher.BeginInvoke((Action)(async () =>
{
this.UrlTextBox.Focus();
}));
} }
} }
} }

View file

@ -6,6 +6,7 @@
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar"
xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d" Title="Taskbar" Background="{DynamicResource BackgroundBrush}" Height="40" Width="750" WindowStyle="None" Topmost="True" mc:Ignorable="d" Title="Taskbar" Background="{DynamicResource BackgroundBrush}" Height="40" Width="750" WindowStyle="None" Topmost="True"
KeyDown="Window_KeyDown" KeyUp="Window_KeyUp"
ResizeMode="NoResize" Icon="../Images/SafeExamBrowser.ico"> ResizeMode="NoResize" Icon="../Images/SafeExamBrowser.ico">
<Window.Resources> <Window.Resources>
<ResourceDictionary> <ResourceDictionary>

View file

@ -6,8 +6,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using System.ComponentModel; using System.ComponentModel;
using System.Windows; using System.Windows;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -20,6 +22,8 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{ {
private bool allowClose; private bool allowClose;
private ILogger logger; private ILogger logger;
private bool isQuitButtonFocusedAtKeyDown;
private bool isFirstChildFocusedAtKeyDown;
public bool ShowClock public bool ShowClock
{ {
@ -31,6 +35,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); } set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
} }
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event QuitButtonClickedEventHandler QuitButtonClicked; public event QuitButtonClickedEventHandler QuitButtonClicked;
internal Taskbar(ILogger logger) internal Taskbar(ILogger logger)
@ -120,7 +125,9 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{ {
Dispatcher.Invoke(() => 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);
}); });
} }
@ -159,5 +166,82 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
Loaded += (o, args) => InitializeBounds(); Loaded += (o, args) => InitializeBounds();
QuitButton.Clicked += QuitButton_Clicked; QuitButton.Clicked += QuitButton_Clicked;
} }
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;
}
} }
} }

View file

@ -207,6 +207,10 @@
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project> <Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
<Name>SafeExamBrowser.Applications.Contracts</Name> <Name>SafeExamBrowser.Applications.Contracts</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Browser.Contracts\SafeExamBrowser.Browser.Contracts.csproj">
<Project>{5fb5273d-277c-41dd-8593-a25ce1aff2e9}</Project>
<Name>SafeExamBrowser.Browser.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj"> <ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project> <Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
<Name>SafeExamBrowser.Configuration.Contracts</Name> <Name>SafeExamBrowser.Configuration.Contracts</Name>

View file

@ -80,9 +80,9 @@ namespace SafeExamBrowser.UserInterface.Mobile
} }
} }
public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow) public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow, ILogger logger)
{ {
return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text)); return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text, logger));
} }
public IExamSelectionDialog CreateExamSelectionDialog(IEnumerable<Exam> exams) public IExamSelectionDialog CreateExamSelectionDialog(IEnumerable<Exam> exams)

View file

@ -14,8 +14,10 @@ using System.Windows.Controls.Primitives;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons; using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
@ -36,6 +38,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
private WindowClosedEventHandler closed; private WindowClosedEventHandler closed;
private WindowClosingEventHandler closing; private WindowClosingEventHandler closing;
private ILogger logger;
private WindowSettings WindowSettings private WindowSettings WindowSettings
{ {
@ -51,6 +54,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
public event ActionRequestedEventHandler DeveloperConsoleRequested; public event ActionRequestedEventHandler DeveloperConsoleRequested;
public event FindRequestedEventHandler FindRequested; public event FindRequestedEventHandler FindRequested;
public event ActionRequestedEventHandler ForwardNavigationRequested; public event ActionRequestedEventHandler ForwardNavigationRequested;
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event ActionRequestedEventHandler HomeNavigationRequested; public event ActionRequestedEventHandler HomeNavigationRequested;
public event ActionRequestedEventHandler ReloadRequested; public event ActionRequestedEventHandler ReloadRequested;
public event ActionRequestedEventHandler ZoomInRequested; public event ActionRequestedEventHandler ZoomInRequested;
@ -69,11 +73,12 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
remove { closing -= value; } remove { closing -= value; }
} }
internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text) internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text, ILogger logger)
{ {
this.isMainWindow = isMainWindow; this.isMainWindow = isMainWindow;
this.settings = settings; this.settings = settings;
this.text = text; this.text = text;
this.logger = logger;
InitializeComponent(); InitializeComponent();
InitializeBrowserWindow(browserControl); InitializeBrowserWindow(browserControl);
@ -454,5 +459,25 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem); FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem);
ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem); ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem);
} }
public void FocusToolbar(bool forward)
{
throw new NotImplementedException();
}
public void FocusBrowser()
{
throw new NotImplementedException();
}
public void Debug()
{
throw new NotImplementedException();
}
public void FocusAddressBar()
{
this.UrlTextBox.Focus();
}
} }
} }

View file

@ -8,6 +8,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Windows; using System.Windows;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -31,6 +32,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); } set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
} }
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event QuitButtonClickedEventHandler QuitButtonClicked; public event QuitButtonClickedEventHandler QuitButtonClicked;
internal Taskbar(ILogger logger) internal Taskbar(ILogger logger)
@ -159,5 +161,18 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
Loaded += (o, args) => InitializeBounds(); Loaded += (o, args) => InitializeBounds();
QuitButton.Clicked += QuitButton_Clicked; QuitButton.Clicked += QuitButton_Clicked;
} }
void ITaskbar.Focus(bool fromTop)
{
base.Activate();
if (fromTop)
{
this.ApplicationStackPanel.Children[0].Focus();
}
else
{
this.QuitButton.Focus();
}
}
} }
} }