SEBWIN-516: Implemented raise hand feature.

This commit is contained in:
Damian Büchel 2021-09-17 10:47:02 +02:00
parent bda36647f3
commit 8a3039ec16
36 changed files with 992 additions and 4 deletions

View file

@ -114,7 +114,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
public void MustFilterContentRequests()
{
var request = new Mock<IRequest>();
var url = "www.test.org";
var url = "http://www.test.org";
filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);

View file

@ -12,6 +12,7 @@ using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Settings;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell;
@ -56,6 +57,12 @@ namespace SafeExamBrowser.Client.Operations
controller.Initialize(Context.Settings.Proctoring);
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.ActionCenter));
if (Context.Settings.SessionMode == SessionMode.Server && Context.Settings.Proctoring.ShowRaiseHandNotification)
{
actionCenter.AddNotificationControl(uiFactory.CreateRaiseHandControl(controller, Location.ActionCenter, Context.Settings.Proctoring));
taskbar.AddNotificationControl(uiFactory.CreateRaiseHandControl(controller, Location.Taskbar, Context.Settings.Proctoring));
}
if (Context.Settings.Proctoring.ShowTaskbarNotification)
{
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.Taskbar));

View file

@ -36,7 +36,9 @@ namespace SafeExamBrowser.Configuration.UnitTests.ConfigurationData
raw.Add(Keys.Browser.ShowReloadButton, true);
settings.Browser.AdditionalWindow.AllowReloading = false;
settings.Browser.AdditionalWindow.ShowReloadButton = false;
settings.Browser.MainWindow.AllowReloading = false;
settings.Browser.MainWindow.ShowReloadButton = false;
sut.Process(raw, settings);
@ -44,7 +46,9 @@ namespace SafeExamBrowser.Configuration.UnitTests.ConfigurationData
Assert.IsFalse(settings.Browser.MainWindow.ShowToolbar);
settings.Browser.AdditionalWindow.AllowReloading = true;
settings.Browser.AdditionalWindow.ShowReloadButton = true;
settings.Browser.MainWindow.AllowReloading = true;
settings.Browser.MainWindow.ShowReloadButton = true;
sut.Process(raw, settings);

View file

@ -219,6 +219,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Mouse.AllowRightButton = true;
settings.Proctoring.Enabled = false;
settings.Proctoring.ForceRaiseHandMessage = false;
settings.Proctoring.JitsiMeet.AllowChat = false;
settings.Proctoring.JitsiMeet.AllowClosedCaptions = false;
settings.Proctoring.JitsiMeet.AllowRaiseHand = false;
@ -233,6 +234,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Proctoring.JitsiMeet.SendVideo = true;
settings.Proctoring.JitsiMeet.ShowMeetingName = false;
settings.Proctoring.JitsiMeet.VideoMuted = false;
settings.Proctoring.ShowRaiseHandNotification = true;
settings.Proctoring.ShowTaskbarNotification = true;
settings.Proctoring.WindowVisibility = WindowVisibility.Hidden;
settings.Proctoring.Zoom.AllowChat = false;

View file

@ -137,7 +137,11 @@ namespace SafeExamBrowser.I18n.Contracts
Notification_AboutTooltip,
Notification_LogTooltip,
Notification_ProctoringActiveTooltip,
Notification_ProctoringHandLowered,
Notification_ProctoringHandRaised,
Notification_ProctoringInactiveTooltip,
Notification_ProctoringLowerHand,
Notification_ProctoringRaiseHand,
OperationStatus_CloseRuntimeConnection,
OperationStatus_EmptyClipboard,
OperationStatus_FinalizeApplications,

View file

@ -369,9 +369,21 @@
<Entry key="Notification_ProctoringActiveTooltip">
Fernüberwachung ist aktiv
</Entry>
<Entry key="Notification_ProctoringHandLowered">
Hand ist gesenkt
</Entry>
<Entry key="Notification_ProctoringHandRaised">
Hand ist erhoben
</Entry>
<Entry key="Notification_ProctoringInactiveTooltip">
Fernüberwachung ist nicht aktiv
</Entry>
<Entry key="Notification_ProctoringLowerHand">
Hand senken
</Entry>
<Entry key="Notification_ProctoringRaiseHand">
Hand erheben
</Entry>
<Entry key="OperationStatus_CloseRuntimeConnection">
Schliesse Verbindung zur Runtime
</Entry>

View file

@ -369,9 +369,21 @@
<Entry key="Notification_ProctoringActiveTooltip">
Remote proctoring is active
</Entry>
<Entry key="Notification_ProctoringHandLowered">
Hand is lowered
</Entry>
<Entry key="Notification_ProctoringHandRaised">
Hand is raised
</Entry>
<Entry key="Notification_ProctoringInactiveTooltip">
Remote proctoring is inactive
</Entry>
<Entry key="Notification_ProctoringLowerHand">
Lower hand
</Entry>
<Entry key="Notification_ProctoringRaiseHand">
Raise hand
</Entry>
<Entry key="OperationStatus_CloseRuntimeConnection">
Closing runtime connection
</Entry>

View file

@ -369,9 +369,21 @@
<Entry key="Notification_ProctoringActiveTooltip">
La surveillance à distance est active
</Entry>
<Entry key="Notification_ProctoringHandLowered">
La main est baissée
</Entry>
<Entry key="Notification_ProctoringHandRaised">
La main est levée
</Entry>
<Entry key="Notification_ProctoringInactiveTooltip">
La surveillance à distance est inactive
</Entry>
<Entry key="Notification_ProctoringLowerHand">
Baisser la main
</Entry>
<Entry key="Notification_ProctoringRaiseHand">
Lever la main
</Entry>
<Entry key="OperationStatus_CloseRuntimeConnection">
Fermeture de la connexion
</Entry>

View file

@ -369,9 +369,21 @@
<Entry key="Notification_ProctoringActiveTooltip">
Il proctoring remoto è attivo
</Entry>
<Entry key="Notification_ProctoringHandLowered">
La mano è abbassata
</Entry>
<Entry key="Notification_ProctoringHandRaised">
La mano è alzata
</Entry>
<Entry key="Notification_ProctoringInactiveTooltip">
Il proctoring remoto è inattivo
</Entry>
<Entry key="Notification_ProctoringLowerHand">
Abbassa la mano
</Entry>
<Entry key="Notification_ProctoringRaiseHand">
Alzi la mano
</Entry>
<Entry key="OperationStatus_CloseRuntimeConnection">
Chiusura della connessione runtime
</Entry>

View file

@ -333,9 +333,21 @@
<Entry key="Notification_ProctoringActiveTooltip">
远程监理处于活动状态
</Entry>
<Entry key="Notification_ProctoringHandLowered">
手被放下
</Entry>
<Entry key="Notification_ProctoringHandRaised">
举手了
</Entry>
<Entry key="Notification_ProctoringInactiveTooltip">
远程监理处于不活动状态
</Entry>
<Entry key="Notification_ProctoringLowerHand">
把手放低
</Entry>
<Entry key="Notification_ProctoringRaiseHand">
举手
</Entry>
<Entry key="OperationStatus_CloseRuntimeConnection">
关闭运行时连接
</Entry>

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2021 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.Proctoring.Contracts.Events
{
/// <summary>
/// The default event handler for proctoring events.
/// </summary>
public delegate void ProctoringEventHandler();
}

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Proctoring.Contracts.Events;
using SafeExamBrowser.Settings.Proctoring;
namespace SafeExamBrowser.Proctoring.Contracts
@ -15,11 +16,36 @@ namespace SafeExamBrowser.Proctoring.Contracts
/// </summary>
public interface IProctoringController
{
/// <summary>
/// Indicates whether the hand is currently raised.
/// </summary>
bool IsHandRaised { get; }
/// <summary>
/// Fired when the hand has been lowered.
/// </summary>
event ProctoringEventHandler HandLowered;
/// <summary>
/// Fired when the hand has been raised.
/// </summary>
event ProctoringEventHandler HandRaised;
/// <summary>
/// Initializes the given settings and starts the proctoring if the settings are valid.
/// </summary>
void Initialize(ProctoringSettings settings);
/// <summary>
/// Lowers the hand.
/// </summary>
void LowerHand();
/// <summary>
/// Raises the hand, optionally with the given message.
/// </summary>
void RaiseHand(string message = default(string));
/// <summary>
/// Stops the proctoring functionality.
/// </summary>

View file

@ -54,6 +54,7 @@
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Events\ProctoringEventHandler.cs" />
<Compile Include="IProctoringController.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View file

@ -19,6 +19,7 @@ using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Proctoring.Contracts.Events;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Server.Contracts.Events;
using SafeExamBrowser.Settings.Proctoring;
@ -44,8 +45,11 @@ namespace SafeExamBrowser.Proctoring
private WindowVisibility windowVisibility;
public IconResource IconResource { get; set; }
public bool IsHandRaised { get; private set; }
public string Tooltip { get; set; }
public event ProctoringEventHandler HandLowered;
public event ProctoringEventHandler HandRaised;
public event NotificationChangedEventHandler NotificationChanged;
public ProctoringController(
@ -86,6 +90,7 @@ namespace SafeExamBrowser.Proctoring
this.settings = settings;
this.windowVisibility = settings.WindowVisibility;
server.HandConfirmed += Server_HandConfirmed;
server.ProctoringConfigurationReceived += Server_ProctoringConfigurationReceived;
server.ProctoringInstructionReceived += Server_ProctoringInstructionReceived;
@ -110,11 +115,51 @@ namespace SafeExamBrowser.Proctoring
}
}
public void LowerHand()
{
var response = server.LowerHand();
if (response.Success)
{
IsHandRaised = false;
HandLowered?.Invoke();
logger.Info("Hand lowered.");
}
else
{
logger.Error($"Failed to send lower hand notification to server! Message: {response.Message}.");
}
}
public void RaiseHand(string message = null)
{
var response = server.RaiseHand(message);
if (response.Success)
{
IsHandRaised = true;
HandRaised?.Invoke();
logger.Info("Hand raised.");
}
else
{
logger.Error($"Failed to send raise hand notification to server! Message: {response.Message}.");
}
}
public void Terminate()
{
StopProctoring();
}
private void Server_HandConfirmed()
{
logger.Info("Hand confirmation received.");
IsHandRaised = false;
HandLowered?.Invoke();
}
private void Server_ProctoringInstructionReceived(ProctoringInstructionEventArgs args)
{
logger.Info("Proctoring instruction received.");

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2021 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.Server.Contracts.Events
{
/// <summary>
/// The default event handler for server events.
/// </summary>
public delegate void ServerEventHandler();
}

View file

@ -20,17 +20,22 @@ namespace SafeExamBrowser.Server.Contracts
public interface IServerProxy
{
/// <summary>
/// Event fired when the server receives new proctoring configuration values.
/// Event fired when the proxy receives a confirmation for a raise hand notification.
/// </summary>
event ServerEventHandler HandConfirmed;
/// <summary>
/// Event fired when the proxy receives new proctoring configuration values.
/// </summary>
event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
/// <summary>
/// Event fired when the server receives a proctoring instruction.
/// Event fired when the proxy receives a proctoring instruction.
/// </summary>
event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
/// <summary>
/// Event fired when the server detects an instruction to terminate SEB.
/// Event fired when the proxy detects an instruction to terminate SEB.
/// </summary>
event TerminationRequestedEventHandler TerminationRequested;
@ -69,6 +74,11 @@ namespace SafeExamBrowser.Server.Contracts
/// </summary>
void Initialize(string api, string connectionToken, string examId, string oauth2Token, ServerSettings settings);
/// <summary>
/// Sends a lower hand notification to the server.
/// </summary>
ServerResponse LowerHand();
/// <summary>
/// Sends the given user session identifier of a LMS and thus establishes a connection with the server.
/// </summary>
@ -83,5 +93,10 @@ namespace SafeExamBrowser.Server.Contracts
/// Stops sending ping and log data to the server.
/// </summary>
void StopConnectivity();
/// <summary>
/// Sends a raise hand notification to the server.
/// </summary>
ServerResponse RaiseHand(string message = default(string));
}
}

View file

@ -59,6 +59,7 @@
<Compile Include="Events\ProctoringConfigurationReceivedEventHandler.cs" />
<Compile Include="Events\ProctoringInstructionEventArgs.cs" />
<Compile Include="Events\ProctoringInstructionReceivedEventHandler.cs" />
<Compile Include="Events\ServerEventHandler.cs" />
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
<Compile Include="IServerProxy.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -13,9 +13,11 @@ namespace SafeExamBrowser.Server.Data
internal class Attributes
{
internal bool AllowChat { get; set; }
internal int Id { get; set; }
internal ProctoringInstructionEventArgs Instruction { get; set; }
internal bool ReceiveAudio { get; set; }
internal bool ReceiveVideo { get; set; }
internal string Type { get; set; }
internal Attributes()
{

View file

@ -10,6 +10,7 @@ namespace SafeExamBrowser.Server.Data
{
internal sealed class Instructions
{
internal const string NOTIFICATION_CONFIRM = "NOTIFICATION_CONFIRM";
internal const string PROCTORING = "SEB_PROCTORING";
internal const string PROCTORING_RECONFIGURATION = "SEB_RECONFIGURE_SETTINGS";
internal const string QUIT = "SEB_QUIT";

View file

@ -179,6 +179,9 @@ namespace SafeExamBrowser.Server
switch (instruction)
{
case Instructions.NOTIFICATION_CONFIRM:
ParseNotificationConfirmation(attributes, attributesJson);
break;
case Instructions.PROCTORING:
ParseProctoringInstruction(attributes, attributesJson);
break;
@ -190,6 +193,19 @@ namespace SafeExamBrowser.Server
return attributes;
}
private void ParseNotificationConfirmation(Attributes attributes, JObject attributesJson)
{
if (attributesJson.ContainsKey("id"))
{
attributes.Id = attributesJson["id"].Value<int>();
}
if (attributesJson.ContainsKey("type"))
{
attributes.Type = attributesJson["type"].Value<string>();
}
}
private void ParseProctoringInstruction(Attributes attributes, JObject attributesJson)
{
var provider = attributesJson["service-type"].Value<string>();

View file

@ -40,6 +40,7 @@ namespace SafeExamBrowser.Server
private int currentPowerSupplyValue;
private int currentWlanValue;
private string examId;
private int handNotificationId;
private HttpClient httpClient;
private ConcurrentQueue<string> instructionConfirmations;
private ILogger logger;
@ -53,6 +54,7 @@ namespace SafeExamBrowser.Server
private ServerSettings settings;
private IWirelessAdapter wirelessAdapter;
public event ServerEventHandler HandConfirmed;
public event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
public event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
public event TerminationRequestedEventHandler TerminationRequested;
@ -234,11 +236,64 @@ namespace SafeExamBrowser.Server
Initialize(settings);
}
public ServerResponse LowerHand()
{
var authorization = ("Authorization", $"Bearer {oauth2Token}");
var contentType = "application/json;charset=UTF-8";
var token = ("SEBConnectionToken", connectionToken);
var json = new JObject
{
["type"] = "NOTIFICATION_CONFIRMED",
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
["numericValue"] = handNotificationId,
};
var content = json.ToString();
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
if (success)
{
logger.Info("Successfully sent lower hand notification.");
}
else
{
logger.Error("Failed to send lower hand notification!");
}
return new ServerResponse(success, response.ToLogString());
}
public void Notify(ILogContent content)
{
logContent.Enqueue(content);
}
public ServerResponse RaiseHand(string message = null)
{
var authorization = ("Authorization", $"Bearer {oauth2Token}");
var contentType = "application/json;charset=UTF-8";
var token = ("SEBConnectionToken", connectionToken);
var json = new JObject
{
["type"] = "NOTIFICATION",
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
["numericValue"] = ++handNotificationId,
["text"] = $"<raisehand> {message}"
};
var content = json.ToString();
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
if (success)
{
logger.Info("Successfully sent raise hand notification.");
}
else
{
logger.Error("Failed to send raise hand notification!");
}
return new ServerResponse(success, response.ToLogString());
}
public ServerResponse SendSessionIdentifier(string identifier)
{
var authorization = ("Authorization", $"Bearer {oauth2Token}");
@ -362,6 +417,9 @@ namespace SafeExamBrowser.Server
{
switch (instruction)
{
case Instructions.NOTIFICATION_CONFIRM when attributes.Type == "raisehand" && attributes.Id == handNotificationId:
Task.Run(() => HandConfirmed?.Invoke());
break;
case Instructions.PROCTORING:
Task.Run(() => ProctoringInstructionReceived?.Invoke(attributes.Instruction));
break;

View file

@ -21,11 +21,21 @@ namespace SafeExamBrowser.Settings.Proctoring
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Determines whether the message input for the raise hand notification will be forced.
/// </summary>
public bool ForceRaiseHandMessage { get; set; }
/// <summary>
/// All settings for remote proctoring with Jitsi Meet.
/// </summary>
public JitsiMeetSettings JitsiMeet { get; set; }
/// <summary>
/// Determines whether the raise hand notification will be shown in the shell.
/// </summary>
public bool ShowRaiseHandNotification { get; set; }
/// <summary>
/// Determines whether the proctoring notification will be shown in the taskbar.
/// </summary>

View file

@ -12,8 +12,10 @@ using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Core.Contracts.Notifications;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
@ -101,6 +103,11 @@ namespace SafeExamBrowser.UserInterface.Contracts
/// </summary>
IProctoringWindow CreateProctoringWindow(IProctoringControl control);
/// <summary>
/// Creates a new notification control for the raise hand functionality of a remote proctoring session.
/// </summary>
INotificationControl CreateRaiseHandControl(IProctoringController controller, Location location, ProctoringSettings settings);
/// <summary>
/// Creates a new runtime window which runs on its own thread.
/// </summary>

View file

@ -125,6 +125,10 @@
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Proctoring.Contracts\SafeExamBrowser.Proctoring.Contracts.csproj">
<Project>{8e52bd1c-0540-4f16-b181-6665d43f7a7b}</Project>
<Name>SafeExamBrowser.Proctoring.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
<Project>{db701e6f-bddc-4cec-b662-335a9dc11809}</Project>
<Name>SafeExamBrowser.Server.Contracts</Name>

View file

@ -0,0 +1,37 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.RaiseHandControl"
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"
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
<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 Name="Grid" Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2">
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
<Border Background="Gray" BorderThickness="0" >
<StackPanel>
<TextBox Name="Message" AcceptsReturn="True" Height="150" IsReadOnly="False" Margin="5,5,5,0" Width="350" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
<Grid>
<Button Name="HandButton" Background="Transparent" Height="30" Margin="5" Padding="5" Template="{StaticResource TaskbarButton}" Width="150">
<Viewbox Stretch="Uniform">
<TextBlock x:Name="HandButtonText" FontWeight="Bold" TextAlignment="Center" />
</Viewbox>
</Button>
</Grid>
</StackPanel>
</Border>
</Popup>
<Button x:Name="NotificationButton" Padding="2" Template="{StaticResource ActionCenterButton}">
<Viewbox Stretch="Uniform">
<TextBlock x:Name="TextBlock" FontWeight="Bold" Margin="2" TextAlignment="Center" VerticalAlignment="Center" Text="L" />
</Viewbox>
</Button>
</Grid>
</UserControl>

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2021 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.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
{
public partial class RaiseHandControl : UserControl, INotificationControl
{
private readonly IProctoringController controller;
private readonly ProctoringSettings settings;
private readonly IText text;
public RaiseHandControl(IProctoringController controller, ProctoringSettings settings, IText text)
{
this.controller = controller;
this.settings = settings;
this.text = text;
InitializeComponent();
InitializeRaiseHandControl();
}
private void InitializeRaiseHandControl()
{
var originalBrush = Grid.Background;
controller.HandLowered += () => Dispatcher.Invoke(ShowLowered);
controller.HandRaised += () => Dispatcher.Invoke(ShowRaised);
HandButton.Click += RaiseHandButton_Click;
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
NotificationButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
NotificationButton.PreviewMouseLeftButtonUp += NotificationButton_PreviewMouseLeftButtonUp;
NotificationButton.PreviewMouseRightButtonUp += NotificationButton_PreviewMouseRightButtonUp;
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
Popup.Closed += (o, args) => Grid.Background = originalBrush;
}
private void NotificationButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (settings.ForceRaiseHandMessage || Popup.IsOpen)
{
Popup.IsOpen = !Popup.IsOpen;
}
else
{
ToggleHand();
}
}
private void NotificationButton_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
Popup.IsOpen = !Popup.IsOpen;
}
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
{
return new[]
{
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
};
}
private void RaiseHandButton_Click(object sender, RoutedEventArgs e)
{
ToggleHand();
}
private void ToggleHand()
{
if (controller.IsHandRaised)
{
controller.LowerHand();
}
else
{
controller.RaiseHand(Message.Text);
Message.Clear();
}
}
private void ShowLowered()
{
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
TextBlock.Text = "L";
}
private void ShowRaised()
{
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringLowerHand);
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandRaised);
TextBlock.Text = "R";
}
}
}

View file

@ -0,0 +1,38 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.RaiseHandControl"
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.Taskbar"
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
<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>
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0" >
<StackPanel>
<TextBox Name="Message" AcceptsReturn="True" Height="150" IsReadOnly="False" Margin="5,5,5,0" Width="350" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
<Grid>
<Button Name="HandButton" Background="Transparent" Height="30" Margin="5" Padding="5" Template="{StaticResource TaskbarButton}" Width="150">
<Viewbox Stretch="Uniform">
<TextBlock x:Name="HandButtonText" FontWeight="Bold" TextAlignment="Center" />
</Viewbox>
</Button>
</Grid>
</StackPanel>
</Border>
</Popup>
<Button x:Name="NotificationButton" Background="Transparent" Template="{StaticResource TaskbarButton}" Padding="5" Width="40">
<Viewbox Stretch="Uniform">
<TextBlock x:Name="TextBlock" FontWeight="Bold" Margin="2" TextAlignment="Center" VerticalAlignment="Center" Text="L" />
</Viewbox>
</Button>
</Grid>
</UserControl>

View file

@ -0,0 +1,125 @@
/*
* Copyright (c) 2021 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.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
{
public partial class RaiseHandControl : UserControl, INotificationControl
{
private readonly IProctoringController controller;
private readonly ProctoringSettings settings;
private readonly IText text;
public RaiseHandControl(IProctoringController controller, ProctoringSettings settings, IText text)
{
this.controller = controller;
this.settings = settings;
this.text = text;
InitializeComponent();
InitializeRaiseHandControl();
}
private void InitializeRaiseHandControl()
{
var originalBrush = NotificationButton.Background;
controller.HandLowered += () => Dispatcher.Invoke(ShowLowered);
controller.HandRaised += () => Dispatcher.Invoke(ShowRaised);
HandButton.Click += RaiseHandButton_Click;
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
NotificationButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
NotificationButton.PreviewMouseLeftButtonUp += NotificationButton_PreviewMouseLeftButtonUp;
NotificationButton.PreviewMouseRightButtonUp += NotificationButton_PreviewMouseRightButtonUp;
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Popup.Opened += (o, args) =>
{
Background = Brushes.LightGray;
NotificationButton.Background = Brushes.LightGray;
};
Popup.Closed += (o, args) =>
{
Background = originalBrush;
NotificationButton.Background = originalBrush;
};
}
private void NotificationButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (settings.ForceRaiseHandMessage || Popup.IsOpen)
{
Popup.IsOpen = !Popup.IsOpen;
}
else
{
ToggleHand();
}
}
private void NotificationButton_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
Popup.IsOpen = !Popup.IsOpen;
}
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
{
return new[]
{
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
};
}
private void RaiseHandButton_Click(object sender, RoutedEventArgs e)
{
ToggleHand();
}
private void ToggleHand()
{
if (controller.IsHandRaised)
{
controller.LowerHand();
}
else
{
controller.RaiseHand(Message.Text);
Message.Clear();
}
}
private void ShowLowered()
{
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
TextBlock.Text = "L";
}
private void ShowRaised()
{
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringLowerHand);
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandRaised);
TextBlock.Text = "R";
}
}
}

View file

@ -67,6 +67,12 @@
<Reference Include="WindowsFormsIntegration" />
</ItemGroup>
<ItemGroup>
<Compile Include="Controls\ActionCenter\RaiseHandControl.xaml.cs">
<DependentUpon>RaiseHandControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Taskbar\RaiseHandControl.xaml.cs">
<DependentUpon>RaiseHandControl.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\AboutWindow.xaml.cs">
<DependentUpon>AboutWindow.xaml</DependentUpon>
</Compile>
@ -193,6 +199,14 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Page Include="Controls\ActionCenter\RaiseHandControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Controls\Taskbar\RaiseHandControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Windows\AboutWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -503,6 +517,10 @@
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Proctoring.Contracts\SafeExamBrowser.Proctoring.Contracts.csproj">
<Project>{8e52bd1c-0540-4f16-b181-6665d43f7a7b}</Project>
<Name>SafeExamBrowser.Proctoring.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
<Project>{DB701E6F-BDDC-4CEC-B662-335A9DC11809}</Project>
<Name>SafeExamBrowser.Server.Contracts</Name>

View file

@ -16,8 +16,10 @@ using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Core.Contracts.Notifications;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
@ -168,6 +170,18 @@ namespace SafeExamBrowser.UserInterface.Desktop
return Application.Current.Dispatcher.Invoke(() => new ProctoringWindow(control));
}
public INotificationControl CreateRaiseHandControl(IProctoringController controller, Location location, ProctoringSettings settings)
{
if (location == Location.ActionCenter)
{
return new Controls.ActionCenter.RaiseHandControl(controller, settings, text);
}
else
{
return new Controls.Taskbar.RaiseHandControl(controller, settings, text);
}
}
public IRuntimeWindow CreateRuntimeWindow(AppConfig appConfig)
{
return Application.Current.Dispatcher.Invoke(() => new RuntimeWindow(appConfig, text));

View file

@ -0,0 +1,37 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter.RaiseHandControl"
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"
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
<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 Name="Grid" Background="{StaticResource ActionCenterDarkBrush}" Height="82" Margin="2">
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
<Border Background="Gray" BorderThickness="0" >
<StackPanel>
<TextBox Name="Message" AcceptsReturn="True" Height="150" IsReadOnly="False" Margin="5,5,5,0" Width="350" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
<Grid>
<Button Name="HandButton" Background="Transparent" Height="30" Margin="5" Padding="5" Template="{StaticResource TaskbarButton}" Width="150">
<Viewbox Stretch="Uniform">
<TextBlock x:Name="HandButtonText" FontWeight="Bold" TextAlignment="Center" />
</Viewbox>
</Button>
</Grid>
</StackPanel>
</Border>
</Popup>
<Button x:Name="NotificationButton" Padding="2" Template="{StaticResource ActionCenterButton}">
<Viewbox Stretch="Uniform">
<TextBlock x:Name="TextBlock" FontWeight="Bold" Margin="2" TextAlignment="Center" VerticalAlignment="Center" Text="L" />
</Viewbox>
</Button>
</Grid>
</UserControl>

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2021 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.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
{
public partial class RaiseHandControl : UserControl, INotificationControl
{
private readonly IProctoringController controller;
private readonly ProctoringSettings settings;
private readonly IText text;
public RaiseHandControl(IProctoringController controller, ProctoringSettings settings, IText text)
{
this.controller = controller;
this.settings = settings;
this.text = text;
InitializeComponent();
InitializeRaiseHandControl();
}
private void InitializeRaiseHandControl()
{
var originalBrush = Grid.Background;
controller.HandLowered += () => Dispatcher.Invoke(ShowLowered);
controller.HandRaised += () => Dispatcher.Invoke(ShowRaised);
HandButton.Click += RaiseHandButton_Click;
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
NotificationButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
NotificationButton.PreviewMouseLeftButtonUp += NotificationButton_PreviewMouseLeftButtonUp;
NotificationButton.PreviewMouseRightButtonUp += NotificationButton_PreviewMouseRightButtonUp;
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
Popup.Closed += (o, args) => Grid.Background = originalBrush;
}
private void NotificationButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (settings.ForceRaiseHandMessage || Popup.IsOpen)
{
Popup.IsOpen = !Popup.IsOpen;
}
else
{
ToggleHand();
}
}
private void NotificationButton_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
Popup.IsOpen = !Popup.IsOpen;
}
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
{
return new[]
{
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
};
}
private void RaiseHandButton_Click(object sender, RoutedEventArgs e)
{
ToggleHand();
}
private void ToggleHand()
{
if (controller.IsHandRaised)
{
controller.LowerHand();
}
else
{
controller.RaiseHand(Message.Text);
Message.Clear();
}
}
private void ShowLowered()
{
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
TextBlock.Text = "L";
}
private void ShowRaised()
{
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringLowerHand);
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandRaised);
TextBlock.Text = "R";
}
}
}

View file

@ -0,0 +1,37 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar.RaiseHandControl"
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"
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
<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>
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0" >
<StackPanel>
<TextBox Name="Message" AcceptsReturn="True" Height="150" IsReadOnly="False" Margin="5,5,5,0" Width="350" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
<Grid>
<Button Name="HandButton" Background="Transparent" Height="30" Margin="5" Padding="5" Template="{StaticResource TaskbarButton}" Width="150">
<Viewbox Stretch="Uniform">
<TextBlock x:Name="HandButtonText" FontWeight="Bold" TextAlignment="Center" />
</Viewbox>
</Button>
</Grid>
</StackPanel>
</Border>
</Popup>
<Button x:Name="NotificationButton" Background="Transparent" Template="{StaticResource TaskbarButton}" Padding="5" Width="60">
<Viewbox Stretch="Uniform">
<TextBlock x:Name="TextBlock" FontWeight="Bold" Margin="2" TextAlignment="Center" VerticalAlignment="Center" Text="L" />
</Viewbox>
</Button>
</Grid>
</UserControl>

View file

@ -0,0 +1,125 @@
/*
* Copyright (c) 2021 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.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar
{
public partial class RaiseHandControl : UserControl, INotificationControl
{
private readonly IProctoringController controller;
private readonly ProctoringSettings settings;
private readonly IText text;
public RaiseHandControl(IProctoringController controller, ProctoringSettings settings, IText text)
{
this.controller = controller;
this.settings = settings;
this.text = text;
InitializeComponent();
InitializeRaiseHandControl();
}
private void InitializeRaiseHandControl()
{
var originalBrush = NotificationButton.Background;
controller.HandLowered += () => Dispatcher.Invoke(ShowLowered);
controller.HandRaised += () => Dispatcher.Invoke(ShowRaised);
HandButton.Click += RaiseHandButton_Click;
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
NotificationButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = Popup.IsMouseOver));
NotificationButton.PreviewMouseLeftButtonUp += NotificationButton_PreviewMouseLeftButtonUp;
NotificationButton.PreviewMouseRightButtonUp += NotificationButton_PreviewMouseRightButtonUp;
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => Popup.IsOpen = IsMouseOver));
Popup.Opened += (o, args) =>
{
Background = Brushes.LightGray;
NotificationButton.Background = Brushes.LightGray;
};
Popup.Closed += (o, args) =>
{
Background = originalBrush;
NotificationButton.Background = originalBrush;
};
}
private void NotificationButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (settings.ForceRaiseHandMessage || Popup.IsOpen)
{
Popup.IsOpen = !Popup.IsOpen;
}
else
{
ToggleHand();
}
}
private void NotificationButton_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
Popup.IsOpen = !Popup.IsOpen;
}
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
{
return new[]
{
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
};
}
private void RaiseHandButton_Click(object sender, RoutedEventArgs e)
{
ToggleHand();
}
private void ToggleHand()
{
if (controller.IsHandRaised)
{
controller.LowerHand();
}
else
{
controller.RaiseHand(Message.Text);
Message.Clear();
}
}
private void ShowLowered()
{
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
TextBlock.Text = "L";
}
private void ShowRaised()
{
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringLowerHand);
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandRaised);
TextBlock.Text = "R";
}
}
}

View file

@ -68,6 +68,12 @@
<Reference Include="WindowsFormsIntegration" />
</ItemGroup>
<ItemGroup>
<Compile Include="Controls\ActionCenter\RaiseHandControl.xaml.cs">
<DependentUpon>RaiseHandControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Taskbar\RaiseHandControl.xaml.cs">
<DependentUpon>RaiseHandControl.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\AboutWindow.xaml.cs">
<DependentUpon>AboutWindow.xaml</DependentUpon>
</Compile>
@ -217,6 +223,10 @@
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Proctoring.Contracts\SafeExamBrowser.Proctoring.Contracts.csproj">
<Project>{8E52BD1C-0540-4F16-B181-6665D43F7A7B}</Project>
<Name>SafeExamBrowser.Proctoring.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
<Project>{DB701E6F-BDDC-4CEC-B662-335A9DC11809}</Project>
<Name>SafeExamBrowser.Server.Contracts</Name>
@ -247,6 +257,14 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Page Include="Controls\ActionCenter\RaiseHandControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Controls\Taskbar\RaiseHandControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Windows\AboutWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>

View file

@ -16,8 +16,10 @@ using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Core.Contracts.Notifications;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
@ -168,6 +170,18 @@ namespace SafeExamBrowser.UserInterface.Mobile
return Application.Current.Dispatcher.Invoke(() => new ProctoringWindow(control));
}
public INotificationControl CreateRaiseHandControl(IProctoringController controller, Location location, ProctoringSettings settings)
{
if (location == Location.ActionCenter)
{
return new Controls.ActionCenter.RaiseHandControl(controller, settings, text);
}
else
{
return new Controls.Taskbar.RaiseHandControl(controller, settings, text);
}
}
public IRuntimeWindow CreateRuntimeWindow(AppConfig appConfig)
{
return Application.Current.Dispatcher.Invoke(() => new RuntimeWindow(appConfig, text));