From 08bf49b61bef1ef7f08fb38a78ffbd3ebd6d48b0 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Fri, 15 Nov 2019 16:00:03 +0100 Subject: [PATCH] SEBWIN-312: Started implementing task view. --- .../ApplicationInfo.cs | 2 +- .../IApplicationInstance.cs | 6 + .../ApplicationFactory.cs | 2 +- .../ExternalApplication.cs | 4 +- .../ExternalApplicationInstance.cs | 5 +- SafeExamBrowser.Browser/BrowserApplication.cs | 8 +- .../BrowserApplicationInstance.cs | 10 +- .../Operations/ShellOperationTests.cs | 3 +- .../Operations/ShellOperation.cs | 2 +- .../Shell/ITaskViewActivator.cs | 13 +- .../ActionCenterApplicationButton.xaml.cs | 2 +- .../TaskbarApplicationControl.xaml.cs | 2 +- .../TaskbarApplicationInstanceButton.xaml.cs | 2 +- .../TaskView.xaml | 6 +- .../TaskView.xaml.cs | 133 +++++++++++++++++- .../ActionCenterApplicationButton.xaml.cs | 2 +- .../TaskbarApplicationControl.xaml.cs | 2 +- .../TaskbarApplicationInstanceButton.xaml.cs | 2 +- .../Activators/TaskViewKeyboardActivator.cs | 75 +++++++++- 19 files changed, 248 insertions(+), 33 deletions(-) diff --git a/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs b/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs index b47e66f1..58572d10 100644 --- a/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs +++ b/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs @@ -28,6 +28,6 @@ namespace SafeExamBrowser.Applications.Contracts /// /// The resource providing the application icon. /// - public IconResource IconResource { get; set; } + public IconResource Icon { get; set; } } } diff --git a/SafeExamBrowser.Applications.Contracts/IApplicationInstance.cs b/SafeExamBrowser.Applications.Contracts/IApplicationInstance.cs index fb8a2b35..c92fcab0 100644 --- a/SafeExamBrowser.Applications.Contracts/IApplicationInstance.cs +++ b/SafeExamBrowser.Applications.Contracts/IApplicationInstance.cs @@ -7,6 +7,7 @@ */ using SafeExamBrowser.Applications.Contracts.Events; +using SafeExamBrowser.Core.Contracts; namespace SafeExamBrowser.Applications.Contracts { @@ -15,6 +16,11 @@ namespace SafeExamBrowser.Applications.Contracts /// public interface IApplicationInstance { + /// + /// The icon resource for this instance. + /// + IconResource Icon { get; } + /// /// The unique identifier for the application instance. /// diff --git a/SafeExamBrowser.Applications/ApplicationFactory.cs b/SafeExamBrowser.Applications/ApplicationFactory.cs index 810ea3ba..8e061650 100644 --- a/SafeExamBrowser.Applications/ApplicationFactory.cs +++ b/SafeExamBrowser.Applications/ApplicationFactory.cs @@ -64,7 +64,7 @@ namespace SafeExamBrowser.Applications private IApplication BuildApplication(string executablePath, WhitelistApplication settings) { var icon = new IconResource { Type = IconResourceType.Embedded, Uri = new Uri(executablePath) }; - var info = new ApplicationInfo { IconResource = icon, Name = settings.DisplayName, Tooltip = settings.Description ?? settings.DisplayName }; + var info = new ApplicationInfo { Icon = icon, Name = settings.DisplayName, Tooltip = settings.Description ?? settings.DisplayName }; var application = new ExternalApplication(executablePath, info, logger.CloneFor(settings.DisplayName), processFactory); return application; diff --git a/SafeExamBrowser.Applications/ExternalApplication.cs b/SafeExamBrowser.Applications/ExternalApplication.cs index 2bf134bb..7d1a3cfe 100644 --- a/SafeExamBrowser.Applications/ExternalApplication.cs +++ b/SafeExamBrowser.Applications/ExternalApplication.cs @@ -44,9 +44,11 @@ namespace SafeExamBrowser.Applications { logger.Info("Starting application..."); + // TODO: Ensure that SEB does not crash if an application cannot be started!! + var process = processFactory.StartNew(executablePath); var id = new ApplicationInstanceIdentifier(process.Id); - var instance = new ExternalApplicationInstance(id, logger.CloneFor($"{Info.Name} {id}"), process); + var instance = new ExternalApplicationInstance(Info.Icon, id, logger.CloneFor($"{Info.Name} {id}"), process); instance.Initialize(); instances.Add(instance); diff --git a/SafeExamBrowser.Applications/ExternalApplicationInstance.cs b/SafeExamBrowser.Applications/ExternalApplicationInstance.cs index 3f3dd083..a9512f2c 100644 --- a/SafeExamBrowser.Applications/ExternalApplicationInstance.cs +++ b/SafeExamBrowser.Applications/ExternalApplicationInstance.cs @@ -10,6 +10,7 @@ using System; using System.Timers; using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Applications.Contracts.Events; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.WindowsApi.Contracts; @@ -24,6 +25,7 @@ namespace SafeExamBrowser.Applications private IProcess process; private Timer timer; + public IconResource Icon { get; } public InstanceIdentifier Id { get; } public string Name { get; } @@ -31,8 +33,9 @@ namespace SafeExamBrowser.Applications public event NameChangedEventHandler NameChanged; public event InstanceTerminatedEventHandler Terminated; - public ExternalApplicationInstance(InstanceIdentifier id, ILogger logger, IProcess process) + public ExternalApplicationInstance(IconResource icon, InstanceIdentifier id, ILogger logger, IProcess process) { + this.Icon = icon; this.Id = id; this.logger = logger; this.process = process; diff --git a/SafeExamBrowser.Browser/BrowserApplication.cs b/SafeExamBrowser.Browser/BrowserApplication.cs index 45af2b62..0ccd5098 100644 --- a/SafeExamBrowser.Browser/BrowserApplication.cs +++ b/SafeExamBrowser.Browser/BrowserApplication.cs @@ -65,7 +65,7 @@ namespace SafeExamBrowser.Browser var cefSettings = InitializeCefSettings(); var success = Cef.Initialize(cefSettings, true, default(IApp)); - Info = BuildApplicationInfo(); + InitializeApplicationInfo(); if (success) { @@ -95,11 +95,11 @@ namespace SafeExamBrowser.Browser logger.Info("Terminated browser."); } - private ApplicationInfo BuildApplicationInfo() + private void InitializeApplicationInfo() { - return new ApplicationInfo + Info = new ApplicationInfo { - IconResource = new BrowserIconResource(), + Icon = new BrowserIconResource(), Name = "Safe Exam Browser", Tooltip = text.Get(TextKey.Browser_Tooltip) }; diff --git a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs index a257d967..6dfd1e93 100644 --- a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs +++ b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs @@ -17,6 +17,7 @@ using SafeExamBrowser.Browser.Events; using SafeExamBrowser.Browser.Filters; using SafeExamBrowser.Browser.Handlers; using SafeExamBrowser.Configuration.Contracts; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Settings.Browser; @@ -48,6 +49,7 @@ namespace SafeExamBrowser.Browser get { return isMainInstance ? settings.MainWindow : settings.AdditionalWindow; } } + public IconResource Icon { get; private set; } public InstanceIdentifier Id { get; private set; } public string Name { get; private set; } @@ -108,6 +110,8 @@ namespace SafeExamBrowser.Browser var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} {Id}"); var requestHandler = new RequestHandler(appConfig, settings.Filter, requestFilter, requestLogger, text); + Icon = new BrowserIconResource(); + displayHandler.FaviconChanged += DisplayHandler_FaviconChanged; displayHandler.ProgressChanged += DisplayHandler_ProgressChanged; downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested; @@ -201,10 +205,10 @@ namespace SafeExamBrowser.Browser { if (task.IsCompleted && task.Result.IsSuccessStatusCode) { - var icon = new BrowserIconResource(uri); + Icon = new BrowserIconResource(uri); - IconChanged?.Invoke(icon); - window.UpdateIcon(icon); + IconChanged?.Invoke(Icon); + window.UpdateIcon(Icon); } }); } diff --git a/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs index 0a5b4878..a24e02d9 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs @@ -98,14 +98,12 @@ namespace SafeExamBrowser.Client.UnitTests.Operations var terminationActivator = new Mock(); context.Activators.Add(actionCenterActivator.Object); - context.Activators.Add(taskViewActivator.Object); context.Activators.Add(terminationActivator.Object); context.Settings.ActionCenter.EnableActionCenter = true; sut.Perform(); actionCenterActivator.Verify(a => a.Start(), Times.Once); - taskViewActivator.Verify(a => a.Start(), Times.Once); terminationActivator.Verify(a => a.Start(), Times.Once); } @@ -176,6 +174,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations [TestMethod] public void Perform_MustInitializeTaskView() { + // Only start activator if ALT+TAB enabled! Assert.Fail("TODO"); } diff --git a/SafeExamBrowser.Client/Operations/ShellOperation.cs b/SafeExamBrowser.Client/Operations/ShellOperation.cs index f67a9ffc..2eaf78b7 100644 --- a/SafeExamBrowser.Client/Operations/ShellOperation.cs +++ b/SafeExamBrowser.Client/Operations/ShellOperation.cs @@ -113,7 +113,7 @@ namespace SafeExamBrowser.Client.Operations actionCenterActivator.Start(); } - if (activator is ITaskViewActivator taskViewActivator) + if (Context.Settings.Keyboard.AllowAltTab && activator is ITaskViewActivator taskViewActivator) { taskView.Register(taskViewActivator); taskViewActivator.Start(); diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskViewActivator.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskViewActivator.cs index 8d2ed19e..f00c6d76 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskViewActivator.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Shell/ITaskViewActivator.cs @@ -16,13 +16,18 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell public interface ITaskViewActivator : IActivator { /// - /// Fired when the next application instance should be selected. + /// Fired when the task view should be hidden. /// - event ActivatorEventHandler Next; + event ActivatorEventHandler Deactivated; /// - /// Fired when the previous application instance should be selected. + /// Fired when the task view should be made visible and the next application instance should be selected. /// - event ActivatorEventHandler Previous; + event ActivatorEventHandler NextActivated; + + /// + /// Fired when the task view should be made visible and the previous application instance should be selected. + /// + event ActivatorEventHandler PreviousActivated; } } diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs index 0e58921f..1ae6d8a3 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs @@ -32,7 +32,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls private void InitializeApplicationInstanceButton() { - Icon.Content = IconResourceLoader.Load(info.IconResource); + Icon.Content = IconResourceLoader.Load(info.Icon); Text.Text = instance?.Name ?? info.Name; Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty); Button.ToolTip = instance?.Name ?? info.Tooltip; diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs index dba68797..9eb37158 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs @@ -37,7 +37,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls application.InstanceStarted += Application_InstanceStarted; Button.Click += Button_Click; - Button.Content = IconResourceLoader.Load(application.Info.IconResource); + Button.Content = IconResourceLoader.Load(application.Info.Icon); Button.MouseEnter += (o, args) => InstancePopup.IsOpen = InstanceStackPanel.Children.Count > 1; Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => InstancePopup.IsOpen = InstancePopup.IsMouseOver)); Button.ToolTip = application.Info.Tooltip; diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationInstanceButton.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationInstanceButton.xaml.cs index a848b3c2..e94bcad8 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationInstanceButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationInstanceButton.xaml.cs @@ -32,7 +32,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls { Button.Click += Button_Click; Button.ToolTip = instance.Name; - Icon.Content = IconResourceLoader.Load(info.IconResource); + Icon.Content = IconResourceLoader.Load(info.Icon); instance.IconChanged += Instance_IconChanged; instance.NameChanged += Instance_NameChanged; Text.Text = instance.Name; diff --git a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml index 25723e03..6985444f 100644 --- a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml +++ b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml @@ -4,9 +4,9 @@ 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" AllowsTransparency="True" Background="#74000000" BorderBrush="DodgerBlue" BorderThickness="1" Title="TaskView" - Height="450" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None"> + mc:Ignorable="d" AllowsTransparency="True" Background="#AA000000" BorderBrush="DodgerBlue" BorderThickness="1" Title="TaskView" + Topmost="True" Height="450" SizeToContent="WidthAndHeight" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None"> - + diff --git a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs index f9e380f3..8f88e170 100644 --- a/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/TaskView.xaml.cs @@ -6,27 +6,156 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; +using System.Collections.Generic; +using System.Linq; using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; using SafeExamBrowser.Applications.Contracts; +using SafeExamBrowser.Core.Contracts; using SafeExamBrowser.UserInterface.Contracts.Shell; +using SafeExamBrowser.UserInterface.Shared.Utilities; namespace SafeExamBrowser.UserInterface.Desktop { public partial class TaskView : Window, ITaskView { + private IList instances; + public TaskView() { InitializeComponent(); + instances = new List(); } public void Add(IApplication application) { - + application.InstanceStarted += Application_InstanceStarted; } public void Register(ITaskViewActivator activator) { - + activator.Deactivated += Activator_Deactivated; + activator.NextActivated += Activator_Next; + activator.PreviousActivated += Activator_Previous; + } + + private void Application_InstanceStarted(IApplicationInstance instance) + { + Dispatcher.InvokeAsync(() => + { + instance.IconChanged += Instance_IconChanged; + instance.NameChanged += Instance_NameChanged; + instance.Terminated += Instance_Terminated; + + instances.Add(instance); + Update(); + }); + } + + private void Activator_Deactivated() + { + Dispatcher.InvokeAsync(Hide); + } + + private void Activator_Next() + { + Dispatcher.InvokeAsync(ShowConditional); + } + + private void Activator_Previous() + { + Dispatcher.InvokeAsync(ShowConditional); + } + + private void Instance_IconChanged(IconResource icon) + { + // TODO Dispatcher.InvokeAsync(...); + } + + private void Instance_NameChanged(string name) + { + // TODO Dispatcher.InvokeAsync(...); + } + + private void Instance_Terminated(InstanceIdentifier id) + { + Dispatcher.InvokeAsync(() => + { + var instance = instances.FirstOrDefault(i => i.Id == id); + + if (instance != default(IApplicationInstance)) + { + instances.Remove(instance); + Update(); + } + }); + } + + private void ShowConditional() + { + if (Visibility != Visibility.Visible && instances.Any()) + { + Show(); + } + } + + private void Update() + { + var max = Math.Ceiling(Math.Sqrt(instances.Count)); + var stack = new Stack(instances); + + Rows.Children.Clear(); + + for (var rowCount = 0; rowCount < max && stack.Any(); rowCount++) + { + var row = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center }; + + Rows.Children.Add(row); + + for (var columnIndex = 0; columnIndex < max && stack.Any(); columnIndex++) + { + var instance = stack.Pop(); + var control = BuildInstanceControl(instance); + + row.Children.Add(control); + } + } + + UpdateLayout(); + + Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left; + Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top; + + if (!instances.Any()) + { + Hide(); + } + } + + private UIElement BuildInstanceControl(IApplicationInstance instance) + { + var border = new Border(); + var stackPanel = new StackPanel(); + var icon = IconResourceLoader.Load(instance.Icon); + + border.BorderBrush = Brushes.White; + border.BorderThickness = new Thickness(1); + border.Height = 150; + border.Margin = new Thickness(5); + border.Padding = new Thickness(2); + border.Width = 250; + border.Child = stackPanel; + + stackPanel.HorizontalAlignment = HorizontalAlignment.Center; + stackPanel.Orientation = Orientation.Vertical; + stackPanel.VerticalAlignment = VerticalAlignment.Center; + stackPanel.Children.Add(new ContentControl { Content = icon, MaxWidth = 50 }); + stackPanel.Children.Add(new TextBlock(new Run($"Instance {instance.Name ?? "NULL"}") { Foreground = Brushes.White, FontWeight = FontWeights.Bold })); + + return border; } } } diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs index eb31f74a..ec29ac3c 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs @@ -32,7 +32,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls private void InitializeApplicationInstanceButton() { - Icon.Content = IconResourceLoader.Load(info.IconResource); + Icon.Content = IconResourceLoader.Load(info.Icon); Text.Text = instance?.Name ?? info.Name; Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty); Button.ToolTip = instance?.Name ?? info.Tooltip; diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs index 8923cf44..1e8db711 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs @@ -37,7 +37,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls application.InstanceStarted += Application_InstanceStarted; Button.Click += Button_Click; - Button.Content = IconResourceLoader.Load(application.Info.IconResource); + Button.Content = IconResourceLoader.Load(application.Info.Icon); Button.MouseEnter += (o, args) => InstancePopup.IsOpen = InstanceStackPanel.Children.Count > 1; Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => InstancePopup.IsOpen = InstancePopup.IsMouseOver)); Button.ToolTip = application.Info.Tooltip; diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs index e901f343..295d4263 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationInstanceButton.xaml.cs @@ -32,7 +32,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls { Button.Click += Button_Click; Button.ToolTip = instance.Name; - Icon.Content = IconResourceLoader.Load(info.IconResource); + Icon.Content = IconResourceLoader.Load(info.Icon); instance.IconChanged += Instance_IconChanged; instance.NameChanged += Instance_NameChanged; Text.Text = instance.Name; diff --git a/SafeExamBrowser.UserInterface.Shared/Activators/TaskViewKeyboardActivator.cs b/SafeExamBrowser.UserInterface.Shared/Activators/TaskViewKeyboardActivator.cs index afb2f513..e3c0276c 100644 --- a/SafeExamBrowser.UserInterface.Shared/Activators/TaskViewKeyboardActivator.cs +++ b/SafeExamBrowser.UserInterface.Shared/Activators/TaskViewKeyboardActivator.cs @@ -17,10 +17,12 @@ namespace SafeExamBrowser.UserInterface.Shared.Activators { public class TaskViewKeyboardActivator : KeyboardActivator, ITaskViewActivator { + private bool Activated, BlockActivation, LeftShift, Tab; private ILogger logger; - public event ActivatorEventHandler Next; - public event ActivatorEventHandler Previous; + public event ActivatorEventHandler Deactivated; + public event ActivatorEventHandler NextActivated; + public event ActivatorEventHandler PreviousActivated; public TaskViewKeyboardActivator(ILogger logger, INativeMethods nativeMethods) : base(nativeMethods) { @@ -29,17 +31,82 @@ namespace SafeExamBrowser.UserInterface.Shared.Activators public void Pause() { - Paused = true; + BlockActivation = true; } public void Resume() { - Paused = false; + BlockActivation = false; } protected override bool Process(Key key, KeyModifier modifier, KeyState state) { + if (IsDeactivation(modifier)) + { + return false; + } + + if (IsActivation(key, modifier, state)) + { + return true; + } + return false; } + + private bool IsActivation(Key key, KeyModifier modifier, KeyState state) + { + var changed = false; + var pressed = state == KeyState.Pressed && modifier.HasFlag(KeyModifier.Alt); + + switch (key) + { + case Key.Tab: + changed = Tab != pressed; + Tab = pressed; + break; + case Key.LeftShift: + changed = LeftShift != pressed; + LeftShift = pressed; + break; + } + + var isActivation = Tab && changed; + + if (isActivation && !BlockActivation) + { + Activated = true; + + if (LeftShift) + { + logger.Debug("Detected sequence for previous instance."); + PreviousActivated?.Invoke(); + } + else + { + logger.Debug("Detected sequence for next instance."); + NextActivated?.Invoke(); + } + } + + return isActivation; + } + + private bool IsDeactivation(KeyModifier modifier) + { + var isDeactivation = Activated && !modifier.HasFlag(KeyModifier.Alt); + + if (isDeactivation) + { + Activated = false; + LeftShift = false; + Tab = false; + + logger.Debug("Detected deactivation sequence."); + Deactivated?.Invoke(); + } + + return isDeactivation; + } } }