SEBWIN-141: Implemented draft of application controls for action center.

This commit is contained in:
dbuechel 2019-03-08 11:43:52 +01:00
parent 1991f9c2d1
commit 519fb9e57b
39 changed files with 474 additions and 115 deletions

View file

@ -29,7 +29,7 @@ namespace SafeExamBrowser.Browser
private int instanceIdCounter = default(int);
private AppConfig appConfig;
private IApplicationButton button;
private IList<IApplicationControl> controls;
private IList<IApplicationInstance> instances;
private IMessageBox messageBox;
private IModuleLogger logger;
@ -48,6 +48,7 @@ namespace SafeExamBrowser.Browser
IUserInterfaceFactory uiFactory)
{
this.appConfig = appConfig;
this.controls = new List<IApplicationControl>();
this.instances = new List<IApplicationInstance>();
this.logger = logger;
this.messageBox = messageBox;
@ -69,10 +70,10 @@ namespace SafeExamBrowser.Browser
}
}
public void RegisterApplicationButton(IApplicationButton button)
public void RegisterApplicationControl(IApplicationControl control)
{
this.button = button;
this.button.Clicked += Button_OnClick;
control.Clicked += ApplicationControl_Clicked;
controls.Add(control);
}
public void Start()
@ -108,7 +109,11 @@ namespace SafeExamBrowser.Browser
instance.PopupRequested += Instance_PopupRequested;
instance.Terminated += Instance_Terminated;
button.RegisterInstance(instance);
foreach (var control in controls)
{
control.RegisterInstance(instance);
}
instances.Add(instance);
instance.Window.Show();
@ -135,7 +140,7 @@ namespace SafeExamBrowser.Browser
return cefSettings;
}
private void Button_OnClick(InstanceIdentifier id = null)
private void ApplicationControl_Clicked(InstanceIdentifier id = null)
{
if (id == null)
{

View file

@ -11,14 +11,14 @@ using SafeExamBrowser.Contracts.UserInterface.Shell.Events;
namespace SafeExamBrowser.Client.UnitTests.Notifications
{
class NotificationButtonMock : INotificationButton
class NotificationButtonMock : INotificationControl
{
private NotificationButtonClickedEventHandler clicked;
private NotificationControlClickedEventHandler clicked;
public bool HasSubscribed;
public bool HasUnsubscribed;
public event NotificationButtonClickedEventHandler Clicked
public event NotificationControlClickedEventHandler Clicked
{
add
{

View file

@ -19,24 +19,26 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestClass]
public class BrowserOperationTests
{
private Mock<IApplicationController> controllerMock;
private Mock<IApplicationInfo> appInfoMock;
private Mock<ILogger> loggerMock;
private Mock<ITaskbar> taskbarMock;
private Mock<IUserInterfaceFactory> uiFactoryMock;
private Mock<IActionCenter> actionCenter;
private Mock<IApplicationController> controller;
private Mock<IApplicationInfo> appInfo;
private Mock<ILogger> logger;
private Mock<ITaskbar> taskbar;
private Mock<IUserInterfaceFactory> uiFactory;
private BrowserOperation sut;
[TestInitialize]
public void Initialize()
{
controllerMock = new Mock<IApplicationController>();
appInfoMock = new Mock<IApplicationInfo>();
loggerMock = new Mock<ILogger>();
taskbarMock = new Mock<ITaskbar>();
uiFactoryMock = new Mock<IUserInterfaceFactory>();
actionCenter = new Mock<IActionCenter>();
controller = new Mock<IApplicationController>();
appInfo = new Mock<IApplicationInfo>();
logger = new Mock<ILogger>();
taskbar = new Mock<ITaskbar>();
uiFactory = new Mock<IUserInterfaceFactory>();
sut = new BrowserOperation(controllerMock.Object, appInfoMock.Object, loggerMock.Object, taskbarMock.Object, uiFactoryMock.Object);
sut = new BrowserOperation(actionCenter.Object, controller.Object, appInfo.Object, logger.Object, taskbar.Object, uiFactory.Object);
}
[TestMethod]
@ -44,15 +46,15 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
{
var order = 0;
controllerMock.Setup(c => c.Initialize()).Callback(() => Assert.AreEqual(++order, 1));
controllerMock.Setup(c => c.RegisterApplicationButton(It.IsAny<IApplicationButton>())).Callback(() => Assert.AreEqual(++order, 2));
taskbarMock.Setup(t => t.AddApplication(It.IsAny<IApplicationButton>())).Callback(() => Assert.AreEqual(++order, 3));
controller.Setup(c => c.Initialize()).Callback(() => Assert.AreEqual(++order, 1));
controller.Setup(c => c.RegisterApplicationControl(It.IsAny<IApplicationControl>())).Callback(() => Assert.AreEqual(++order, 2));
taskbar.Setup(t => t.AddApplicationControl(It.IsAny<IApplicationControl>())).Callback(() => Assert.AreEqual(++order, 3));
sut.Perform();
controllerMock.Verify(c => c.Initialize(), Times.Once);
controllerMock.Verify(c => c.RegisterApplicationButton(It.IsAny<IApplicationButton>()), Times.Once);
taskbarMock.Verify(t => t.AddApplication(It.IsAny<IApplicationButton>()), Times.Once);
controller.Verify(c => c.Initialize(), Times.Once);
controller.Verify(c => c.RegisterApplicationControl(It.IsAny<IApplicationControl>()), Times.Once);
taskbar.Verify(t => t.AddApplicationControl(It.IsAny<IApplicationControl>()), Times.Once);
}
[TestMethod]
@ -60,7 +62,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
{
sut.Revert();
controllerMock.Verify(c => c.Terminate(), Times.Once);
controller.Verify(c => c.Terminate(), Times.Once);
}
}
}

View file

@ -11,7 +11,6 @@ using Moq;
using SafeExamBrowser.Client.Operations;
using SafeExamBrowser.Contracts.Client;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.SystemComponents;
using SafeExamBrowser.Contracts.UserInterface;
@ -58,7 +57,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
settings.AllowWirelessNetwork = true;
settings.EnableTaskbar = true;
systemInfoMock.SetupGet(s => s.HasBattery).Returns(true);
uiFactoryMock.Setup(u => u.CreateNotification(It.IsAny<INotificationInfo>())).Returns(new Mock<INotificationButton>().Object);
uiFactoryMock.Setup(u => u.CreateNotificationControl(It.IsAny<INotificationInfo>())).Returns(new Mock<INotificationControl>().Object);
sut = new TaskbarOperation(
loggerMock.Object,
@ -84,7 +83,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
powerSupplyMock.Verify(p => p.Initialize(It.IsAny<ISystemPowerSupplyControl>()), Times.Once);
wirelessNetworkMock.Verify(w => w.Initialize(It.IsAny<ISystemWirelessNetworkControl>()), Times.Once);
taskbarMock.Verify(t => t.AddSystemControl(It.IsAny<ISystemControl>()), Times.Exactly(3));
taskbarMock.Verify(t => t.AddNotification(It.IsAny<INotificationButton>()), Times.Exactly(2));
taskbarMock.Verify(t => t.AddNotificationControl(It.IsAny<INotificationControl>()), Times.Exactly(2));
}
[TestMethod]

View file

@ -230,7 +230,7 @@ namespace SafeExamBrowser.Client
var moduleLogger = new ModuleLogger(logger, "BrowserController");
var browserController = new BrowserApplicationController(configuration.AppConfig, configuration.Settings.Browser, messageBox, moduleLogger, text, uiFactory);
var browserInfo = new BrowserApplicationInfo();
var operation = new BrowserOperation(browserController, browserInfo, logger, taskbar, uiFactory);
var operation = new BrowserOperation(actionCenter, browserController, browserInfo, logger, taskbar, uiFactory);
this.browserController = browserController;

View file

@ -16,7 +16,7 @@ namespace SafeExamBrowser.Client.Notifications
{
internal class AboutNotificationController : INotificationController
{
private INotificationButton notification;
private INotificationControl notification;
private AppConfig appConfig;
private IUserInterfaceFactory uiFactory;
private IWindow window;
@ -27,7 +27,7 @@ namespace SafeExamBrowser.Client.Notifications
this.uiFactory = uiFactory;
}
public void RegisterNotification(INotificationButton notification)
public void RegisterNotification(INotificationControl notification)
{
this.notification = notification;

View file

@ -16,7 +16,7 @@ namespace SafeExamBrowser.Client.Notifications
{
internal class LogNotificationController : INotificationController
{
private INotificationButton notification;
private INotificationControl notification;
private ILogger logger;
private IUserInterfaceFactory uiFactory;
private IWindow window;
@ -27,7 +27,7 @@ namespace SafeExamBrowser.Client.Notifications
this.uiFactory = uiFactory;
}
public void RegisterNotification(INotificationButton notification)
public void RegisterNotification(INotificationControl notification)
{
this.notification = notification;

View file

@ -11,6 +11,7 @@ using SafeExamBrowser.Contracts.Client;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.SystemComponents;
using SafeExamBrowser.Contracts.UserInterface;
@ -69,10 +70,21 @@ namespace SafeExamBrowser.Client.Operations
public OperationResult Perform()
{
foreach (var activator in activators)
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeActionCenter);
if (settings.EnableActionCenter)
{
actionCenter.Register(activator);
activator.Start();
logger.Info("Initializing action center...");
foreach (var activator in activators)
{
actionCenter.Register(activator);
activator.Start();
}
}
else
{
logger.Info("Action center is disabled, skipping initialization.");
}
return OperationResult.Success;
@ -80,9 +92,20 @@ namespace SafeExamBrowser.Client.Operations
public OperationResult Revert()
{
foreach (var activator in activators)
StatusChanged?.Invoke(TextKey.OperationStatus_TerminateActionCenter);
if (settings.EnableActionCenter)
{
activator.Stop();
logger.Info("Terminating action center...");
foreach (var activator in activators)
{
activator.Stop();
}
}
else
{
logger.Info("Action center was disabled, skipping termination.");
}
return OperationResult.Success;

View file

@ -18,6 +18,7 @@ namespace SafeExamBrowser.Client.Operations
{
internal class BrowserOperation : IOperation
{
private IActionCenter actionCenter;
private IApplicationController browserController;
private IApplicationInfo browserInfo;
private ILogger logger;
@ -28,12 +29,14 @@ namespace SafeExamBrowser.Client.Operations
public event StatusChangedEventHandler StatusChanged;
public BrowserOperation(
IActionCenter actionCenter,
IApplicationController browserController,
IApplicationInfo browserInfo,
ILogger logger,
ITaskbar taskbar,
IUserInterfaceFactory uiFactory)
{
this.actionCenter = actionCenter;
this.browserController = browserController;
this.browserInfo = browserInfo;
this.logger = logger;
@ -46,12 +49,15 @@ namespace SafeExamBrowser.Client.Operations
logger.Info("Initializing browser...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeBrowser);
var browserButton = uiFactory.CreateApplicationButton(browserInfo);
var actionCenterControl = uiFactory.CreateApplicationControl(browserInfo, Location.ActionCenter);
var taskbarControl = uiFactory.CreateApplicationControl(browserInfo, Location.Taskbar);
browserController.Initialize();
browserController.RegisterApplicationButton(browserButton);
browserController.RegisterApplicationControl(actionCenterControl);
browserController.RegisterApplicationControl(taskbarControl);
taskbar.AddApplication(browserButton);
actionCenter.AddApplicationControl(actionCenterControl);
taskbar.AddApplicationControl(taskbarControl);
return OperationResult.Success;
}

View file

@ -142,10 +142,10 @@ namespace SafeExamBrowser.Client.Operations
private void AddAboutNotification()
{
var aboutNotification = uiFactory.CreateNotification(aboutInfo);
var aboutNotification = uiFactory.CreateNotificationControl(aboutInfo);
aboutController.RegisterNotification(aboutNotification);
taskbar.AddNotification(aboutNotification);
taskbar.AddNotificationControl(aboutNotification);
}
private void AddKeyboardLayoutControl()
@ -158,10 +158,10 @@ namespace SafeExamBrowser.Client.Operations
private void AddLogNotification()
{
var logNotification = uiFactory.CreateNotification(logInfo);
var logNotification = uiFactory.CreateNotificationControl(logInfo);
logController.RegisterNotification(logNotification);
taskbar.AddNotification(logNotification);
taskbar.AddNotificationControl(logNotification);
}
private void AddPowerSupplyControl()

View file

@ -21,9 +21,9 @@ namespace SafeExamBrowser.Contracts.Applications
void Initialize();
/// <summary>
/// Registers the taskbar button for this application.
/// Registers an application control for this application.
/// </summary>
void RegisterApplicationButton(IApplicationButton button);
void RegisterApplicationControl(IApplicationControl control);
/// <summary>
/// Starts the execution of the application.

View file

@ -18,7 +18,7 @@ namespace SafeExamBrowser.Contracts.Client
/// <summary>
/// Registers the taskbar notification.
/// </summary>
void RegisterNotification(INotificationButton notification);
void RegisterNotification(INotificationControl notification);
/// <summary>
/// Instructs the controller to shut down and release all used resources.

View file

@ -56,6 +56,7 @@ namespace SafeExamBrowser.Contracts.I18n
OperationStatus_CloseRuntimeConnection,
OperationStatus_EmptyClipboard,
OperationStatus_FinalizeServiceSession,
OperationStatus_InitializeActionCenter,
OperationStatus_InitializeBrowser,
OperationStatus_InitializeClient,
OperationStatus_InitializeConfiguration,
@ -80,6 +81,7 @@ namespace SafeExamBrowser.Contracts.I18n
OperationStatus_StopMouseInterception,
OperationStatus_StopProcessMonitoring,
OperationStatus_StopWindowMonitoring,
OperationStatus_TerminateActionCenter,
OperationStatus_TerminateBrowser,
OperationStatus_TerminateTaskbar,
OperationStatus_WaitExplorerStartup,

View file

@ -188,11 +188,12 @@
<Compile Include="UserInterface\MessageBox\IMessageBox.cs" />
<Compile Include="UserInterface\IProgressIndicator.cs" />
<Compile Include="UserInterface\Shell\Events\ActivatorEventHandler.cs" />
<Compile Include="UserInterface\Shell\Events\ApplicationButtonClickedEventHandler.cs" />
<Compile Include="UserInterface\Shell\Events\ApplicationControlClickedEventHandler.cs" />
<Compile Include="UserInterface\Shell\Events\KeyboardLayoutSelectedEventHandler.cs" />
<Compile Include="UserInterface\Shell\Events\NotificationButtonClickedEventHandler.cs" />
<Compile Include="UserInterface\Shell\Events\NotificationControlClickedEventHandler.cs" />
<Compile Include="UserInterface\Shell\IActionCenter.cs" />
<Compile Include="UserInterface\Shell\IActionCenterActivator.cs" />
<Compile Include="UserInterface\Shell\Location.cs" />
<Compile Include="UserInterface\Windows\Events\WindowClosingEventHandler.cs" />
<Compile Include="UserInterface\Shell\Events\WirelessNetworkSelectedEventHandler.cs" />
<Compile Include="UserInterface\Shell\Events\QuitButtonClickedEventHandler.cs" />
@ -200,7 +201,7 @@
<Compile Include="UserInterface\Windows\IPasswordDialogResult.cs" />
<Compile Include="UserInterface\Windows\IRuntimeWindow.cs" />
<Compile Include="UserInterface\MessageBox\MessageBoxResult.cs" />
<Compile Include="UserInterface\Shell\INotificationButton.cs" />
<Compile Include="UserInterface\Shell\INotificationControl.cs" />
<Compile Include="UserInterface\Windows\ISplashScreen.cs" />
<Compile Include="UserInterface\Shell\ISystemKeyboardLayoutControl.cs" />
<Compile Include="UserInterface\Shell\ISystemPowerSupplyControl.cs" />
@ -209,7 +210,7 @@
<Compile Include="UserInterface\Shell\ITaskbar.cs" />
<Compile Include="I18n\ITextResource.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UserInterface\Shell\IApplicationButton.cs" />
<Compile Include="UserInterface\Shell\IApplicationControl.cs" />
<Compile Include="UserInterface\IUserInterfaceFactory.cs" />
<Compile Include="UserInterface\Windows\IWindow.cs" />
<Compile Include="UserInterface\MessageBox\MessageBoxAction.cs" />

View file

@ -31,9 +31,9 @@ namespace SafeExamBrowser.Contracts.UserInterface
IWindow CreateAboutWindow(AppConfig appConfig);
/// <summary>
/// Creates a taskbar button, initialized with the given application information.
/// Creates an application control for the specified location, initialized with the given application information.
/// </summary>
IApplicationButton CreateApplicationButton(IApplicationInfo info);
IApplicationControl CreateApplicationControl(IApplicationInfo info, Location location);
/// <summary>
/// Creates a new browser window loaded with the given browser control and settings.
@ -51,9 +51,9 @@ namespace SafeExamBrowser.Contracts.UserInterface
IWindow CreateLogWindow(ILogger logger);
/// <summary>
/// Creates a taskbar notification, initialized with the given notification information.
/// Creates a notification control, initialized with the given notification information.
/// </summary>
INotificationButton CreateNotification(INotificationInfo info);
INotificationControl CreateNotificationControl(INotificationInfo info);
/// <summary>
/// Creates a password dialog with the given message and title.

View file

@ -11,8 +11,8 @@ using SafeExamBrowser.Contracts.Applications;
namespace SafeExamBrowser.Contracts.UserInterface.Shell.Events
{
/// <summary>
/// Indicates that an <see cref="IApplicationButton"/> has been clicked, optionally specifying the ID of the selected instance (if
/// Indicates that an <see cref="IApplicationControl"/> has been clicked, optionally specifying the identifier of the selected instance (if
/// multiple instances of the same application are running).
/// </summary>
public delegate void ApplicationButtonClickedEventHandler(InstanceIdentifier id = null);
public delegate void ApplicationControlClickedEventHandler(InstanceIdentifier id = null);
}

View file

@ -9,7 +9,7 @@
namespace SafeExamBrowser.Contracts.UserInterface.Shell.Events
{
/// <summary>
/// Indicates that the user clicked on a <see cref="INotificationButton"/> in the <see cref="ITaskbar"/>.
/// Indicates that the user clicked on a <see cref="INotificationControl"/> in the shell.
/// </summary>
public delegate void NotificationButtonClickedEventHandler();
public delegate void NotificationControlClickedEventHandler();
}

View file

@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.UserInterface.Shell.Events;
namespace SafeExamBrowser.Contracts.UserInterface.Shell
{
/// <summary>
@ -13,6 +15,26 @@ namespace SafeExamBrowser.Contracts.UserInterface.Shell
/// </summary>
public interface IActionCenter
{
/// <summary>
/// Event fired when the user clicked the quit button.
/// </summary>
event QuitButtonClickedEventHandler QuitButtonClicked;
/// <summary>
/// Adds the given application control to the action center.
/// </summary>
void AddApplicationControl(IApplicationControl control);
/// <summary>
/// Adds the given notification control to the action center.
/// </summary>
void AddNotificationControl(INotificationControl control);
/// <summary>
/// Adds the given system control to the action center.
/// </summary>
void AddSystemControl(ISystemControl control);
/// <summary>
/// Closes the action center.
/// </summary>

View file

@ -12,18 +12,18 @@ using SafeExamBrowser.Contracts.UserInterface.Shell.Events;
namespace SafeExamBrowser.Contracts.UserInterface.Shell
{
/// <summary>
/// The button of a (third-party) application which can be loaded into the <see cref="ITaskbar"/>.
/// The control for a (third-party) application which can be loaded into the shell.
/// </summary>
public interface IApplicationButton
public interface IApplicationControl
{
/// <summary>
/// Event fired when the user clicked on the application button. If multiple instances of an application are active,
/// the handler is only executed when the user selects one of the instances.
/// Event fired when the user clicked on the application control. If multiple instances of an application are active,
/// the handler should only executed when the user selects one of the instances.
/// </summary>
event ApplicationButtonClickedEventHandler Clicked;
event ApplicationControlClickedEventHandler Clicked;
/// <summary>
/// Registers a new instance of an application, to be started / displayed if the user clicked the taskbar button.
/// Registers a new instance of an application to be accessed via the application control.
/// </summary>
void RegisterInstance(IApplicationInstance instance);
}

View file

@ -11,13 +11,13 @@ using SafeExamBrowser.Contracts.UserInterface.Shell.Events;
namespace SafeExamBrowser.Contracts.UserInterface.Shell
{
/// <summary>
/// The button of a notification which can be loaded into the <see cref="ITaskbar"/>.
/// The control for a notification which can be loaded into the shell.
/// </summary>
public interface INotificationButton
public interface INotificationControl
{
/// <summary>
/// Event fired when the user clicked on the notification icon.
/// Event fired when the user clicked on the notification control.
/// </summary>
event NotificationButtonClickedEventHandler Clicked;
event NotificationControlClickedEventHandler Clicked;
}
}

View file

@ -26,14 +26,14 @@ namespace SafeExamBrowser.Contracts.UserInterface.Shell
event QuitButtonClickedEventHandler QuitButtonClicked;
/// <summary>
/// Adds the given application button to the taskbar.
/// Adds the given application control to the taskbar.
/// </summary>
void AddApplication(IApplicationButton button);
void AddApplicationControl(IApplicationControl control);
/// <summary>
/// Adds the given notification button to the taskbar.
/// Adds the given notification control to the taskbar.
/// </summary>
void AddNotification(INotificationButton button);
void AddNotificationControl(INotificationControl control);
/// <summary>
/// Adds the given system control to the taskbar.

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.Contracts.UserInterface.Shell
{
/// <summary>
/// Defines all possible locations of a user control in the shell.
/// </summary>
public enum Location
{
/// <summary>
/// A user control styled for and placed in the action center.
/// </summary>
ActionCenter,
/// <summary>
/// A user control styled for and placed in the taskbar.
/// </summary>
Taskbar
}
}

View file

@ -126,6 +126,9 @@
<Entry key="OperationStatus_FinalizeServiceSession">
Finalizing service session
</Entry>
<Entry key="OperationStatus_InitializeActionCenter">
Initializing action center
</Entry>
<Entry key="OperationStatus_InitializeBrowser">
Initializing browser
</Entry>
@ -198,6 +201,9 @@
<Entry key="OperationStatus_StopWindowMonitoring">
Stopping window monitoring
</Entry>
<Entry key="OperationStatus_TerminateActionCenter">
Terminating action center
</Entry>
<Entry key="OperationStatus_TerminateBrowser">
Terminating browser
</Entry>

View file

@ -4,9 +4,30 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop"
mc:Ignorable="d" Title="ActionCenter" Height="1000" Width="400" Background="#EEF0F0F0" AllowsTransparency="True" WindowStyle="None"
Topmost="True" ResizeMode="NoResize">
mc:Ignorable="d" Title="ActionCenter" Height="1000" Width="400" Background="#EEF0F0F0" AllowsTransparency="True" WindowStyle="None" Topmost="True" ResizeMode="NoResize">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="./Templates/ScrollViewers.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
<StackPanel x:Name="ApplicationPanel" Orientation="Vertical" />
</ScrollViewer>
<UniformGrid x:Name="ControlPanel" Grid.Row="1" Columns="4" Margin="5">
<Label Height="60">Control</Label>
<Label Height="60">Control</Label>
<Label Height="60">Control</Label>
<Label Height="60">Control</Label>
<Label Height="60">Control</Label>
<Label Height="60">Control</Label>
<Label Height="60">Control</Label>
</UniformGrid>
</Grid>
</Window>

View file

@ -10,16 +10,37 @@ using System;
using System.Windows;
using System.Windows.Media.Animation;
using SafeExamBrowser.Contracts.UserInterface.Shell;
using SafeExamBrowser.Contracts.UserInterface.Shell.Events;
namespace SafeExamBrowser.UserInterface.Desktop
{
public partial class ActionCenter : Window, IActionCenter
{
public event QuitButtonClickedEventHandler QuitButtonClicked;
public ActionCenter()
{
InitializeComponent();
}
public void AddApplicationControl(IApplicationControl control)
{
if (control is UIElement uiElement)
{
ApplicationPanel.Children.Add(uiElement);
}
}
public void AddNotificationControl(INotificationControl control)
{
}
public void AddSystemControl(ISystemControl control)
{
}
public new void Close()
{
Dispatcher.Invoke(base.Close);

View file

@ -0,0 +1,24 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenterApplicationButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Templates/Buttons.xaml" />
<ResourceDictionary Source="../Templates/Colors.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Button x:Name="Button" Background="Transparent" Click="Button_Click" Height="40" Padding="10" Template="{StaticResource ActionCenterButton}">
<StackPanel Orientation="Horizontal">
<ContentControl x:Name="Icon" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,10,0" Width="20" />
<TextBlock x:Name="Text" HorizontalAlignment="Left" VerticalAlignment="Center" Padding="5" MaxWidth="350" TextTrimming="CharacterEllipsis" />
</StackPanel>
</Button>
</Grid>
</UserControl>

View file

@ -0,0 +1,66 @@
/*
* 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.Windows;
using System.Windows.Controls;
using SafeExamBrowser.Contracts.Applications;
using SafeExamBrowser.Contracts.Core;
using SafeExamBrowser.Contracts.UserInterface.Shell.Events;
using SafeExamBrowser.UserInterface.Desktop.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
public partial class ActionCenterApplicationButton : UserControl
{
private IApplicationInfo info;
private IApplicationInstance instance;
internal event ApplicationControlClickedEventHandler Clicked;
public ActionCenterApplicationButton(IApplicationInfo info, IApplicationInstance instance = null)
{
this.info = info;
this.instance = instance;
InitializeComponent();
InitializeApplicationInstanceButton();
}
private void InitializeApplicationInstanceButton()
{
Icon.Content = IconResourceLoader.Load(info.IconResource);
Text.Text = instance?.Name ?? info.Name;
Button.ToolTip = instance?.Name ?? info.Tooltip;
if (instance != null)
{
instance.IconChanged += Instance_IconChanged;
instance.NameChanged += Instance_NameChanged;
}
}
private void Instance_IconChanged(IIconResource icon)
{
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
}
private void Instance_NameChanged(string name)
{
Dispatcher.Invoke(() =>
{
Text.Text = name;
Button.ToolTip = name;
});
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Clicked?.Invoke(instance?.Id);
}
}
}

View file

@ -0,0 +1,29 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenterApplicationControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
mc:Ignorable="d" d:DesignHeight="250" d:DesignWidth="500">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Templates/Buttons.xaml" />
<ResourceDictionary Source="../Templates/Colors.xaml" />
<ResourceDictionary Source="../Templates/ScrollViewers.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="ApplicationName" Background="#AAD3D3D3" FontWeight="Bold" Padding="5" TextAlignment="Center" />
<ContentControl Grid.Row="1" x:Name="ApplicationButton" />
<StackPanel Grid.Row="2" x:Name="InstancePanel" Orientation="Vertical" />
<Border Grid.Row="3" BorderBrush="LightGray" BorderThickness="0,0,0,1" Margin="75,4" />
</Grid>
</UserControl>

View file

@ -0,0 +1,70 @@
/*
* 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.Windows;
using System.Windows.Controls;
using SafeExamBrowser.Contracts.Applications;
using SafeExamBrowser.Contracts.UserInterface.Shell;
using SafeExamBrowser.Contracts.UserInterface.Shell.Events;
using SafeExamBrowser.UserInterface.Desktop.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
public partial class ActionCenterApplicationControl : UserControl, IApplicationControl
{
private IApplicationInfo info;
public event ApplicationControlClickedEventHandler Clicked;
public ActionCenterApplicationControl(IApplicationInfo info)
{
this.info = info;
InitializeComponent();
InitializeApplicationControl(info);
}
public void RegisterInstance(IApplicationInstance instance)
{
Dispatcher.Invoke(() =>
{
var button = new ActionCenterApplicationButton(info, instance);
button.Clicked += (id) => Clicked?.Invoke(id);
instance.Terminated += (id) => Instance_OnTerminated(id, button);
InstancePanel.Children.Add(button);
ApplicationName.Visibility = Visibility.Visible;
ApplicationButton.Visibility = Visibility.Collapsed;
});
}
private void InitializeApplicationControl(IApplicationInfo info)
{
var button = new ActionCenterApplicationButton(info);
button.Button.Click += (o, args) => Clicked?.Invoke();
ApplicationName.Text = info.Name;
ApplicationButton.Content = button;
}
private void Instance_OnTerminated(InstanceIdentifier id, ActionCenterApplicationButton button)
{
Dispatcher.InvokeAsync(() =>
{
InstancePanel.Children.Remove(button);
if (InstancePanel.Children.Count == 0)
{
ApplicationName.Visibility = Visibility.Collapsed;
ApplicationButton.Visibility = Visibility.Visible;
}
});
}
}
}

View file

@ -15,9 +15,9 @@ using SafeExamBrowser.UserInterface.Desktop.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
public partial class NotificationButton : UserControl, INotificationButton
public partial class NotificationButton : UserControl, INotificationControl
{
public event NotificationButtonClickedEventHandler Clicked;
public event NotificationControlClickedEventHandler Clicked;
public NotificationButton(INotificationInfo info)
{

View file

@ -1,4 +1,4 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ApplicationButton"
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.TaskbarApplicationControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View file

@ -20,26 +20,26 @@ using SafeExamBrowser.UserInterface.Desktop.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
public partial class ApplicationButton : UserControl, IApplicationButton
public partial class TaskbarApplicationControl : UserControl, IApplicationControl
{
private IApplicationInfo info;
private IList<IApplicationInstance> instances = new List<IApplicationInstance>();
public event ApplicationButtonClickedEventHandler Clicked;
public event ApplicationControlClickedEventHandler Clicked;
public ApplicationButton(IApplicationInfo info)
public TaskbarApplicationControl(IApplicationInfo info)
{
this.info = info;
InitializeComponent();
InitializeApplicationButton();
InitializeApplicationControl();
}
public void RegisterInstance(IApplicationInstance instance)
{
Dispatcher.Invoke(() =>
{
var instanceButton = new ApplicationInstanceButton(instance, info);
var instanceButton = new TaskbarApplicationInstanceButton(instance, info);
instanceButton.Clicked += (id) => Clicked?.Invoke(id);
instance.Terminated += (id) => Instance_OnTerminated(id, instanceButton);
@ -49,7 +49,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
});
}
private void InitializeApplicationButton()
private void InitializeApplicationControl()
{
var originalBrush = Button.Background;
@ -85,7 +85,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
}
}
private void Instance_OnTerminated(InstanceIdentifier id, ApplicationInstanceButton instanceButton)
private void Instance_OnTerminated(InstanceIdentifier id, TaskbarApplicationInstanceButton instanceButton)
{
Dispatcher.InvokeAsync(() =>
{

View file

@ -1,4 +1,4 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ApplicationInstanceButton"
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.TaskbarApplicationInstanceButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View file

@ -15,14 +15,14 @@ using SafeExamBrowser.UserInterface.Desktop.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
public partial class ApplicationInstanceButton : UserControl
public partial class TaskbarApplicationInstanceButton : UserControl
{
private IApplicationInfo info;
private IApplicationInstance instance;
internal event ApplicationButtonClickedEventHandler Clicked;
internal event ApplicationControlClickedEventHandler Clicked;
public ApplicationInstanceButton(IApplicationInstance instance, IApplicationInfo info)
public TaskbarApplicationInstanceButton(IApplicationInstance instance, IApplicationInfo info)
{
this.info = info;
this.instance = instance;

View file

@ -74,11 +74,17 @@
<Compile Include="BrowserWindow.xaml.cs">
<DependentUpon>BrowserWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\ApplicationButton.xaml.cs">
<DependentUpon>ApplicationButton.xaml</DependentUpon>
<Compile Include="Controls\ActionCenterApplicationControl.xaml.cs">
<DependentUpon>ActionCenterApplicationControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\ApplicationInstanceButton.xaml.cs">
<DependentUpon>ApplicationInstanceButton.xaml</DependentUpon>
<Compile Include="Controls\ActionCenterApplicationButton.xaml.cs">
<DependentUpon>ActionCenterApplicationButton.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarApplicationControl.xaml.cs">
<DependentUpon>TaskbarApplicationControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarApplicationInstanceButton.xaml.cs">
<DependentUpon>TaskbarApplicationInstanceButton.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\DateTimeControl.xaml.cs">
<DependentUpon>DateTimeControl.xaml</DependentUpon>
@ -137,11 +143,19 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\ApplicationButton.xaml">
<Page Include="Controls\ActionCenterApplicationControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\ApplicationInstanceButton.xaml">
<Page Include="Controls\ActionCenterApplicationButton.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\TaskbarApplicationControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\TaskbarApplicationInstanceButton.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>

View file

@ -21,8 +21,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" x:Name="ApplicationScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"
Template="{StaticResource SmallBarScrollViewer}">
<ScrollViewer Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" Template="{StaticResource SmallBarScrollViewer}">
<StackPanel x:Name="ApplicationStackPanel" Orientation="Horizontal" />
</ScrollViewer>
<StackPanel Grid.Column="1" x:Name="NotificationStackPanel" Orientation="Horizontal" VerticalAlignment="Stretch" />

View file

@ -38,17 +38,17 @@ namespace SafeExamBrowser.UserInterface.Desktop
QuitButton.Clicked += QuitButton_Clicked;
}
public void AddApplication(IApplicationButton button)
public void AddApplicationControl(IApplicationControl control)
{
if (button is UIElement uiElement)
if (control is UIElement uiElement)
{
ApplicationStackPanel.Children.Add(uiElement);
}
}
public void AddNotification(INotificationButton button)
public void AddNotificationControl(INotificationControl control)
{
if (button is UIElement uiElement)
if (control is UIElement uiElement)
{
NotificationStackPanel.Children.Add(uiElement);
}

View file

@ -3,27 +3,25 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Templates/Colors.xaml" />
</ResourceDictionary.MergedDictionaries>
<ControlTemplate x:Key="TaskbarButton" TargetType="Button">
<Border x:Name="ButtonContent" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding Background}"
BorderThickness="1" Cursor="Hand" Padding="{TemplateBinding Padding}">
<ControlTemplate x:Key="ActionCenterButton" TargetType="Button">
<Border x:Name="ButtonContent" Background="Transparent" BorderBrush="Transparent" BorderThickness="1" Cursor="Hand" Padding="{TemplateBinding Padding}">
<ContentPresenter ContentSource="Content" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
RenderOptions.BitmapScalingMode="HighQuality" VerticalAlignment="{TemplateBinding VerticalAlignment}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonContent" Property="Background" Value="LightBlue" />
<Setter TargetName="ButtonContent" Property="Background" Value="#AAADD8E6" />
<Setter TargetName="ButtonContent" Property="BorderBrush" Value="DodgerBlue" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="ButtonContent" Property="BorderBrush" Value="SkyBlue" />
<Setter TargetName="ButtonContent" Property="BorderBrush" Value="#AA87CEEB" />
<Setter TargetName="ButtonContent" Property="BorderThickness" Value="2" />
<Setter Property="Cursor" Value="Hand" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ControlTemplate x:Key="BrowserButton" TargetType="Button">
<Border x:Name="ButtonContent" Background="Transparent" BorderBrush="Transparent" BorderThickness="1" Cursor="Hand"
Padding="{TemplateBinding Padding}">
<Border x:Name="ButtonContent" Background="Transparent" BorderBrush="Transparent" BorderThickness="1" Cursor="Hand" Padding="{TemplateBinding Padding}">
<ContentPresenter ContentSource="Content" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
RenderOptions.BitmapScalingMode="HighQuality" VerticalAlignment="{TemplateBinding VerticalAlignment}" />
</Border>
@ -42,4 +40,22 @@
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ControlTemplate x:Key="TaskbarButton" TargetType="Button">
<Border x:Name="ButtonContent" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding Background}"
BorderThickness="1" Cursor="Hand" Padding="{TemplateBinding Padding}">
<ContentPresenter ContentSource="Content" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
RenderOptions.BitmapScalingMode="HighQuality" VerticalAlignment="{TemplateBinding VerticalAlignment}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonContent" Property="Background" Value="LightBlue" />
<Setter TargetName="ButtonContent" Property="BorderBrush" Value="DodgerBlue" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="ButtonContent" Property="BorderBrush" Value="SkyBlue" />
<Setter TargetName="ButtonContent" Property="BorderThickness" Value="2" />
<Setter Property="Cursor" Value="Hand" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ResourceDictionary>

View file

@ -40,9 +40,16 @@ namespace SafeExamBrowser.UserInterface.Desktop
return new AboutWindow(appConfig, text);
}
public IApplicationButton CreateApplicationButton(IApplicationInfo info)
public IApplicationControl CreateApplicationControl(IApplicationInfo info, Location location)
{
return new ApplicationButton(info);
if (location == Location.ActionCenter)
{
return new ActionCenterApplicationControl(info);
}
else
{
return new TaskbarApplicationControl(info);
}
}
public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow)
@ -79,7 +86,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
return logWindow;
}
public INotificationButton CreateNotification(INotificationInfo info)
public INotificationControl CreateNotificationControl(INotificationInfo info)
{
return new NotificationButton(info);
}