SEBWIN-312: Implemented folder dialog for custom application path selection.

This commit is contained in:
dbuechel 2019-12-06 17:42:46 +01:00
parent b1781ba1ed
commit 6e7ddf1f8a
24 changed files with 248 additions and 33 deletions

View file

@ -26,7 +26,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
private Mock<ILogger> logger;
private AppSettings settings;
private Mock<ITaskbar> taskbar;
private Mock<ITaskView> taskView;
private Mock<ITaskview> taskview;
private Mock<IUserInterfaceFactory> uiFactory;
private BrowserOperation sut;
@ -40,13 +40,13 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
logger = new Mock<ILogger>();
settings = new AppSettings();
taskbar = new Mock<ITaskbar>();
taskView = new Mock<ITaskView>();
taskview = new Mock<ITaskview>();
uiFactory = new Mock<IUserInterfaceFactory>();
context.Browser = browser.Object;
context.Settings = settings;
sut = new BrowserOperation(actionCenter.Object, context, logger.Object, taskbar.Object, taskView.Object, uiFactory.Object);
sut = new BrowserOperation(actionCenter.Object, context, logger.Object, taskbar.Object, taskview.Object, uiFactory.Object);
}
[TestMethod]

View file

@ -38,7 +38,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
private Mock<IPowerSupply> powerSupply;
private Mock<ISystemInfo> systemInfo;
private Mock<ITaskbar> taskbar;
private Mock<ITaskView> taskView;
private Mock<ITaskview> taskview;
private Mock<IText> text;
private Mock<IUserInterfaceFactory> uiFactory;
private Mock<IWirelessAdapter> wirelessAdapter;
@ -60,7 +60,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
powerSupply = new Mock<IPowerSupply>();
systemInfo = new Mock<ISystemInfo>();
taskbar = new Mock<ITaskbar>();
taskView = new Mock<ITaskView>();
taskview = new Mock<ITaskview>();
text = new Mock<IText>();
uiFactory = new Mock<IUserInterfaceFactory>();
wirelessAdapter = new Mock<IWirelessAdapter>();
@ -84,7 +84,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
powerSupply.Object,
systemInfo.Object,
taskbar.Object,
taskView.Object,
taskview.Object,
text.Object,
uiFactory.Object,
wirelessAdapter.Object);
@ -94,7 +94,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
public void Perform_MustInitializeActivators()
{
var actionCenterActivator = new Mock<IActionCenterActivator>();
var taskViewActivator = new Mock<ITaskViewActivator>();
var taskViewActivator = new Mock<ITaskviewActivator>();
var terminationActivator = new Mock<ITerminationActivator>();
context.Activators.Add(actionCenterActivator.Object);
@ -261,18 +261,18 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
public void Revert_MustTerminateActivators()
{
var actionCenterActivator = new Mock<IActionCenterActivator>();
var taskViewActivator = new Mock<ITaskViewActivator>();
var taskviewActivator = new Mock<ITaskviewActivator>();
var terminationActivator = new Mock<ITerminationActivator>();
context.Activators.Add(actionCenterActivator.Object);
context.Activators.Add(taskViewActivator.Object);
context.Activators.Add(taskviewActivator.Object);
context.Activators.Add(terminationActivator.Object);
context.Settings.ActionCenter.EnableActionCenter = true;
sut.Revert();
actionCenterActivator.Verify(a => a.Stop(), Times.Once);
taskViewActivator.Verify(a => a.Stop(), Times.Once);
taskviewActivator.Verify(a => a.Stop(), Times.Once);
terminationActivator.Verify(a => a.Stop(), Times.Once);
}

View file

@ -528,7 +528,15 @@ namespace SafeExamBrowser.Client
private void AskForApplicationPath(ApplicationNotFoundEventArgs args)
{
// TODO
var message = text.Get(TextKey.FolderDialog_ApplicationLocation).Replace("%%NAME%%", args.DisplayName).Replace("%%EXECUTABLE%%", args.ExecutableName);
var dialog = uiFactory.CreateFolderDialog(message);
var result = dialog.Show(splashScreen);
if (result.Success)
{
args.CustomPath = result.FolderPath;
args.Success = true;
}
}
private void InformAboutFailedApplicationInitialization(ApplicationInitializationFailedEventArgs args)

View file

@ -71,7 +71,7 @@ namespace SafeExamBrowser.Client
private IRuntimeProxy runtimeProxy;
private ISystemInfo systemInfo;
private ITaskbar taskbar;
private ITaskView taskview;
private ITaskview taskview;
private IText text;
private ITextResource textResource;
private IUserInterfaceFactory uiFactory;
@ -95,7 +95,7 @@ namespace SafeExamBrowser.Client
uiFactory = BuildUserInterfaceFactory();
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client);
taskbar = BuildTaskbar();
taskview = BuildTaskView();
taskview = BuildTaskview();
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, processFactory);
@ -265,7 +265,7 @@ namespace SafeExamBrowser.Client
context.Activators.Add(new ActionCenterKeyboardActivator(ModuleLogger(nameof(ActionCenterKeyboardActivator)), nativeMethods));
context.Activators.Add(new ActionCenterTouchActivator(ModuleLogger(nameof(ActionCenterTouchActivator)), nativeMethods));
context.Activators.Add(new TaskViewKeyboardActivator(ModuleLogger(nameof(TaskViewKeyboardActivator)), nativeMethods));
context.Activators.Add(new TaskviewKeyboardActivator(ModuleLogger(nameof(TaskviewKeyboardActivator)), nativeMethods));
context.Activators.Add(new TerminationActivator(ModuleLogger(nameof(TerminationActivator)), nativeMethods));
return operation;
@ -304,7 +304,7 @@ namespace SafeExamBrowser.Client
}
}
private ITaskView BuildTaskView()
private ITaskview BuildTaskview()
{
switch (uiMode)
{

View file

@ -20,7 +20,7 @@ namespace SafeExamBrowser.Client.Operations
private IActionCenter actionCenter;
private ILogger logger;
private ITaskbar taskbar;
private ITaskView taskview;
private ITaskview taskview;
private IUserInterfaceFactory uiFactory;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
@ -31,7 +31,7 @@ namespace SafeExamBrowser.Client.Operations
ClientContext context,
ILogger logger,
ITaskbar taskbar,
ITaskView taskview,
ITaskview taskview,
IUserInterfaceFactory uiFactory) : base(context)
{
this.actionCenter = actionCenter;

View file

@ -35,7 +35,7 @@ namespace SafeExamBrowser.Client.Operations
private IPowerSupply powerSupply;
private ISystemInfo systemInfo;
private ITaskbar taskbar;
private ITaskView taskview;
private ITaskview taskview;
private IText text;
private IUserInterfaceFactory uiFactory;
private IWirelessAdapter wirelessAdapter;
@ -56,7 +56,7 @@ namespace SafeExamBrowser.Client.Operations
IPowerSupply powerSupply,
ISystemInfo systemInfo,
ITaskbar taskbar,
ITaskView taskview,
ITaskview taskview,
IText text,
IUserInterfaceFactory uiFactory,
IWirelessAdapter wirelessAdapter) : base(context)
@ -114,7 +114,7 @@ namespace SafeExamBrowser.Client.Operations
actionCenterActivator.Start();
}
if (Context.Settings.Keyboard.AllowAltTab && activator is ITaskViewActivator taskViewActivator)
if (Context.Settings.Keyboard.AllowAltTab && activator is ITaskviewActivator taskViewActivator)
{
taskview.Register(taskViewActivator);
taskViewActivator.Start();

View file

@ -23,6 +23,7 @@ namespace SafeExamBrowser.I18n.Contracts
BrowserWindow_DeveloperConsoleMenuItem,
BrowserWindow_ZoomMenuItem,
Build,
FolderDialog_ApplicationLocation,
LockScreen_AllowOption,
LockScreen_Message,
LockScreen_TerminateOption,

View file

@ -27,6 +27,9 @@
<Entry key="Build">
Build
</Entry>
<Entry key="FolderDialog_ApplicationLocation">
Application "%%NAME%%" could not be found on the system! Please locate the folder containing the main executable "%%EXECUTABLE%%".
</Entry>
<Entry key="LockScreen_AllowOption">
Temporarily allow the blacklisted applications. This applies only to the currently running instances and session!
</Entry>

View file

@ -49,6 +49,11 @@ namespace SafeExamBrowser.UserInterface.Contracts
/// </summary>
IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow);
/// <summary>
/// Creates a folder dialog with the given message.
/// </summary>
IFolderDialog CreateFolderDialog(string message);
/// <summary>
/// Creates a system control which allows to change the keyboard layout of the computer.
/// </summary>

View file

@ -76,13 +76,15 @@
<Compile Include="Shell\INotificationControl.cs" />
<Compile Include="Shell\ISystemControl.cs" />
<Compile Include="Shell\ITaskbar.cs" />
<Compile Include="Shell\ITaskView.cs" />
<Compile Include="Shell\ITaskViewActivator.cs" />
<Compile Include="Shell\ITaskview.cs" />
<Compile Include="Shell\ITaskviewActivator.cs" />
<Compile Include="Shell\ITerminationActivator.cs" />
<Compile Include="Shell\Location.cs" />
<Compile Include="Windows\Data\FolderDialogResult.cs" />
<Compile Include="Windows\Data\LockScreenOption.cs" />
<Compile Include="Windows\Data\LockScreenResult.cs" />
<Compile Include="Windows\Events\WindowClosingEventHandler.cs" />
<Compile Include="Windows\IFolderDialog.cs" />
<Compile Include="Windows\ILockScreen.cs" />
<Compile Include="Windows\IPasswordDialog.cs" />
<Compile Include="Windows\Data\PasswordDialogResult.cs" />

View file

@ -13,7 +13,7 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
/// <summary>
/// The task view provides an overview of all currently running application instances.
/// </summary>
public interface ITaskView
public interface ITaskview
{
/// <summary>
/// Adds the given application to the task view.
@ -23,6 +23,6 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
/// <summary>
/// Registers the specified activator for the task view.
/// </summary>
void Register(ITaskViewActivator activator);
void Register(ITaskviewActivator activator);
}
}

View file

@ -11,9 +11,9 @@ using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
namespace SafeExamBrowser.UserInterface.Contracts.Shell
{
/// <summary>
/// A module which can be used to control the <see cref="ITaskView"/>.
/// A module which can be used to control the <see cref="ITaskview"/>.
/// </summary>
public interface ITaskViewActivator : IActivator
public interface ITaskviewActivator : IActivator
{
/// <summary>
/// Fired when the task view should be hidden.

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2019 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.UserInterface.Contracts.Windows.Data
{
/// <summary>
/// Defines the user interaction result of an <see cref="IFolderDialog"/>.
/// </summary>
public class FolderDialogResult
{
/// <summary>
/// The full path of the folder selected by the user, or <c>null</c> if the interaction was unsuccessful.
/// </summary>
public string FolderPath { get; set; }
/// <summary>
/// Indicates whether the user confirmed the dialog or not.
/// </summary>
public bool Success { get; set; }
}
}

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2019 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/.
*/
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
namespace SafeExamBrowser.UserInterface.Contracts.Windows
{
public interface IFolderDialog
{
/// <summary>
/// Shows the dialog to the user. If a parent window is specified, the dialog is rendered modally for the given parent.
/// </summary>
FolderDialogResult Show(IWindow parent = null);
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2019 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/.
*/
using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
namespace SafeExamBrowser.UserInterface.Desktop
{
internal class FolderDialog : IFolderDialog
{
private string message;
internal FolderDialog(string message)
{
this.message = message;
}
public FolderDialogResult Show(IWindow parent = null)
{
var result = new FolderDialogResult();
using (var dialog = new FolderBrowserDialog())
{
var dialogResult = DialogResult.None;
dialog.Description = message;
dialog.ShowNewFolderButton = false;
if (parent is Window w)
{
dialogResult = dialog.ShowDialog(new Win32Window(w));
}
else
{
dialogResult = dialog.ShowDialog();
}
if (dialogResult == DialogResult.OK)
{
result.FolderPath = dialog.SelectedPath;
result.Success = true;
}
}
return result;
}
private class Win32Window : System.Windows.Forms.IWin32Window
{
private Window w;
public Win32Window(Window w)
{
this.w = w;
}
public IntPtr Handle => w.Dispatcher.Invoke(() => new WindowInteropHelper(w).Handle);
}
}
}

View file

@ -145,6 +145,7 @@
<Compile Include="Controls\TaskviewWindowControl.xaml.cs">
<DependentUpon>TaskviewWindowControl.xaml</DependentUpon>
</Compile>
<Compile Include="FolderDialog.cs" />
<Compile Include="LockScreen.xaml.cs">
<DependentUpon>LockScreen.xaml</DependentUpon>
</Compile>

View file

@ -18,7 +18,7 @@ using SafeExamBrowser.UserInterface.Desktop.Controls;
namespace SafeExamBrowser.UserInterface.Desktop
{
public partial class Taskview : Window, ITaskView
public partial class Taskview : Window, ITaskview
{
private IList<IApplication> applications;
private LinkedListNode<TaskviewWindowControl> current;
@ -41,7 +41,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
applications.Add(application);
}
public void Register(ITaskViewActivator activator)
public void Register(ITaskviewActivator activator)
{
activator.Deactivated += Activator_Deactivated;
activator.NextActivated += Activator_Next;

View file

@ -75,6 +75,11 @@ namespace SafeExamBrowser.UserInterface.Desktop
return new BrowserWindow(control, settings, isMainWindow, text);
}
public IFolderDialog CreateFolderDialog(string message)
{
return new FolderDialog(message);
}
public ISystemControl CreateKeyboardLayoutControl(IKeyboard keyboard, Location location)
{
if (location == Location.ActionCenter)

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2019 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/.
*/
using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
namespace SafeExamBrowser.UserInterface.Mobile
{
internal class FolderDialog : IFolderDialog
{
private string message;
internal FolderDialog(string message)
{
this.message = message;
}
public FolderDialogResult Show(IWindow parent = null)
{
var result = new FolderDialogResult();
using (var dialog = new FolderBrowserDialog())
{
var dialogResult = DialogResult.None;
dialog.Description = message;
dialog.ShowNewFolderButton = false;
if (parent is Window w)
{
dialogResult = dialog.ShowDialog(new Win32Window(w));
}
else
{
dialogResult = dialog.ShowDialog();
}
if (dialogResult == DialogResult.OK)
{
result.FolderPath = dialog.SelectedPath;
result.Success = true;
}
}
return result;
}
private class Win32Window : System.Windows.Forms.IWin32Window
{
private Window w;
public Win32Window(Window w)
{
this.w = w;
}
public IntPtr Handle => w.Dispatcher.Invoke(() => new WindowInteropHelper(w).Handle);
}
}
}

View file

@ -146,6 +146,7 @@
<Compile Include="Controls\TaskviewWindowControl.xaml.cs">
<DependentUpon>TaskviewWindowControl.xaml</DependentUpon>
</Compile>
<Compile Include="FolderDialog.cs" />
<Compile Include="LockScreen.xaml.cs">
<DependentUpon>LockScreen.xaml</DependentUpon>
</Compile>

View file

@ -18,7 +18,7 @@ using SafeExamBrowser.UserInterface.Mobile.Controls;
namespace SafeExamBrowser.UserInterface.Mobile
{
public partial class Taskview : Window, ITaskView
public partial class Taskview : Window, ITaskview
{
private IList<IApplication> applications;
private LinkedListNode<TaskviewWindowControl> current;
@ -41,7 +41,7 @@ namespace SafeExamBrowser.UserInterface.Mobile
applications.Add(application);
}
public void Register(ITaskViewActivator activator)
public void Register(ITaskviewActivator activator)
{
activator.Deactivated += Activator_Deactivated;
activator.NextActivated += Activator_Next;

View file

@ -75,6 +75,11 @@ namespace SafeExamBrowser.UserInterface.Mobile
return new BrowserWindow(control, settings, isMainWindow, text);
}
public IFolderDialog CreateFolderDialog(string message)
{
return new FolderDialog(message);
}
public ISystemControl CreateKeyboardLayoutControl(IKeyboard keyboard, Location location)
{
if (location == Location.ActionCenter)

View file

@ -15,7 +15,7 @@ using SafeExamBrowser.WindowsApi.Contracts.Events;
namespace SafeExamBrowser.UserInterface.Shared.Activators
{
public class TaskViewKeyboardActivator : KeyboardActivator, ITaskViewActivator
public class TaskviewKeyboardActivator : KeyboardActivator, ITaskviewActivator
{
private bool Activated, LeftShift, Tab;
private ILogger logger;
@ -24,7 +24,7 @@ namespace SafeExamBrowser.UserInterface.Shared.Activators
public event ActivatorEventHandler NextActivated;
public event ActivatorEventHandler PreviousActivated;
public TaskViewKeyboardActivator(ILogger logger, INativeMethods nativeMethods) : base(nativeMethods)
public TaskviewKeyboardActivator(ILogger logger, INativeMethods nativeMethods) : base(nativeMethods)
{
this.logger = logger;
}

View file

@ -66,7 +66,7 @@
<Compile Include="Activators\ActionCenterKeyboardActivator.cs" />
<Compile Include="Activators\ActionCenterTouchActivator.cs" />
<Compile Include="Activators\KeyboardActivator.cs" />
<Compile Include="Activators\TaskViewKeyboardActivator.cs" />
<Compile Include="Activators\TaskviewKeyboardActivator.cs" />
<Compile Include="Activators\TerminationActivator.cs" />
<Compile Include="Activators\TouchActivator.cs" />
<Compile Include="Properties\AssemblyInfo.cs">