SEBWIN-312: Started implementing task view.
This commit is contained in:
		
							parent
							
								
									5f31656649
								
							
						
					
					
						commit
						08bf49b61b
					
				
					 19 changed files with 248 additions and 33 deletions
				
			
		| 
						 | 
				
			
			@ -28,6 +28,6 @@ namespace SafeExamBrowser.Applications.Contracts
 | 
			
		|||
		/// <summary>
 | 
			
		||||
		/// The resource providing the application icon.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		public IconResource IconResource { get; set; }
 | 
			
		||||
		public IconResource Icon { get; set; }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		|||
	/// </summary>
 | 
			
		||||
	public interface IApplicationInstance
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The icon resource for this instance.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		IconResource Icon { get; }
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// The unique identifier for the application instance.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
			};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,14 +98,12 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
 | 
			
		|||
			var terminationActivator = new Mock<ITerminationActivator>();
 | 
			
		||||
 | 
			
		||||
			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");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,13 +16,18 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
 | 
			
		|||
	public interface ITaskViewActivator : IActivator
 | 
			
		||||
	{
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Fired when the next application instance should be selected.
 | 
			
		||||
		/// Fired when the task view should be hidden.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		event ActivatorEventHandler Next;
 | 
			
		||||
		event ActivatorEventHandler Deactivated;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// 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.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		event ActivatorEventHandler Previous;
 | 
			
		||||
		event ActivatorEventHandler NextActivated;
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Fired when the task view should be made visible and the previous application instance should be selected.
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		event ActivatorEventHandler PreviousActivated;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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">
 | 
			
		||||
    <Grid>
 | 
			
		||||
        
 | 
			
		||||
        <StackPanel Name="Rows" Margin="10" Orientation="Vertical" />
 | 
			
		||||
    </Grid>
 | 
			
		||||
</Window>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<IApplicationInstance> instances;
 | 
			
		||||
 | 
			
		||||
		public TaskView()
 | 
			
		||||
		{
 | 
			
		||||
			InitializeComponent();
 | 
			
		||||
			instances = new List<IApplicationInstance>();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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<IApplicationInstance>(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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue