Restore SEBPatch
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.AboutWindow" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
|
||||
mc:Ignorable="d" Background="White" Height="450" Width="675" ResizeMode="NoResize" Icon="../Images/SafeExamBrowser.ico" FontSize="16"
|
||||
ShowInTaskbar="False" WindowStartupLocation="CenterScreen">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1.2*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.ColumnSpan="2" Source="pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/SplashScreen.png" Margin="0,5,0,0" />
|
||||
<TextBlock x:Name="VersionInfo" Grid.Row="0" Grid.Column="1" Foreground="DimGray" Margin="35,95,100,10" TextWrapping="Wrap" />
|
||||
<ScrollViewer Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Template="{StaticResource SmallBarScrollViewer}" VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock x:Name="MainText" Margin="10" TextWrapping="Wrap">
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Bold><Underline>.NET Framework</Underline></Bold>
|
||||
<LineBreak />
|
||||
Copyright © 2002-2024 Microsoft. All rights reserved.
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Bold><Underline>CefSharp (.NET bindings for the Chromium Embedded Framework)</Underline></Bold>
|
||||
<LineBreak />
|
||||
Copyright © 2010-2024 The CefSharp Authors. All rights reserved.
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Bold><Underline>CEF (Chromium Embedded Framework)</Underline></Bold>
|
||||
<LineBreak />
|
||||
Copyright © 2008-2024 The Chromium Embedded Framework Authors. All rights reserved.
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Bold><Underline>Font-Awesome-WPF</Underline></Bold>
|
||||
<LineBreak />
|
||||
Copyright © 2014-2024 charri. All rights reserved.
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Bold><Underline>KGy SOFT Core & Drawing Libraries</Underline></Bold>
|
||||
<LineBreak />
|
||||
Copyright © 2005-2024 KGy SOFT. All rights reserved.
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Bold><Underline>KnownFolders</Underline></Bold>
|
||||
<LineBreak />
|
||||
Copyright © 2017-2024 Syroot. All rights reserved.
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Bold><Underline>Microsoft.Windows.SDK.Contracts</Underline></Bold>
|
||||
<LineBreak />
|
||||
Copyright © 2019-2024 Microsoft. All rights reserved.
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Bold><Underline>NAudio</Underline></Bold>
|
||||
<LineBreak />
|
||||
Copyright © 2008-2024 Mark Heath & contributors. All rights reserved.
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Bold><Underline>Newtonsoft.Json</Underline></Bold>
|
||||
<LineBreak />
|
||||
Copyright © 2007-2024 James Newton-King. All rights reserved.
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Bold><Underline>Visual C++ Redistributable</Underline></Bold>
|
||||
<LineBreak />
|
||||
Copyright © 1993-2024 Microsoft. All rights reserved.
|
||||
</TextBlock>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class AboutWindow : Window, IWindow
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly IText text;
|
||||
|
||||
private WindowClosedEventHandler closed;
|
||||
private WindowClosingEventHandler closing;
|
||||
|
||||
event WindowClosedEventHandler IWindow.Closed
|
||||
{
|
||||
add { closed += value; }
|
||||
remove { closed -= value; }
|
||||
}
|
||||
|
||||
event WindowClosingEventHandler IWindow.Closing
|
||||
{
|
||||
add { closing += value; }
|
||||
remove { closing -= value; }
|
||||
}
|
||||
|
||||
internal AboutWindow(AppConfig appConfig, IText text)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeAboutWindow();
|
||||
}
|
||||
|
||||
public void BringToForeground()
|
||||
{
|
||||
Activate();
|
||||
}
|
||||
|
||||
private void InitializeAboutWindow()
|
||||
{
|
||||
Closed += (o, args) => closed?.Invoke();
|
||||
Closing += (o, args) => closing?.Invoke();
|
||||
MainText.Inlines.InsertBefore(MainText.Inlines.FirstInline, new Run(text.Get(TextKey.AboutWindow_LicenseInfo)));
|
||||
Title = text.Get(TextKey.AboutWindow_Title);
|
||||
|
||||
VersionInfo.Inlines.Add(new Run($"{text.Get(TextKey.Version)} {appConfig.ProgramInformationalVersion}"));
|
||||
VersionInfo.Inlines.Add(new LineBreak());
|
||||
VersionInfo.Inlines.Add(new Run($"{text.Get(TextKey.Build)} {appConfig.ProgramBuildVersion}") { FontSize = 10, Foreground = Brushes.Gray });
|
||||
VersionInfo.Inlines.Add(new LineBreak());
|
||||
VersionInfo.Inlines.Add(new LineBreak());
|
||||
VersionInfo.Inlines.Add(new Run(appConfig.ProgramCopyright) { FontSize = 12, Foreground = Brushes.Gray });
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.ActionCenter" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter"
|
||||
mc:Ignorable="d" Title="ActionCenter" Height="1000" Width="400" Background="{DynamicResource BackgroundTransparentBrush}"
|
||||
AllowsTransparency="True" FontSize="16" WindowStyle="None" Topmost="True" ResizeMode="NoResize">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
|
||||
<StackPanel x:Name="ApplicationPanel" Orientation="Vertical" />
|
||||
</ScrollViewer>
|
||||
<UniformGrid x:Name="ControlPanel" Grid.Row="1" Columns="3" Margin="10">
|
||||
<local:Clock x:Name="Clock" />
|
||||
<local:QuitButton x:Name="QuitButton" />
|
||||
</UniformGrid>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Animation;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class ActionCenter : Window, IActionCenter
|
||||
{
|
||||
public bool ShowClock
|
||||
{
|
||||
set { Dispatcher.Invoke(() => Clock.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
|
||||
}
|
||||
|
||||
public bool ShowQuitButton
|
||||
{
|
||||
set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
|
||||
}
|
||||
|
||||
public event QuitButtonClickedEventHandler QuitButtonClicked;
|
||||
|
||||
internal ActionCenter()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeActionCenter();
|
||||
}
|
||||
|
||||
public void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false)
|
||||
{
|
||||
if (control is UIElement uiElement)
|
||||
{
|
||||
if (atFirstPosition)
|
||||
{
|
||||
ApplicationPanel.Children.Insert(0, uiElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplicationPanel.Children.Add(uiElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNotificationControl(INotificationControl control)
|
||||
{
|
||||
if (control is UIElement uiElement)
|
||||
{
|
||||
ControlPanel.Children.Insert(ControlPanel.Children.Count - 2, uiElement);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddSystemControl(ISystemControl control)
|
||||
{
|
||||
if (control is UIElement uiElement)
|
||||
{
|
||||
ControlPanel.Children.Insert(ControlPanel.Children.Count - 2, uiElement);
|
||||
}
|
||||
}
|
||||
|
||||
public new void Close()
|
||||
{
|
||||
Dispatcher.Invoke(base.Close);
|
||||
}
|
||||
|
||||
public new void Hide()
|
||||
{
|
||||
Dispatcher.Invoke(HideAnimated);
|
||||
}
|
||||
|
||||
public void InitializeBounds()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
Height = SystemParameters.WorkArea.Height;
|
||||
Top = 0;
|
||||
Left = -Width;
|
||||
});
|
||||
}
|
||||
|
||||
public void InitializeText(IText text)
|
||||
{
|
||||
QuitButton.ToolTip = text.Get(TextKey.Shell_QuitButton);
|
||||
QuitButton.Text.Text = text.Get(TextKey.Shell_QuitButton);
|
||||
}
|
||||
|
||||
public void Promote()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Dispatcher.Invoke(ShowAnimated);
|
||||
Thread.Sleep(2000);
|
||||
Dispatcher.Invoke(HideAnimated);
|
||||
});
|
||||
}
|
||||
|
||||
public void Register(IActionCenterActivator activator)
|
||||
{
|
||||
activator.Activated += Activator_Activated;
|
||||
activator.Deactivated += Activator_Deactivated;
|
||||
activator.Toggled += Activator_Toggled;
|
||||
}
|
||||
|
||||
public new void Show()
|
||||
{
|
||||
Dispatcher.Invoke(ShowAnimated);
|
||||
}
|
||||
|
||||
private void HideAnimated()
|
||||
{
|
||||
var storyboard = new Storyboard();
|
||||
var animation = new DoubleAnimation
|
||||
{
|
||||
EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut },
|
||||
From = 0,
|
||||
To = -Width,
|
||||
Duration = new Duration(TimeSpan.FromMilliseconds(500))
|
||||
};
|
||||
|
||||
Storyboard.SetTarget(animation, this);
|
||||
Storyboard.SetTargetProperty(animation, new PropertyPath(LeftProperty));
|
||||
|
||||
storyboard.Children.Add(animation);
|
||||
storyboard.Completed += HideAnimation_Completed;
|
||||
storyboard.Begin();
|
||||
}
|
||||
|
||||
private void ShowAnimated()
|
||||
{
|
||||
var storyboard = new Storyboard();
|
||||
var animation = new DoubleAnimation
|
||||
{
|
||||
EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut },
|
||||
From = -Width,
|
||||
To = 0,
|
||||
Duration = new Duration(TimeSpan.FromMilliseconds(500))
|
||||
};
|
||||
|
||||
Storyboard.SetTarget(animation, this);
|
||||
Storyboard.SetTargetProperty(animation, new PropertyPath(LeftProperty));
|
||||
|
||||
InitializeBounds();
|
||||
base.Show();
|
||||
|
||||
storyboard.Children.Add(animation);
|
||||
storyboard.Completed += ShowAnimation_Completed;
|
||||
storyboard.Begin();
|
||||
}
|
||||
|
||||
private void ShowAnimation_Completed(object sender, EventArgs e)
|
||||
{
|
||||
Activate();
|
||||
Deactivated += ActionCenter_Deactivated;
|
||||
}
|
||||
|
||||
private void HideAnimation_Completed(object sender, EventArgs e)
|
||||
{
|
||||
Deactivated -= ActionCenter_Deactivated;
|
||||
base.Hide();
|
||||
}
|
||||
|
||||
private void ActionCenter_Deactivated(object sender, EventArgs e)
|
||||
{
|
||||
HideAnimated();
|
||||
}
|
||||
|
||||
private void Activator_Activated()
|
||||
{
|
||||
Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (Visibility != Visibility.Visible)
|
||||
{
|
||||
ShowAnimated();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void Activator_Deactivated()
|
||||
{
|
||||
Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (Visibility == Visibility.Visible)
|
||||
{
|
||||
HideAnimated();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void Activator_Toggled()
|
||||
{
|
||||
Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (Visibility != Visibility.Visible)
|
||||
{
|
||||
ShowAnimated();
|
||||
}
|
||||
else
|
||||
{
|
||||
HideAnimated();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeActionCenter()
|
||||
{
|
||||
QuitButton.Clicked += (args) => QuitButtonClicked?.Invoke(args);
|
||||
}
|
||||
}
|
||||
}
|
128
SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml
Normal file
128
SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml
Normal file
@@ -0,0 +1,128 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.BrowserWindow" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Controls"
|
||||
mc:Ignorable="d" Background="#FFF0F0F0" FontSize="16" Height="500" Width="750" MinHeight="250" MinWidth="250" Icon="../Images/SafeExamBrowser.ico">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border x:Name="Toolbar" Grid.Row="0" BorderBrush="LightGray" BorderThickness="0,0,0,1" Margin="5,0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" x:Name="HomeButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1" x:Name="BackwardButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="2" x:Name="ForwardButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="3" x:Name="ReloadButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="4" x:Name="UrlTextBox" Height="50" HorizontalAlignment="Stretch" Margin="5,10" Padding="8" VerticalContentAlignment="Center" />
|
||||
<Button Grid.Column="5" x:Name="DownloadsButton" Height="50" HorizontalAlignment="Center" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" Visibility="Collapsed">
|
||||
<fa:ImageAwesome Icon="Download" />
|
||||
</Button>
|
||||
<Popup x:Name="DownloadsPopup" AllowsTransparency="True" PopupAnimation="Slide" Placement="Custom" PlacementTarget="{Binding ElementName=BrowserControlHost}">
|
||||
<Border Background="{StaticResource BackgroundBrush}" BorderBrush="LightGray" BorderThickness="1,0,1,1" MinWidth="250">
|
||||
<StackPanel x:Name="Downloads" Orientation="Vertical" />
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button Grid.Column="6" x:Name="MenuButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
|
||||
<Popup x:Name="MenuPopup" IsOpen="False" AllowsTransparency="True" PopupAnimation="Slide" Placement="Custom" PlacementTarget="{Binding ElementName=BrowserControlHost}">
|
||||
<Border Background="{StaticResource BackgroundBrush}" BorderBrush="LightGray" BorderThickness="1,0,1,1" Width="350">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Grid x:Name="ZoomMenuItem" Height="55">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" x:Name="ZoomText" HorizontalAlignment="Left" Margin="10,0" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1" x:Name="ZoomInButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" TabIndex="30">
|
||||
<fa:ImageAwesome Icon="SearchPlus" />
|
||||
</Button>
|
||||
<Button Grid.Column="2" x:Name="ZoomResetButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" TabIndex="31">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="ZoomLevel" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
<Button Grid.Column="3" x:Name="ZoomOutButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" TabIndex="32">
|
||||
<fa:ImageAwesome Icon="SearchMinus" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid x:Name="FindMenuItem" Height="55">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" x:Name="FindMenuText" HorizontalAlignment="Left" Margin="10,0" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1" x:Name="FindMenuButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" TabIndex="33">
|
||||
<fa:ImageAwesome Icon="Search" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid x:Name="DeveloperConsoleMenuItem" Height="55">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" x:Name="DeveloperConsoleText" HorizontalAlignment="Left" Margin="10,0" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1" x:Name="DeveloperConsoleButton" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" TabIndex="34">
|
||||
<fa:ImageAwesome Icon="Wrench" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
<ProgressBar Grid.Row="1" x:Name="ProgressBar" Background="Transparent" BorderThickness="0" Foreground="DodgerBlue" Height="2" Margin="0,0,0,-1" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<WindowsFormsHost Grid.Row="1" x:Name="BrowserControlHost" />
|
||||
<Border x:Name="Findbar" Grid.Row="2" BorderBrush="LightGray" BorderThickness="0,1,0,0" Margin="5,0" Visibility="Collapsed">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Height="50" Margin="5,0">
|
||||
<TextBox x:Name="FindTextBox" Padding="5,0" VerticalContentAlignment="Center" Width="200" />
|
||||
<Button x:Name="FindPreviousButton" Cursor="Hand" Padding="5" VerticalContentAlignment="Center" Width="50">
|
||||
<fa:ImageAwesome Icon="AngleUp" />
|
||||
</Button>
|
||||
<Button x:Name="FindNextButton" Cursor="Hand" Padding="5" VerticalContentAlignment="Center" Width="50">
|
||||
<fa:ImageAwesome Icon="AngleDown" />
|
||||
</Button>
|
||||
<CheckBox Grid.Column="1" x:Name="FindCaseSensitiveCheckBox" Cursor="Hand" Margin="10,0" Padding="5,0" VerticalContentAlignment="Center" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="4" x:Name="FindbarCloseButton" Height="50" HorizontalAlignment="Center" Margin="5" Padding="10" Template="{StaticResource BrowserButton}" VerticalAlignment="Center">
|
||||
<fa:ImageAwesome Icon="Close" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,646 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media.Imaging;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
|
||||
using SafeExamBrowser.UserInterface.Mobile.Controls.Browser;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class BrowserWindow : Window, IBrowserWindow
|
||||
{
|
||||
private const string CLEAR_FIND_TERM = "thisisahacktoclearthesearchresultsasitappearsthatthereisnosuchfunctionalityincef";
|
||||
|
||||
private readonly bool isMainWindow;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly IText text;
|
||||
private readonly ILogger logger;
|
||||
private readonly IBrowserControl browserControl;
|
||||
|
||||
private WindowClosedEventHandler closed;
|
||||
private WindowClosingEventHandler closing;
|
||||
private bool browserControlGetsFocusFromTaskbar = false;
|
||||
private IInputElement tabKeyDownFocusElement = null;
|
||||
|
||||
private WindowSettings WindowSettings
|
||||
{
|
||||
get { return isMainWindow ? settings.MainWindow : settings.AdditionalWindow; }
|
||||
}
|
||||
|
||||
public bool CanNavigateBackwards { set => Dispatcher.Invoke(() => BackwardButton.IsEnabled = value); }
|
||||
public bool CanNavigateForwards { set => Dispatcher.Invoke(() => ForwardButton.IsEnabled = value); }
|
||||
public IntPtr Handle { get; private set; }
|
||||
|
||||
public event AddressChangedEventHandler AddressChanged;
|
||||
public event ActionRequestedEventHandler BackwardNavigationRequested;
|
||||
public event ActionRequestedEventHandler DeveloperConsoleRequested;
|
||||
public event FindRequestedEventHandler FindRequested;
|
||||
public event ActionRequestedEventHandler ForwardNavigationRequested;
|
||||
public event ActionRequestedEventHandler HomeNavigationRequested;
|
||||
public event ActionRequestedEventHandler ReloadRequested;
|
||||
public event ActionRequestedEventHandler ZoomInRequested;
|
||||
public event ActionRequestedEventHandler ZoomOutRequested;
|
||||
public event ActionRequestedEventHandler ZoomResetRequested;
|
||||
public event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||
|
||||
event WindowClosedEventHandler IWindow.Closed
|
||||
{
|
||||
add { closed += value; }
|
||||
remove { closed -= value; }
|
||||
}
|
||||
|
||||
event WindowClosingEventHandler IWindow.Closing
|
||||
{
|
||||
add { closing += value; }
|
||||
remove { closing -= value; }
|
||||
}
|
||||
|
||||
internal BrowserWindow(IBrowserControl browserControl, BrowserSettings settings, bool isMainWindow, IText text, ILogger logger)
|
||||
{
|
||||
this.isMainWindow = isMainWindow;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
this.logger = logger;
|
||||
this.browserControl = browserControl;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeBrowserWindow(browserControl);
|
||||
}
|
||||
|
||||
public void BringToForeground()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (WindowState == WindowState.Minimized)
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
Activate();
|
||||
});
|
||||
}
|
||||
|
||||
public new void Close()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
Closing -= BrowserWindow_Closing;
|
||||
closing?.Invoke();
|
||||
base.Close();
|
||||
});
|
||||
}
|
||||
|
||||
public void FocusToolbar(bool forward)
|
||||
{
|
||||
Dispatcher.BeginInvoke((Action) (async () =>
|
||||
{
|
||||
Activate();
|
||||
await Task.Delay(50);
|
||||
|
||||
// focus all elements in the toolbar, such that the last element that is enabled gets focus
|
||||
var buttons = new System.Windows.Controls.Control[] { ForwardButton, BackwardButton, ReloadButton, UrlTextBox, MenuButton, };
|
||||
for (var i = forward ? 0 : buttons.Length - 1; i >= 0 && i < buttons.Length; i += forward ? 1 : -1)
|
||||
{
|
||||
if (buttons[i].IsEnabled && buttons[i].Visibility == Visibility.Visible)
|
||||
{
|
||||
buttons[i].Focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public void FocusBrowser()
|
||||
{
|
||||
Dispatcher.BeginInvoke((Action) (async () =>
|
||||
{
|
||||
FocusToolbar(false);
|
||||
await Task.Delay(100);
|
||||
|
||||
browserControlGetsFocusFromTaskbar = true;
|
||||
|
||||
var focusedElement = FocusManager.GetFocusedElement(this) as UIElement;
|
||||
focusedElement.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
|
||||
|
||||
await Task.Delay(150);
|
||||
browserControlGetsFocusFromTaskbar = false;
|
||||
}));
|
||||
}
|
||||
|
||||
public void FocusAddressBar()
|
||||
{
|
||||
Dispatcher.BeginInvoke((Action) (() =>
|
||||
{
|
||||
UrlTextBox.Focus();
|
||||
}));
|
||||
}
|
||||
|
||||
public new void Hide()
|
||||
{
|
||||
Dispatcher.Invoke(base.Hide);
|
||||
}
|
||||
|
||||
public new void Show()
|
||||
{
|
||||
Dispatcher.Invoke(base.Show);
|
||||
}
|
||||
|
||||
public void ShowFindbar()
|
||||
{
|
||||
Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
Findbar.Visibility = Visibility.Visible;
|
||||
FindTextBox.Focus();
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateAddress(string url)
|
||||
{
|
||||
Dispatcher.Invoke(() => UrlTextBox.Text = url);
|
||||
}
|
||||
|
||||
public void UpdateIcon(IconResource icon)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (icon is BitmapIconResource bitmap)
|
||||
{
|
||||
Icon = new BitmapImage(bitmap.Uri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateDownloadState(DownloadItemState state)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var isNewItem = true;
|
||||
|
||||
foreach (var child in Downloads.Children)
|
||||
{
|
||||
if (child is DownloadItemControl control && control.Id == state.Id)
|
||||
{
|
||||
control.Update(state);
|
||||
isNewItem = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNewItem)
|
||||
{
|
||||
var control = new DownloadItemControl(state.Id, text);
|
||||
|
||||
control.Update(state);
|
||||
Downloads.Children.Add(control);
|
||||
}
|
||||
|
||||
DownloadsButton.Visibility = Visibility.Visible;
|
||||
DownloadsPopup.IsOpen = IsActive;
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateLoadingState(bool isLoading)
|
||||
{
|
||||
Dispatcher.Invoke(() => ProgressBar.Visibility = isLoading ? Visibility.Visible : Visibility.Hidden);
|
||||
}
|
||||
|
||||
public void UpdateProgress(double value)
|
||||
{
|
||||
Dispatcher.Invoke(() => ProgressBar.Value = value * 100);
|
||||
}
|
||||
|
||||
public void UpdateTitle(string title)
|
||||
{
|
||||
Dispatcher.Invoke(() => Title = title);
|
||||
}
|
||||
|
||||
public void UpdateZoomLevel(double value)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ZoomLevel.Text = $"{value}%";
|
||||
var zoomButtonName = text.Get(TextKey.BrowserWindow_ZoomLevelReset).Replace("%%ZOOM%%", value.ToString("0"));
|
||||
ZoomResetButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, zoomButtonName);
|
||||
});
|
||||
}
|
||||
|
||||
private void BrowserWindow_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
if (isMainWindow)
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
closing?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void BrowserWindow_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Tab)
|
||||
{
|
||||
var hasShift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
|
||||
if (Toolbar.IsKeyboardFocusWithin && hasShift)
|
||||
{
|
||||
var firstActiveElementInToolbar = Toolbar.PredictFocus(FocusNavigationDirection.Right);
|
||||
if (firstActiveElementInToolbar is System.Windows.UIElement)
|
||||
{
|
||||
var control = firstActiveElementInToolbar as System.Windows.UIElement;
|
||||
if (control.IsKeyboardFocusWithin)
|
||||
{
|
||||
LoseFocusRequested?.Invoke(false);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tabKeyDownFocusElement = FocusManager.GetFocusedElement(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
tabKeyDownFocusElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void BrowserWindow_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.F5)
|
||||
{
|
||||
ReloadRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (e.Key == Key.Home)
|
||||
{
|
||||
HomeNavigationRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (settings.AllowFind && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && e.Key == Key.F)
|
||||
{
|
||||
ShowFindbar();
|
||||
}
|
||||
|
||||
if (e.Key == Key.Tab)
|
||||
{
|
||||
var hasCtrl = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
|
||||
var hasShift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
|
||||
if (BrowserControlHost.IsFocused && hasCtrl)
|
||||
{
|
||||
if (Findbar.Visibility == Visibility.Hidden || hasShift)
|
||||
{
|
||||
Toolbar.Focus();
|
||||
}
|
||||
else if (Toolbar.Visibility == Visibility.Hidden)
|
||||
{
|
||||
Findbar.Focus();
|
||||
}
|
||||
}
|
||||
else if (MenuPopup.IsKeyboardFocusWithin)
|
||||
{
|
||||
var focusedElement = FocusManager.GetFocusedElement(this);
|
||||
var focusedControl = focusedElement as System.Windows.Controls.Control;
|
||||
var prevFocusedControl = tabKeyDownFocusElement as System.Windows.Controls.Control;
|
||||
|
||||
if (focusedControl != null && prevFocusedControl != null)
|
||||
{
|
||||
if (!hasShift && focusedControl.TabIndex < prevFocusedControl.TabIndex)
|
||||
{
|
||||
MenuPopup.IsOpen = false;
|
||||
FocusBrowser();
|
||||
}
|
||||
else if (hasShift && focusedControl.TabIndex > prevFocusedControl.TabIndex)
|
||||
{
|
||||
MenuPopup.IsOpen = false;
|
||||
MenuButton.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.Key == Key.Escape && MenuPopup.IsOpen)
|
||||
{
|
||||
MenuPopup.IsOpen = false;
|
||||
MenuButton.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void BrowserWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Handle = new WindowInteropHelper(this).Handle;
|
||||
|
||||
if (isMainWindow)
|
||||
{
|
||||
this.DisableCloseButton();
|
||||
}
|
||||
}
|
||||
|
||||
private void FindbarCloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FindRequested?.Invoke(CLEAR_FIND_TERM, true, false);
|
||||
Findbar.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void FindNextButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FindRequested?.Invoke(FindTextBox.Text, false, FindCaseSensitiveCheckBox.IsChecked == true);
|
||||
}
|
||||
|
||||
private void FindPreviousButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FindRequested?.Invoke(FindTextBox.Text, false, FindCaseSensitiveCheckBox.IsChecked == true, false);
|
||||
}
|
||||
|
||||
private void FindTextBox_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(FindTextBox.Text))
|
||||
{
|
||||
FindRequested?.Invoke(CLEAR_FIND_TERM, true, false);
|
||||
}
|
||||
else if (e.Key == Key.Enter)
|
||||
{
|
||||
FindRequested?.Invoke(FindTextBox.Text, false, FindCaseSensitiveCheckBox.IsChecked == true);
|
||||
}
|
||||
else
|
||||
{
|
||||
FindRequested?.Invoke(FindTextBox.Text, true, FindCaseSensitiveCheckBox.IsChecked == true);
|
||||
}
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width - Toolbar.Margin.Right - popupSize.Width, -2), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(SystemParameters.WorkArea))
|
||||
{
|
||||
Dispatcher.InvokeAsync(InitializeBounds);
|
||||
}
|
||||
}
|
||||
|
||||
private void UrlTextBox_GotMouseCapture(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (UrlTextBox.Tag as bool? != true)
|
||||
{
|
||||
UrlTextBox.SelectAll();
|
||||
UrlTextBox.Tag = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UrlTextBox_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
AddressChanged?.Invoke(UrlTextBox.Text);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeBrowserWindow(IBrowserControl browserControl)
|
||||
{
|
||||
if (browserControl.EmbeddableControl is System.Windows.Forms.Control control)
|
||||
{
|
||||
BrowserControlHost.Child = control;
|
||||
}
|
||||
|
||||
RegisterEvents();
|
||||
InitializeBounds();
|
||||
ApplySettings();
|
||||
LoadIcons();
|
||||
LoadText();
|
||||
}
|
||||
|
||||
private void RegisterEvents()
|
||||
{
|
||||
BackwardButton.Click += (o, args) => BackwardNavigationRequested?.Invoke();
|
||||
Closed += (o, args) => closed?.Invoke();
|
||||
Closing += BrowserWindow_Closing;
|
||||
DeveloperConsoleButton.Click += (o, args) => DeveloperConsoleRequested?.Invoke();
|
||||
DownloadsButton.Click += (o, args) => DownloadsPopup.IsOpen = !DownloadsPopup.IsOpen;
|
||||
DownloadsButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => DownloadsPopup.IsOpen = DownloadsPopup.IsMouseOver));
|
||||
DownloadsPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
DownloadsPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => DownloadsPopup.IsOpen = DownloadsPopup.IsMouseOver));
|
||||
FindbarCloseButton.Click += FindbarCloseButton_Click;
|
||||
FindNextButton.Click += FindNextButton_Click;
|
||||
FindPreviousButton.Click += FindPreviousButton_Click;
|
||||
FindMenuButton.Click += (o, args) => ShowFindbar();
|
||||
FindTextBox.KeyUp += FindTextBox_KeyUp;
|
||||
ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke();
|
||||
HomeButton.Click += (o, args) => HomeNavigationRequested?.Invoke();
|
||||
Loaded += BrowserWindow_Loaded;
|
||||
MenuButton.Click += MenuButton_Click;
|
||||
MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
MenuPopup.LostFocus += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsKeyboardFocusWithin));
|
||||
KeyDown += BrowserWindow_KeyDown;
|
||||
KeyUp += BrowserWindow_KeyUp;
|
||||
LocationChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; };
|
||||
ReloadButton.Click += (o, args) => ReloadRequested?.Invoke();
|
||||
SizeChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; };
|
||||
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
|
||||
UrlTextBox.GotKeyboardFocus += (o, args) => UrlTextBox.SelectAll();
|
||||
UrlTextBox.GotMouseCapture += UrlTextBox_GotMouseCapture;
|
||||
UrlTextBox.LostKeyboardFocus += (o, args) => UrlTextBox.Tag = null;
|
||||
UrlTextBox.LostFocus += (o, args) => UrlTextBox.Tag = null;
|
||||
UrlTextBox.KeyUp += UrlTextBox_KeyUp;
|
||||
UrlTextBox.MouseDoubleClick += (o, args) => UrlTextBox.SelectAll();
|
||||
ZoomInButton.Click += (o, args) => ZoomInRequested?.Invoke();
|
||||
ZoomOutButton.Click += (o, args) => ZoomOutRequested?.Invoke();
|
||||
ZoomResetButton.Click += (o, args) => ZoomResetRequested?.Invoke();
|
||||
BrowserControlHost.GotKeyboardFocus += BrowserControlHost_GotKeyboardFocus;
|
||||
}
|
||||
|
||||
private void MenuButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MenuPopup.IsOpen = !MenuPopup.IsOpen;
|
||||
ZoomInButton.Focus();
|
||||
}
|
||||
|
||||
private void BrowserControlHost_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||
{
|
||||
var forward = !browserControlGetsFocusFromTaskbar;
|
||||
|
||||
// focus the first / last element on the page
|
||||
var javascript = @"
|
||||
if (typeof __SEB_focusElement === 'undefined') {
|
||||
__SEB_focusElement = function (forward) {
|
||||
if (!document.body) { return; }
|
||||
var items = [].map
|
||||
.call(document.body.querySelectorAll(['input', 'select', 'a[href]', 'textarea', 'button', '[tabindex]']), function(el, i) { return { el, i } })
|
||||
.filter(function(e) { return e.el.tabIndex >= 0 && !e.el.disabled && e.el.offsetParent; })
|
||||
.sort(function(a,b) { return a.el.tabIndex === b.el.tabIndex ? a.i - b.i : (a.el.tabIndex || 9E9) - (b.el.tabIndex || 9E9); })
|
||||
var item = items[forward ? 1 : items.length - 1];
|
||||
if (item && item.focus && typeof item.focus !== 'function')
|
||||
throw ('item.focus is not a function, ' + typeof item.focus)
|
||||
setTimeout(function () { item && item.focus && item.focus(); }, 20);
|
||||
}
|
||||
}";
|
||||
browserControl.ExecuteJavaScript(javascript, result =>
|
||||
{
|
||||
if (!result.Success)
|
||||
{
|
||||
logger.Warn($"Failed to initialize JavaScript: {result.Message}!");
|
||||
}
|
||||
});
|
||||
|
||||
browserControl.ExecuteJavaScript("__SEB_focusElement(" + forward.ToString().ToLower() + ")", result =>
|
||||
{
|
||||
if (!result.Success)
|
||||
{
|
||||
logger.Warn($"Failed to execute JavaScript: {result.Message}!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ApplySettings()
|
||||
{
|
||||
BackwardButton.IsEnabled = WindowSettings.AllowBackwardNavigation;
|
||||
BackwardButton.Visibility = WindowSettings.AllowBackwardNavigation ? Visibility.Visible : Visibility.Collapsed;
|
||||
DeveloperConsoleMenuItem.Visibility = WindowSettings.AllowDeveloperConsole ? Visibility.Visible : Visibility.Collapsed;
|
||||
FindMenuItem.Visibility = settings.AllowFind ? Visibility.Visible : Visibility.Collapsed;
|
||||
ForwardButton.IsEnabled = WindowSettings.AllowForwardNavigation;
|
||||
ForwardButton.Visibility = WindowSettings.AllowForwardNavigation ? Visibility.Visible : Visibility.Collapsed;
|
||||
HomeButton.IsEnabled = WindowSettings.ShowHomeButton;
|
||||
HomeButton.Visibility = WindowSettings.ShowHomeButton ? Visibility.Visible : Visibility.Collapsed;
|
||||
ReloadButton.IsEnabled = WindowSettings.AllowReloading;
|
||||
ReloadButton.Visibility = WindowSettings.ShowReloadButton ? Visibility.Visible : Visibility.Collapsed;
|
||||
Toolbar.Visibility = WindowSettings.ShowToolbar ? Visibility.Visible : Visibility.Collapsed;
|
||||
UrlTextBox.Visibility = WindowSettings.AllowAddressBar ? Visibility.Visible : Visibility.Hidden;
|
||||
ZoomMenuItem.Visibility = settings.AllowPageZoom ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
if (!WindowSettings.AllowAddressBar)
|
||||
{
|
||||
BackwardButton.Height = 35;
|
||||
ForwardButton.Height = 35;
|
||||
ReloadButton.Height = 35;
|
||||
UrlTextBox.Height = 20;
|
||||
DownloadsButton.Height = 35;
|
||||
MenuButton.Height = 35;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeBounds()
|
||||
{
|
||||
if (isMainWindow && WindowSettings.FullScreenMode)
|
||||
{
|
||||
Top = 0;
|
||||
Left = 0;
|
||||
Height = SystemParameters.WorkArea.Height;
|
||||
Width = SystemParameters.WorkArea.Width;
|
||||
ResizeMode = ResizeMode.NoResize;
|
||||
WindowStyle = WindowStyle.None;
|
||||
}
|
||||
else if (WindowSettings.RelativeHeight == 100 && WindowSettings.RelativeWidth == 100)
|
||||
{
|
||||
WindowState = WindowState.Maximized;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (WindowSettings.RelativeHeight > 0)
|
||||
{
|
||||
Height = SystemParameters.WorkArea.Height * WindowSettings.RelativeHeight.Value / 100;
|
||||
Top = (SystemParameters.WorkArea.Height / 2) - (Height / 2);
|
||||
}
|
||||
else if (WindowSettings.AbsoluteHeight > 0)
|
||||
{
|
||||
Height = this.TransformFromPhysical(0, WindowSettings.AbsoluteHeight.Value).Y;
|
||||
Top = (SystemParameters.WorkArea.Height / 2) - (Height / 2);
|
||||
}
|
||||
|
||||
if (WindowSettings.RelativeWidth > 0)
|
||||
{
|
||||
Width = SystemParameters.WorkArea.Width * WindowSettings.RelativeWidth.Value / 100;
|
||||
}
|
||||
else if (WindowSettings.AbsoluteWidth > 0)
|
||||
{
|
||||
Width = this.TransformFromPhysical(WindowSettings.AbsoluteWidth.Value, 0).X;
|
||||
}
|
||||
|
||||
if (Height > SystemParameters.WorkArea.Height)
|
||||
{
|
||||
Top = 0;
|
||||
Height = SystemParameters.WorkArea.Height;
|
||||
}
|
||||
|
||||
if (Width > SystemParameters.WorkArea.Width)
|
||||
{
|
||||
Left = 0;
|
||||
Width = SystemParameters.WorkArea.Width;
|
||||
}
|
||||
|
||||
switch (WindowSettings.Position)
|
||||
{
|
||||
case WindowPosition.Left:
|
||||
Left = 0;
|
||||
break;
|
||||
case WindowPosition.Center:
|
||||
Left = (SystemParameters.WorkArea.Width / 2) - (Width / 2);
|
||||
break;
|
||||
case WindowPosition.Right:
|
||||
Left = SystemParameters.WorkArea.Width - Width;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadIcons()
|
||||
{
|
||||
var backward = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/NavigateBack.xaml") };
|
||||
var forward = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/NavigateForward.xaml") };
|
||||
var home = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Home.xaml") };
|
||||
var menu = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Menu.xaml") };
|
||||
var reload = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Reload.xaml") };
|
||||
|
||||
BackwardButton.Content = IconResourceLoader.Load(backward);
|
||||
ForwardButton.Content = IconResourceLoader.Load(forward);
|
||||
HomeButton.Content = IconResourceLoader.Load(home);
|
||||
MenuButton.Content = IconResourceLoader.Load(menu);
|
||||
ReloadButton.Content = IconResourceLoader.Load(reload);
|
||||
}
|
||||
|
||||
private void LoadText()
|
||||
{
|
||||
DeveloperConsoleText.Text = text.Get(TextKey.BrowserWindow_DeveloperConsoleMenuItem);
|
||||
DeveloperConsoleButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_DeveloperConsoleMenuItem));
|
||||
FindCaseSensitiveCheckBox.Content = text.Get(TextKey.BrowserWindow_FindCaseSensitive);
|
||||
FindMenuText.Text = text.Get(TextKey.BrowserWindow_FindMenuItem);
|
||||
FindMenuButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_FindMenuItem));
|
||||
ZoomText.Text = text.Get(TextKey.BrowserWindow_ZoomMenuItem);
|
||||
ZoomInButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_ZoomMenuPlus));
|
||||
ZoomOutButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_ZoomMenuMinus));
|
||||
ReloadButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_ReloadButton));
|
||||
BackwardButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_BackwardButton));
|
||||
ForwardButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_ForwardButton));
|
||||
DownloadsButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_DownloadsButton));
|
||||
HomeButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_HomeButton));
|
||||
MenuButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_MenuButton));
|
||||
UrlTextBox.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text.Get(TextKey.BrowserWindow_UrlTextBox));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.CredentialsDialog" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
|
||||
mc:Ignorable="d" Background="Transparent" Height="750" Width="1000" FontSize="16" ResizeMode="NoResize" Topmost="True" AllowsTransparency="True" WindowStyle="None">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid Background="#66000000" FocusManager.FocusedElement="{Binding ElementName=Password}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" />
|
||||
<Grid Grid.Row="1" Background="White">
|
||||
<Grid Margin="25">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<fa:ImageAwesome Grid.Column="0" Name="PurposeIcon" Foreground="LightGray" Icon="Key" Margin="25" Rotation="90" Width="75" />
|
||||
<Grid Grid.Column="1" Margin="25,0,25,25">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" x:Name="Message" Margin="0,20" TextWrapping="WrapWithOverflow" VerticalAlignment="Bottom" />
|
||||
<StackPanel Grid.Row="1" Orientation="Vertical">
|
||||
<Label Name="UsernameLabel" Padding="0,5" Target="{Binding ElementName=Username}" />
|
||||
<TextBox Name="Username" Padding="12" VerticalContentAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" Orientation="Vertical">
|
||||
<Label Name="PasswordLabel" Padding="0,5" Target="{Binding ElementName=Password}" />
|
||||
<PasswordBox x:Name="Password" Padding="12" VerticalContentAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" Background="{StaticResource BackgroundBrush}">
|
||||
<WrapPanel Orientation="Horizontal" Margin="50,25" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button x:Name="ConfirmButton" Cursor="Hand" Margin="20,0" Padding="20,10" MinWidth="100" />
|
||||
<Button x:Name="CancelButton" Cursor="Hand" Padding="20,10" MinWidth="100" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using FontAwesome.WPF;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class CredentialsDialog : Window, ICredentialsDialog
|
||||
{
|
||||
private readonly IText text;
|
||||
|
||||
private WindowClosedEventHandler closed;
|
||||
private WindowClosingEventHandler closing;
|
||||
|
||||
event WindowClosedEventHandler IWindow.Closed
|
||||
{
|
||||
add { closed += value; }
|
||||
remove { closed -= value; }
|
||||
}
|
||||
|
||||
event WindowClosingEventHandler IWindow.Closing
|
||||
{
|
||||
add { closing += value; }
|
||||
remove { closing -= value; }
|
||||
}
|
||||
|
||||
internal CredentialsDialog(CredentialsDialogPurpose purpose, string message, string title, IText text)
|
||||
{
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeCredentialsDialog(purpose, message, title);
|
||||
}
|
||||
|
||||
public void BringToForeground()
|
||||
{
|
||||
Dispatcher.Invoke(Activate);
|
||||
}
|
||||
|
||||
public CredentialsDialogResult Show(IWindow parent = null)
|
||||
{
|
||||
return Dispatcher.Invoke(() =>
|
||||
{
|
||||
var result = new CredentialsDialogResult { Success = false };
|
||||
|
||||
if (parent is Window)
|
||||
{
|
||||
Owner = parent as Window;
|
||||
}
|
||||
|
||||
InitializeBounds();
|
||||
|
||||
if (ShowDialog() is true)
|
||||
{
|
||||
result.Password = Password.Password;
|
||||
result.Success = true;
|
||||
result.Username = Username.Text;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeBounds()
|
||||
{
|
||||
Left = 0;
|
||||
Top = 0;
|
||||
Height = SystemParameters.PrimaryScreenHeight;
|
||||
Width = SystemParameters.PrimaryScreenWidth;
|
||||
}
|
||||
|
||||
private void InitializeCredentialsDialog(CredentialsDialogPurpose purpose, string message, string title)
|
||||
{
|
||||
InitializeBounds();
|
||||
|
||||
if (purpose == CredentialsDialogPurpose.WirelessNetwork)
|
||||
{
|
||||
PurposeIcon.Icon = FontAwesomeIcon.Wifi;
|
||||
PurposeIcon.Rotation = 0;
|
||||
UsernameLabel.Content = text.Get(TextKey.CredentialsDialog_UsernameOptionalLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
PurposeIcon.Icon = FontAwesomeIcon.Key;
|
||||
PurposeIcon.Rotation = 90;
|
||||
UsernameLabel.Content = text.Get(TextKey.CredentialsDialog_UsernameLabel);
|
||||
}
|
||||
|
||||
PasswordLabel.Content = text.Get(TextKey.CredentialsDialog_PasswordLabel);
|
||||
Message.Text = message;
|
||||
Title = title;
|
||||
|
||||
Closed += (o, args) => closed?.Invoke();
|
||||
Closing += (o, args) => closing?.Invoke();
|
||||
Loaded += (o, args) => Activate();
|
||||
|
||||
CancelButton.Content = text.Get(TextKey.PasswordDialog_Cancel);
|
||||
CancelButton.Click += CancelButton_Click;
|
||||
|
||||
ConfirmButton.Content = text.Get(TextKey.PasswordDialog_Confirm);
|
||||
ConfirmButton.Click += ConfirmButton_Click;
|
||||
|
||||
Password.KeyDown += Password_KeyDown;
|
||||
|
||||
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Password_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(SystemParameters.WorkArea))
|
||||
{
|
||||
Dispatcher.InvokeAsync(InitializeBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.ExamSelectionDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Windows"
|
||||
mc:Ignorable="d" Background="Transparent" Height="750" Width="1000" FontSize="16" ResizeMode="NoResize" Topmost="True" AllowsTransparency="True" WindowStyle="None">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid Background="#66000000">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" />
|
||||
<Grid Grid.Row="1" Background="White">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<fa:ImageAwesome Grid.Column="0" Foreground="LightGray" Icon="PencilSquareOutline" Margin="50" Width="75" />
|
||||
<Grid Grid.Column="1" Margin="0,0,50,50">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" x:Name="Message" Margin="0,25" TextWrapping="WrapWithOverflow" VerticalAlignment="Bottom" />
|
||||
<ListBox Grid.Row="1" x:Name="ExamList" Cursor="Hand">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock FontWeight="Bold" Text="{Binding Name}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Margin="0,0,5,0" Text="{Binding Id}" />
|
||||
<TextBlock Margin="0,0,5,0" Text="-" />
|
||||
<TextBlock FontStyle="Italic" Text="{Binding LmsName}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<WrapPanel Orientation="Horizontal" Margin="50,25" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button x:Name="SelectButton" Cursor="Hand" Margin="10,0" Padding="10,5" MinWidth="75" IsEnabled="False" />
|
||||
<Button x:Name="CancelButton" Cursor="Hand" Padding="10,5" MinWidth="75" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
public partial class ExamSelectionDialog : Window, IExamSelectionDialog
|
||||
{
|
||||
private readonly IText text;
|
||||
|
||||
public ExamSelectionDialog(IEnumerable<Exam> exams, IText text)
|
||||
{
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeExamSelectionDialog(exams);
|
||||
}
|
||||
|
||||
public ExamSelectionDialogResult Show(IWindow parent = null)
|
||||
{
|
||||
return Dispatcher.Invoke(() =>
|
||||
{
|
||||
var result = new ExamSelectionDialogResult { Success = false };
|
||||
|
||||
if (parent is Window)
|
||||
{
|
||||
Owner = parent as Window;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
}
|
||||
|
||||
InitializeBounds();
|
||||
|
||||
if (ShowDialog() is true)
|
||||
{
|
||||
result.SelectedExam = ExamList.SelectedItem as Exam;
|
||||
result.Success = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeBounds()
|
||||
{
|
||||
Left = 0;
|
||||
Top = 0;
|
||||
Height = SystemParameters.PrimaryScreenHeight;
|
||||
Width = SystemParameters.PrimaryScreenWidth;
|
||||
}
|
||||
|
||||
private void InitializeExamSelectionDialog(IEnumerable<Exam> exams)
|
||||
{
|
||||
InitializeBounds();
|
||||
|
||||
Message.Text = text.Get(TextKey.ExamSelectionDialog_Message);
|
||||
Title = text.Get(TextKey.ExamSelectionDialog_Title);
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
|
||||
CancelButton.Content = text.Get(TextKey.ExamSelectionDialog_Cancel);
|
||||
CancelButton.Click += CancelButton_Click;
|
||||
|
||||
SelectButton.Content = text.Get(TextKey.ExamSelectionDialog_Select);
|
||||
SelectButton.Click += ConfirmButton_Click;
|
||||
|
||||
ExamList.ItemsSource = exams;
|
||||
ExamList.SelectionChanged += ExamList_SelectionChanged;
|
||||
|
||||
Loaded += (o, args) => Activate();
|
||||
|
||||
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ExamList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
SelectButton.IsEnabled = ExamList.SelectedItem != null;
|
||||
}
|
||||
|
||||
private void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(SystemParameters.WorkArea))
|
||||
{
|
||||
Dispatcher.InvokeAsync(InitializeBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.FileSystemDialog" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
|
||||
mc:Ignorable="d" Height="750" Width="750" FontSize="16" ResizeMode="NoResize" Topmost="True" WindowState="Maximized">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" Margin="25,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<fa:ImageAwesome Grid.Column="0" Name="OperationIcon" Foreground="LightGray" Height="35" Icon="FileOutline" />
|
||||
<TextBlock Grid.Column="1" Name="Message" Margin="10,0,0,0" TextWrapping="Wrap" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<TreeView Grid.Row="1" Name="FileSystem" Margin="10,0" TreeViewItem.Expanded="FileSystem_Expanded" />
|
||||
<TextBlock Grid.Row="2" Name="SelectedElement" Margin="10,5,10,5" Padding="0,8" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<Grid Grid.Row="3" Name="NewElement" Margin="10,0,10,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Name="NewElementLabel" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Column="1" Name="NewElementName" Margin="5,0,0,0" Padding="8" VerticalContentAlignment="Center" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<WrapPanel Orientation="Horizontal" Margin="25" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button Name="SelectButton" Cursor="Hand" IsEnabled="False" Margin="20,0" Padding="20,10" MinWidth="75" />
|
||||
<Button Name="CancelButton" Cursor="Hand" Padding="20,10" MinWidth="75" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using FontAwesome.WPF;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class FileSystemDialog : Window
|
||||
{
|
||||
private readonly FileSystemElement element;
|
||||
private readonly string initialPath;
|
||||
private readonly string message;
|
||||
private readonly FileSystemOperation operation;
|
||||
private readonly IWindow parent;
|
||||
private readonly ISystemInfo systemInfo;
|
||||
private readonly bool restrictNavigation;
|
||||
private readonly bool showElementPath;
|
||||
private readonly IText text;
|
||||
private readonly string title;
|
||||
|
||||
internal FileSystemDialog(
|
||||
FileSystemElement element,
|
||||
FileSystemOperation operation,
|
||||
ISystemInfo systemInfo,
|
||||
IText text,
|
||||
string initialPath = default,
|
||||
string message = default,
|
||||
string title = default,
|
||||
IWindow parent = default,
|
||||
bool restrictNavigation = false,
|
||||
bool showElementPath = true)
|
||||
{
|
||||
this.element = element;
|
||||
this.initialPath = initialPath;
|
||||
this.message = message;
|
||||
this.operation = operation;
|
||||
this.systemInfo = systemInfo;
|
||||
this.parent = parent;
|
||||
this.restrictNavigation = restrictNavigation;
|
||||
this.showElementPath = showElementPath;
|
||||
this.text = text;
|
||||
this.title = title;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeDialog();
|
||||
}
|
||||
|
||||
internal new FileSystemDialogResult Show()
|
||||
{
|
||||
var result = new FileSystemDialogResult();
|
||||
|
||||
if (parent is Window)
|
||||
{
|
||||
Owner = parent as Window;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
}
|
||||
|
||||
if (ShowDialog() == true)
|
||||
{
|
||||
result.FullPath = BuildFullPath();
|
||||
result.Success = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void FileSystem_Expanded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (e.Source is TreeViewItem item && item.Items.Count == 1 && !(item.Items[0] is TreeViewItem))
|
||||
{
|
||||
Load(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void FileSystem_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
if (e.NewValue is TreeViewItem item)
|
||||
{
|
||||
if (item.Tag is DirectoryInfo directory)
|
||||
{
|
||||
SelectButton.IsEnabled = element == FileSystemElement.Folder || operation == FileSystemOperation.Save;
|
||||
SelectedElement.Text = directory.FullName;
|
||||
SelectedElement.ToolTip = directory.FullName;
|
||||
}
|
||||
else if (item.Tag is FileInfo file)
|
||||
{
|
||||
SelectButton.IsEnabled = element == FileSystemElement.File;
|
||||
SelectedElement.Text = file.FullName;
|
||||
SelectedElement.ToolTip = file.FullName;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectButton.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NewElementName_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && IsValid())
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildFullPath()
|
||||
{
|
||||
var fullPath = SelectedElement.Text;
|
||||
|
||||
if (operation == FileSystemOperation.Save)
|
||||
{
|
||||
fullPath = Path.Combine(SelectedElement.Text, NewElementName.Text);
|
||||
|
||||
if (element == FileSystemElement.File)
|
||||
{
|
||||
var extension = Path.GetExtension(initialPath);
|
||||
|
||||
if (!fullPath.EndsWith(extension))
|
||||
{
|
||||
fullPath = $"{fullPath}{extension}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
var fullPath = BuildFullPath();
|
||||
var isValid = true;
|
||||
|
||||
if (element == FileSystemElement.File && operation == FileSystemOperation.Save && File.Exists(fullPath))
|
||||
{
|
||||
var message = text.Get(TextKey.FileSystemDialog_OverwriteWarning);
|
||||
var title = text.Get(TextKey.FileSystemDialog_OverwriteWarningTitle);
|
||||
var result = System.Windows.MessageBox.Show(this, message, title, MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||
|
||||
isValid = result == MessageBoxResult.Yes;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private void Load(TreeViewItem item)
|
||||
{
|
||||
item.Items.Clear();
|
||||
|
||||
if (item.Tag is DirectoryInfo directory)
|
||||
{
|
||||
item.Cursor = Cursors.Wait;
|
||||
item.BeginInit();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var subDirectory in directory.GetDirectories())
|
||||
{
|
||||
if (!subDirectory.Attributes.HasFlag(FileAttributes.Hidden) || initialPath?.Contains(subDirectory.FullName) == true)
|
||||
{
|
||||
item.Items.Add(CreateItem(subDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var file in directory.GetFiles())
|
||||
{
|
||||
if (!file.Attributes.HasFlag(FileAttributes.Hidden) || initialPath?.Contains(file.FullName) == true)
|
||||
{
|
||||
item.Items.Add(CreateItem(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
item.Items.Add(CreateErrorItem(e));
|
||||
}
|
||||
|
||||
item.EndInit();
|
||||
item.Cursor = Cursors.Hand;
|
||||
}
|
||||
}
|
||||
|
||||
private TreeViewItem CreateErrorItem(Exception e)
|
||||
{
|
||||
var item = new TreeViewItem();
|
||||
|
||||
item.Foreground = Brushes.Red;
|
||||
item.Header = $"{text.Get(TextKey.FileSystemDialog_LoadError)} {e.Message}";
|
||||
item.ToolTip = e.GetType() + Environment.NewLine + e.StackTrace;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private TreeViewItem CreateItem(DirectoryInfo directory)
|
||||
{
|
||||
var header = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(2) };
|
||||
var image = new Image
|
||||
{
|
||||
Height = 24,
|
||||
Source = IconLoader.LoadIconFor(directory)
|
||||
};
|
||||
var item = new TreeViewItem();
|
||||
var textBlock = new TextBlock { Margin = new Thickness(5, 0, 0, 0), Text = directory.Name, VerticalAlignment = VerticalAlignment.Center };
|
||||
|
||||
header.Children.Add(image);
|
||||
header.Children.Add(textBlock);
|
||||
|
||||
item.Cursor = Cursors.Hand;
|
||||
item.Header = header;
|
||||
item.Tag = directory;
|
||||
item.ToolTip = directory.FullName;
|
||||
item.Items.Add(text.Get(TextKey.FileSystemDialog_Loading));
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private TreeViewItem CreateItem(FileInfo file)
|
||||
{
|
||||
var header = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(2) };
|
||||
var image = new Image
|
||||
{
|
||||
Height = 24,
|
||||
Source = IconLoader.LoadIconFor(file)
|
||||
};
|
||||
var item = new TreeViewItem();
|
||||
var textBlock = new TextBlock { Margin = new Thickness(5, 0, 0, 0), Text = file.Name, VerticalAlignment = VerticalAlignment.Center };
|
||||
|
||||
header.Children.Add(image);
|
||||
header.Children.Add(textBlock);
|
||||
|
||||
item.Header = header;
|
||||
item.Tag = file;
|
||||
item.ToolTip = file.FullName;
|
||||
|
||||
if (element == FileSystemElement.File && operation == FileSystemOperation.Open)
|
||||
{
|
||||
item.Cursor = Cursors.Hand;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Cursor = Cursors.No;
|
||||
item.Focusable = false;
|
||||
textBlock.Foreground = Brushes.Gray;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private void InitializeDialog()
|
||||
{
|
||||
CancelButton.Click += CancelButton_Click;
|
||||
CancelButton.Content = text.Get(TextKey.FileSystemDialog_Cancel);
|
||||
FileSystem.SelectedItemChanged += FileSystem_SelectedItemChanged;
|
||||
NewElement.Visibility = operation == FileSystemOperation.Save ? Visibility.Visible : Visibility.Collapsed;
|
||||
NewElementLabel.Text = text.Get(TextKey.FileSystemDialog_SaveAs);
|
||||
NewElementName.KeyUp += NewElementName_KeyUp;
|
||||
OperationIcon.Icon = operation == FileSystemOperation.Save ? FontAwesomeIcon.Download : FontAwesomeIcon.Search;
|
||||
SelectButton.Click += SelectButton_Click;
|
||||
SelectButton.Content = text.Get(TextKey.FileSystemDialog_Select);
|
||||
SelectedElement.Visibility = showElementPath ? Visibility.Visible : Visibility.Hidden;
|
||||
|
||||
InitializeText();
|
||||
InitializeFileSystem();
|
||||
}
|
||||
|
||||
private void InitializeFileSystem()
|
||||
{
|
||||
if (restrictNavigation && !string.IsNullOrEmpty(initialPath))
|
||||
{
|
||||
InitializeRestricted();
|
||||
}
|
||||
else
|
||||
{
|
||||
InitializeUnrestricted();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeRestricted()
|
||||
{
|
||||
var root = Directory.Exists(initialPath) ? initialPath : Path.GetDirectoryName(initialPath);
|
||||
|
||||
if (Directory.Exists(root))
|
||||
{
|
||||
var directory = CreateItem(new DirectoryInfo(root));
|
||||
|
||||
FileSystem.Items.Add(directory);
|
||||
|
||||
directory.IsExpanded = true;
|
||||
directory.IsSelected = true;
|
||||
directory.BringIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeUnrestricted()
|
||||
{
|
||||
foreach (var drive in systemInfo.GetDrives())
|
||||
{
|
||||
FileSystem.Items.Add(CreateItem(drive.RootDirectory));
|
||||
}
|
||||
|
||||
if (FileSystem.HasItems && FileSystem.Items[0] is TreeViewItem item)
|
||||
{
|
||||
item.IsSelected = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(initialPath))
|
||||
{
|
||||
var root = Path.GetPathRoot(initialPath);
|
||||
var path = initialPath.Replace(root, "").Split(Path.DirectorySeparatorChar);
|
||||
var segments = new List<string> { root };
|
||||
|
||||
segments.AddRange(path);
|
||||
|
||||
SelectInitialPath(FileSystem.Items, segments);
|
||||
|
||||
if (element == FileSystemElement.File && operation == FileSystemOperation.Save)
|
||||
{
|
||||
NewElementName.Text = Path.GetFileName(initialPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectInitialPath(ItemCollection items, List<string> segments)
|
||||
{
|
||||
var segment = segments.FirstOrDefault();
|
||||
|
||||
if (segment != default)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item is TreeViewItem i && i.Tag is DirectoryInfo d && d.Name.Equals(segment))
|
||||
{
|
||||
i.IsExpanded = true;
|
||||
i.IsSelected = true;
|
||||
i.BringIntoView();
|
||||
|
||||
SelectInitialPath(i.Items, segments.Skip(1).ToList());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeText()
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
if (element == FileSystemElement.File)
|
||||
{
|
||||
if (operation == FileSystemOperation.Open)
|
||||
{
|
||||
Message.Text = text.Get(TextKey.FileSystemDialog_OpenFileMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Message.Text = text.Get(TextKey.FileSystemDialog_SaveFileMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (operation == FileSystemOperation.Open)
|
||||
{
|
||||
Message.Text = text.Get(TextKey.FileSystemDialog_OpenFolderMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Message.Text = text.Get(TextKey.FileSystemDialog_SaveFolderMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Message.Text = message;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(title))
|
||||
{
|
||||
Title = text.Get(TextKey.FileSystemDialog_Title);
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = title;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
SafeExamBrowser.UserInterface.Mobile/Windows/LockScreen.xaml
Normal file
34
SafeExamBrowser.UserInterface.Mobile/Windows/LockScreen.xaml
Normal file
@@ -0,0 +1,34 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.LockScreen" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
|
||||
mc:Ignorable="d" d:DesignWidth="1500" Background="Red" FontSize="16" ResizeMode="NoResize" Topmost="True" WindowState="Maximized" WindowStyle="None">
|
||||
<Grid FocusManager.FocusedElement="{Binding ElementName=Password}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="1" Orientation="Vertical">
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Margin="0,10">
|
||||
<Image Height="100" Margin="0,0,20,0" Source="../Images/SafeExamBrowser.ico" />
|
||||
<TextBlock Name="Heading" Foreground="White" FontSize="75" FontWeight="ExtraBold" TextAlignment="Center" Text="SEB LOCKED" />
|
||||
</StackPanel>
|
||||
<TextBlock Name="Message" Foreground="White" FontSize="24" FontWeight="DemiBold" Margin="0,10" Padding="5" TextWrapping="Wrap" />
|
||||
<StackPanel Name="Options" Margin="0,10" Orientation="Vertical" />
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Margin="0,10">
|
||||
<PasswordBox Name="Password" Margin="10,5" MinWidth="500" Padding="12" />
|
||||
<Button Name="Button" Cursor="Hand" Margin="10,5" MinWidth="125" Padding="12" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
214
SafeExamBrowser.UserInterface.Mobile/Windows/LockScreen.xaml.cs
Normal file
214
SafeExamBrowser.UserInterface.Mobile/Windows/LockScreen.xaml.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Settings.UserInterface;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
using Screen = System.Windows.Forms.Screen;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class LockScreen : Window, ILockScreen
|
||||
{
|
||||
private readonly AutoResetEvent autoResetEvent;
|
||||
private readonly LockScreenSettings settings;
|
||||
private readonly IText text;
|
||||
|
||||
private bool canceled;
|
||||
private IList<Window> windows;
|
||||
|
||||
event WindowClosedEventHandler IWindow.Closed
|
||||
{
|
||||
add { throw new NotImplementedException(); }
|
||||
remove { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
event WindowClosingEventHandler IWindow.Closing
|
||||
{
|
||||
add { throw new NotImplementedException(); }
|
||||
remove { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
internal LockScreen(string message, string title, LockScreenSettings settings, IText text, IEnumerable<LockScreenOption> options)
|
||||
{
|
||||
this.autoResetEvent = new AutoResetEvent(false);
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeLockScreen(message, title, options);
|
||||
}
|
||||
|
||||
public void BringToForeground()
|
||||
{
|
||||
Dispatcher.Invoke(Activate);
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
canceled = true;
|
||||
autoResetEvent.Set();
|
||||
}
|
||||
|
||||
public new void Close()
|
||||
{
|
||||
Dispatcher.Invoke(CloseAll);
|
||||
}
|
||||
|
||||
public void InitializeBounds()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
foreach (var window in windows)
|
||||
{
|
||||
window.Topmost = false;
|
||||
window.WindowState = WindowState.Normal;
|
||||
window.Activate();
|
||||
window.Topmost = true;
|
||||
window.WindowState = WindowState.Maximized;
|
||||
}
|
||||
|
||||
Topmost = false;
|
||||
WindowState = WindowState.Normal;
|
||||
Activate();
|
||||
Topmost = true;
|
||||
WindowState = WindowState.Maximized;
|
||||
});
|
||||
}
|
||||
|
||||
public new void Show()
|
||||
{
|
||||
Dispatcher.Invoke(ShowAll);
|
||||
}
|
||||
|
||||
public LockScreenResult WaitForResult()
|
||||
{
|
||||
var result = new LockScreenResult();
|
||||
|
||||
autoResetEvent.WaitOne();
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
result.Password = Password.Password;
|
||||
|
||||
foreach (var child in Options.Children)
|
||||
{
|
||||
if (child is RadioButton option && option.IsChecked == true)
|
||||
{
|
||||
result.OptionId = option.Tag as Guid?;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
result.Canceled = canceled;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void InitializeLockScreen(string message, string title, IEnumerable<LockScreenOption> options)
|
||||
{
|
||||
windows = new List<Window>();
|
||||
|
||||
Button.Content = text.Get(TextKey.LockScreen_UnlockButton);
|
||||
Button.Click += Button_Click;
|
||||
Heading.Text = title;
|
||||
Loaded += (o, args) => Activate();
|
||||
Message.Text = message;
|
||||
Password.KeyDown += Password_KeyDown;
|
||||
|
||||
if (Parser.TryParseBrush(settings.BackgroundColor, out var brush))
|
||||
{
|
||||
Background = brush;
|
||||
}
|
||||
|
||||
foreach (var option in options)
|
||||
{
|
||||
Options.Children.Add(new RadioButton
|
||||
{
|
||||
Content = new TextBlock { Text = option.Text, TextWrapping = TextWrapping.Wrap },
|
||||
Cursor = Cursors.Hand,
|
||||
FontSize = Message.FontSize,
|
||||
Foreground = Message.Foreground,
|
||||
IsChecked = options.First() == option,
|
||||
Margin = new Thickness(5, options.First() == option ? 5 : 10, 5, options.Last() == option ? 5 : 10),
|
||||
Tag = option.Id,
|
||||
VerticalContentAlignment = VerticalAlignment.Center
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseAll()
|
||||
{
|
||||
foreach (var window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
|
||||
base.Close();
|
||||
}
|
||||
|
||||
private void ShowAll()
|
||||
{
|
||||
foreach (var screen in Screen.AllScreens)
|
||||
{
|
||||
if (!screen.Primary)
|
||||
{
|
||||
ShowLockWindowOn(screen);
|
||||
}
|
||||
}
|
||||
|
||||
base.Show();
|
||||
}
|
||||
|
||||
private void ShowLockWindowOn(Screen screen)
|
||||
{
|
||||
var window = new Window();
|
||||
var position = this.TransformFromPhysical(screen.Bounds.X, screen.Bounds.Y);
|
||||
var size = this.TransformFromPhysical(screen.Bounds.Width, screen.Bounds.Height);
|
||||
|
||||
window.Background = Brushes.Red;
|
||||
window.Topmost = true;
|
||||
window.Left = position.X;
|
||||
window.Top = position.Y;
|
||||
window.Width = size.X;
|
||||
window.Height = size.Y;
|
||||
window.ResizeMode = ResizeMode.NoResize;
|
||||
window.WindowStyle = WindowStyle.None;
|
||||
|
||||
window.Show();
|
||||
windows.Add(window);
|
||||
|
||||
// The window can only be maximized after it was shown on its screen, otherwise it is rendered on the primary screen!
|
||||
window.WindowState = WindowState.Maximized;
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
autoResetEvent.Set();
|
||||
}
|
||||
|
||||
private void Password_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
autoResetEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
SafeExamBrowser.UserInterface.Mobile/Windows/LogWindow.xaml
Normal file
30
SafeExamBrowser.UserInterface.Mobile/Windows/LogWindow.xaml
Normal file
@@ -0,0 +1,30 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.LogWindow" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
|
||||
mc:Ignorable="d" Title="{Binding Path=WindowTitle}" Background="Black" Foreground="White" Height="750" Width="1000" MinHeight="350"
|
||||
MinWidth="350" Topmost="True" WindowState="Maximized" WindowStartupLocation="CenterScreen" Icon="../Images/LogNotification.ico">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Background="White" Orientation="Horizontal">
|
||||
<CheckBox Cursor="Hand" IsChecked="{Binding AutoScroll}" Margin="5" VerticalContentAlignment="Center" Content="{Binding AutoScrollTitle}" />
|
||||
<CheckBox Cursor="Hand" IsChecked="{Binding Path=Topmost, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Margin="5" VerticalContentAlignment="Center" Content="{Binding TopmostTitle}" />
|
||||
</StackPanel>
|
||||
<ScrollViewer x:Name="ScrollViewer" Grid.Row="1" HorizontalScrollBarVisibility="Auto" Padding="5,5,5,0" PanningMode="Both"
|
||||
VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
|
||||
<TextBlock x:Name="LogContent" FontFamily="Consolas" Foreground="White" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
111
SafeExamBrowser.UserInterface.Mobile/Windows/LogWindow.xaml.cs
Normal file
111
SafeExamBrowser.UserInterface.Mobile/Windows/LogWindow.xaml.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
|
||||
using SafeExamBrowser.UserInterface.Mobile.ViewModels;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class LogWindow : Window, IWindow
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly LogViewModel model;
|
||||
|
||||
private WindowClosedEventHandler closed;
|
||||
private WindowClosingEventHandler closing;
|
||||
|
||||
event WindowClosedEventHandler IWindow.Closed
|
||||
{
|
||||
add { closed += value; }
|
||||
remove { closed -= value; }
|
||||
}
|
||||
|
||||
event WindowClosingEventHandler IWindow.Closing
|
||||
{
|
||||
add { closing += value; }
|
||||
remove { closing -= value; }
|
||||
}
|
||||
|
||||
internal LogWindow(ILogger logger, IText text)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.logger = logger;
|
||||
this.model = new LogViewModel(text, ScrollViewer, LogContent);
|
||||
|
||||
InitializeLogWindow();
|
||||
}
|
||||
|
||||
public void BringToForeground()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (WindowState == WindowState.Minimized)
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
Activate();
|
||||
});
|
||||
}
|
||||
|
||||
public new void Close()
|
||||
{
|
||||
Dispatcher.Invoke(base.Close);
|
||||
}
|
||||
|
||||
public new void Hide()
|
||||
{
|
||||
Dispatcher.Invoke(base.Hide);
|
||||
}
|
||||
|
||||
public new void Show()
|
||||
{
|
||||
Dispatcher.Invoke(base.Show);
|
||||
}
|
||||
|
||||
private void InitializeLogWindow()
|
||||
{
|
||||
DataContext = model;
|
||||
Closed += (o, args) => closed?.Invoke();
|
||||
Closing += LogWindow_Closing;
|
||||
Loaded += LogWindow_Loaded;
|
||||
}
|
||||
|
||||
private void LogWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Cursor = Cursors.Wait;
|
||||
|
||||
var log = logger.GetLog();
|
||||
|
||||
foreach (var content in log)
|
||||
{
|
||||
model.Notify(content);
|
||||
}
|
||||
|
||||
logger.Subscribe(model);
|
||||
logger.Debug("Opened log window.");
|
||||
|
||||
Cursor = Cursors.Arrow;
|
||||
}
|
||||
|
||||
private void LogWindow_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
logger.Unsubscribe(model);
|
||||
logger.Debug("Closed log window.");
|
||||
|
||||
closing?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.MessageBoxDialog" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
|
||||
mc:Ignorable="d" Background="Transparent" Height="750" Width="1000" FontSize="16" ResizeMode="NoResize" Topmost="True" AllowsTransparency="True" WindowStyle="None">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid Background="#66000000">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" />
|
||||
<Grid Grid.Row="1" Background="White">
|
||||
<Grid Margin="25">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentControl Grid.Column="0" x:Name="Image" Margin="25" Width="75" />
|
||||
<TextBlock Grid.Column="1" x:Name="Message" Margin="12,20" TextWrapping="WrapWithOverflow" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" Background="{StaticResource BackgroundBrush}">
|
||||
<WrapPanel Orientation="Horizontal" Margin="50,25" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button x:Name="OkButton" Cursor="Hand" Padding="20,10" MinWidth="100" />
|
||||
<Button x:Name="CancelButton" Cursor="Hand" Margin="20,0,0,0" Padding="20,10" MinWidth="100" />
|
||||
<Button x:Name="YesButton" Cursor="Hand" Margin="0,0,20,0" Padding="20,10" MinWidth="100" />
|
||||
<Button x:Name="NoButton" Cursor="Hand" Padding="20,10" MinWidth="100" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media.Imaging;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
using MessageBoxResult = SafeExamBrowser.UserInterface.Contracts.MessageBox.MessageBoxResult;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class MessageBoxDialog : Window
|
||||
{
|
||||
private readonly IText text;
|
||||
|
||||
public MessageBoxDialog(IText text)
|
||||
{
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeMessageBox();
|
||||
}
|
||||
|
||||
internal MessageBoxResult Show(string message, string title, MessageBoxAction action, MessageBoxIcon icon, Window parent = null)
|
||||
{
|
||||
Message.Text = message;
|
||||
Title = title;
|
||||
|
||||
if (parent is Window)
|
||||
{
|
||||
Owner = parent as Window;
|
||||
}
|
||||
|
||||
InitializeBounds();
|
||||
InitializeAction(action);
|
||||
InitializeIcon(icon);
|
||||
|
||||
ShowDialog();
|
||||
|
||||
return ResultFor(action);
|
||||
}
|
||||
|
||||
private void InitializeAction(MessageBoxAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case MessageBoxAction.Ok:
|
||||
CancelButton.Visibility = Visibility.Collapsed;
|
||||
OkButton.Visibility = Visibility.Visible;
|
||||
OkButton.Focus();
|
||||
YesButton.Visibility = Visibility.Collapsed;
|
||||
NoButton.Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
case MessageBoxAction.OkCancel:
|
||||
CancelButton.Visibility = Visibility.Visible;
|
||||
CancelButton.Focus();
|
||||
OkButton.Visibility = Visibility.Visible;
|
||||
YesButton.Visibility = Visibility.Collapsed;
|
||||
NoButton.Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
case MessageBoxAction.YesNo:
|
||||
CancelButton.Visibility = Visibility.Collapsed;
|
||||
OkButton.Visibility = Visibility.Collapsed;
|
||||
YesButton.Visibility = Visibility.Visible;
|
||||
NoButton.Visibility = Visibility.Visible;
|
||||
NoButton.Focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeBounds()
|
||||
{
|
||||
Left = 0;
|
||||
Top = 0;
|
||||
Height = SystemParameters.PrimaryScreenHeight;
|
||||
Width = SystemParameters.PrimaryScreenWidth;
|
||||
}
|
||||
|
||||
private void InitializeIcon(MessageBoxIcon icon)
|
||||
{
|
||||
var handle = default(IntPtr);
|
||||
|
||||
switch (icon)
|
||||
{
|
||||
case MessageBoxIcon.Error:
|
||||
handle = SystemIcons.Error.Handle;
|
||||
break;
|
||||
case MessageBoxIcon.Information:
|
||||
handle = SystemIcons.Information.Handle;
|
||||
break;
|
||||
case MessageBoxIcon.Question:
|
||||
handle = SystemIcons.Question.Handle;
|
||||
break;
|
||||
case MessageBoxIcon.Warning:
|
||||
handle = SystemIcons.Warning.Handle;
|
||||
break;
|
||||
}
|
||||
|
||||
if (handle != default(IntPtr))
|
||||
{
|
||||
Image.Content = new System.Windows.Controls.Image
|
||||
{
|
||||
Source = Imaging.CreateBitmapSourceFromHIcon(handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeMessageBox()
|
||||
{
|
||||
InitializeBounds();
|
||||
|
||||
CancelButton.Content = text.Get(TextKey.MessageBox_CancelButton);
|
||||
CancelButton.Click += CancelButton_Click;
|
||||
|
||||
NoButton.Content = text.Get(TextKey.MessageBox_NoButton);
|
||||
NoButton.Click += NoButton_Click;
|
||||
|
||||
OkButton.Content = text.Get(TextKey.MessageBox_OkButton);
|
||||
OkButton.Click += OkButton_Click;
|
||||
|
||||
YesButton.Content = text.Get(TextKey.MessageBox_YesButton);
|
||||
YesButton.Click += YesButton_Click;
|
||||
|
||||
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
|
||||
}
|
||||
|
||||
private MessageBoxResult ResultFor(MessageBoxAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case MessageBoxAction.Ok:
|
||||
return DialogResult == true ? MessageBoxResult.Ok : MessageBoxResult.None;
|
||||
case MessageBoxAction.OkCancel:
|
||||
return DialogResult == true ? MessageBoxResult.Ok : MessageBoxResult.Cancel;
|
||||
case MessageBoxAction.YesNo:
|
||||
return DialogResult == true ? MessageBoxResult.Yes : MessageBoxResult.No;
|
||||
default:
|
||||
return MessageBoxResult.None;
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void NoButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void YesButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(SystemParameters.WorkArea))
|
||||
{
|
||||
Dispatcher.InvokeAsync(InitializeBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.PasswordDialog" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
|
||||
mc:Ignorable="d" Background="Transparent" Height="750" Width="1000" FontSize="16" ResizeMode="NoResize" Topmost="True" AllowsTransparency="True" WindowStyle="None">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid Background="#66000000" FocusManager.FocusedElement="{Binding ElementName=Password}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" />
|
||||
<Grid Grid.Row="1" Background="White">
|
||||
<Grid Margin="25">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<fa:ImageAwesome Grid.Column="0" Foreground="LightGray" Icon="Key" Margin="25" Rotation="90" Width="75" />
|
||||
<Grid Grid.Column="1" Margin="25,0,25,25">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" x:Name="Message" Margin="0,20" TextWrapping="WrapWithOverflow" VerticalAlignment="Bottom" />
|
||||
<PasswordBox Grid.Row="1" x:Name="Password" Padding="12" VerticalContentAlignment="Center" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" Background="{StaticResource BackgroundBrush}">
|
||||
<WrapPanel Orientation="Horizontal" Margin="50,25" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button x:Name="ConfirmButton" Cursor="Hand" Margin="20,0" Padding="20,10" MinWidth="100" />
|
||||
<Button x:Name="CancelButton" Cursor="Hand" Padding="20,10" MinWidth="100" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class PasswordDialog : Window, IPasswordDialog
|
||||
{
|
||||
private readonly IText text;
|
||||
|
||||
private WindowClosedEventHandler closed;
|
||||
private WindowClosingEventHandler closing;
|
||||
|
||||
event WindowClosedEventHandler IWindow.Closed
|
||||
{
|
||||
add { closed += value; }
|
||||
remove { closed -= value; }
|
||||
}
|
||||
|
||||
event WindowClosingEventHandler IWindow.Closing
|
||||
{
|
||||
add { closing += value; }
|
||||
remove { closing -= value; }
|
||||
}
|
||||
|
||||
internal PasswordDialog(string message, string title, IText text)
|
||||
{
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializePasswordDialog(message, title);
|
||||
}
|
||||
|
||||
public void BringToForeground()
|
||||
{
|
||||
Dispatcher.Invoke(Activate);
|
||||
}
|
||||
|
||||
public PasswordDialogResult Show(IWindow parent = null)
|
||||
{
|
||||
return Dispatcher.Invoke(() =>
|
||||
{
|
||||
var result = new PasswordDialogResult { Success = false };
|
||||
|
||||
if (parent is Window)
|
||||
{
|
||||
Owner = parent as Window;
|
||||
}
|
||||
|
||||
InitializeBounds();
|
||||
|
||||
if (ShowDialog() is true)
|
||||
{
|
||||
result.Password = Password.Password;
|
||||
result.Success = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeBounds()
|
||||
{
|
||||
Left = 0;
|
||||
Top = 0;
|
||||
Height = SystemParameters.PrimaryScreenHeight;
|
||||
Width = SystemParameters.PrimaryScreenWidth;
|
||||
}
|
||||
|
||||
private void InitializePasswordDialog(string message, string title)
|
||||
{
|
||||
InitializeBounds();
|
||||
|
||||
Message.Text = message;
|
||||
Title = title;
|
||||
|
||||
Closed += (o, args) => closed?.Invoke();
|
||||
Closing += (o, args) => closing?.Invoke();
|
||||
Loaded += (o, args) => Activate();
|
||||
|
||||
CancelButton.Content = text.Get(TextKey.PasswordDialog_Cancel);
|
||||
CancelButton.Click += CancelButton_Click;
|
||||
|
||||
ConfirmButton.Content = text.Get(TextKey.PasswordDialog_Confirm);
|
||||
ConfirmButton.Click += ConfirmButton_Click;
|
||||
|
||||
Password.KeyDown += Password_KeyDown;
|
||||
|
||||
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Password_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(SystemParameters.WorkArea))
|
||||
{
|
||||
Dispatcher.InvokeAsync(InitializeBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.ProctoringFinalizationDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Windows"
|
||||
mc:Ignorable="d" Background="Transparent" Height="750" Width="1000" FontSize="16" ResizeMode="NoResize" Topmost="True" AllowsTransparency="True" WindowStyle="None">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid Background="#66000000">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" />
|
||||
<Grid Grid.Row="1" Background="White">
|
||||
<Grid Name="ProgressPanel" Margin="50">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Name="Info" TextWrapping="WrapWithOverflow" VerticalAlignment="Bottom" />
|
||||
<Grid Grid.Row="1">
|
||||
<ProgressBar Name="Progress" Height="35" Margin="0,25" />
|
||||
<TextBlock Name="Percentage" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="2" Name="Status" FontStyle="Italic" TextWrapping="WrapWithOverflow" VerticalAlignment="Top" />
|
||||
</Grid>
|
||||
<Grid Name="FailurePanel" Margin="50">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<fa:ImageAwesome Grid.Column="0" Foreground="LightGray" Icon="Warning" Margin="10,0,50,0" Width="50" />
|
||||
<WrapPanel Grid.Column="1" Orientation="Vertical" VerticalAlignment="Center">
|
||||
<TextBlock Name="Message" TextWrapping="WrapWithOverflow" />
|
||||
<TextBlock Name="CachePath" FontFamily="Courier New" Margin="0,25,0,0" TextWrapping="WrapWithOverflow" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" Name="ButtonPanel" Background="{StaticResource BackgroundBrush}">
|
||||
<WrapPanel Orientation="Horizontal" Margin="50,25" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button Name="Button" Cursor="Hand" Padding="10,5" MinWidth="75" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Proctoring;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
public partial class ProctoringFinalizationDialog : Window, IProctoringFinalizationDialog
|
||||
{
|
||||
private readonly IText text;
|
||||
|
||||
public ProctoringFinalizationDialog(IText text)
|
||||
{
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeDialog();
|
||||
}
|
||||
|
||||
public new void Show()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
InitializeBounds();
|
||||
ShowDialog();
|
||||
});
|
||||
}
|
||||
|
||||
public void Update(RemainingWorkUpdatedEventArgs status)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (status.HasFailed)
|
||||
{
|
||||
ShowFailure(status);
|
||||
}
|
||||
else if (status.IsFinished)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowProgress(status);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeBounds()
|
||||
{
|
||||
Left = 0;
|
||||
Top = 0;
|
||||
Height = SystemParameters.PrimaryScreenHeight;
|
||||
Width = SystemParameters.PrimaryScreenWidth;
|
||||
}
|
||||
|
||||
private void InitializeDialog()
|
||||
{
|
||||
Button.Click += (o, args) => Close();
|
||||
Button.Content = text.Get(TextKey.ProctoringFinalizationDialog_Confirm);
|
||||
Title = text.Get(TextKey.ProctoringFinalizationDialog_Title);
|
||||
|
||||
InitializeBounds();
|
||||
|
||||
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
|
||||
}
|
||||
|
||||
private void ShowFailure(RemainingWorkUpdatedEventArgs status)
|
||||
{
|
||||
ButtonPanel.Visibility = Visibility.Visible;
|
||||
|
||||
// TODO: Revert once cache handling has been specified and changed!
|
||||
CachePath.Text = status.CachePath ?? "-";
|
||||
CachePath.Visibility = Visibility.Collapsed;
|
||||
|
||||
Cursor = Cursors.Arrow;
|
||||
FailurePanel.Visibility = Visibility.Visible;
|
||||
Message.Text = text.Get(TextKey.ProctoringFinalizationDialog_FailureMessage);
|
||||
ProgressPanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ShowProgress(RemainingWorkUpdatedEventArgs status)
|
||||
{
|
||||
ButtonPanel.Visibility = Visibility.Collapsed;
|
||||
Cursor = Cursors.Wait;
|
||||
FailurePanel.Visibility = Visibility.Collapsed;
|
||||
Info.Text = text.Get(TextKey.ProctoringFinalizationDialog_InfoMessage);
|
||||
ProgressPanel.Visibility = Visibility.Visible;
|
||||
|
||||
if (status.IsWaiting)
|
||||
{
|
||||
var count = $"{status.Total - status.Progress}";
|
||||
var time = $"{status.Resume.ToLongTimeString()}";
|
||||
|
||||
Percentage.Text = "";
|
||||
Progress.IsIndeterminate = true;
|
||||
Status.Text = text.Get(TextKey.ProctoringFinalizationDialog_StatusWaiting).Replace("%%_COUNT_%%", count).Replace("%%_TIME_%%", time);
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = $"{status.Progress}";
|
||||
var total = $"{status.Total}";
|
||||
|
||||
Percentage.Text = $"{status.Progress / (double) (status.Total > 0 ? status.Total : 1) * 100:N0}%";
|
||||
Progress.IsIndeterminate = false;
|
||||
Progress.Maximum = status.Total;
|
||||
Progress.Value = status.Progress;
|
||||
|
||||
if (status.Next.HasValue)
|
||||
{
|
||||
Status.Text = text.Get(TextKey.ProctoringFinalizationDialog_StatusAndTime).Replace("%%_TIME_%%", $"{status.Next.Value.ToLongTimeString()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Status.Text = text.Get(TextKey.ProctoringFinalizationDialog_Status);
|
||||
}
|
||||
|
||||
Status.Text = Status.Text.Replace("%%_COUNT_%%", count).Replace("%%_TOTAL_%%", total);
|
||||
}
|
||||
}
|
||||
|
||||
private void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(SystemParameters.WorkArea))
|
||||
{
|
||||
Dispatcher.InvokeAsync(InitializeBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.ProctoringWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Windows"
|
||||
mc:Ignorable="d" Height="550" Width="650" MinHeight="250" MinWidth="250" Topmost="True" WindowStyle="ToolWindow">
|
||||
</Window>
|
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Proctoring;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
public partial class ProctoringWindow : Window, IProctoringWindow
|
||||
{
|
||||
private WindowClosedEventHandler closed;
|
||||
private WindowClosingEventHandler closing;
|
||||
|
||||
event WindowClosedEventHandler IWindow.Closed
|
||||
{
|
||||
add { closed += value; }
|
||||
remove { closed -= value; }
|
||||
}
|
||||
|
||||
event WindowClosingEventHandler IWindow.Closing
|
||||
{
|
||||
add { closing += value; }
|
||||
remove { closing -= value; }
|
||||
}
|
||||
|
||||
public ProctoringWindow(IProctoringControl control)
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeWindow(control);
|
||||
}
|
||||
|
||||
public void BringToForeground()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (WindowState == WindowState.Minimized)
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
Activate();
|
||||
});
|
||||
}
|
||||
|
||||
public new void Close()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
Closing -= ProctoringWindow_Closing;
|
||||
closing?.Invoke();
|
||||
base.Close();
|
||||
});
|
||||
}
|
||||
|
||||
public new void Hide()
|
||||
{
|
||||
Dispatcher.Invoke(base.Hide);
|
||||
}
|
||||
|
||||
public void HideWithDelay()
|
||||
{
|
||||
Dispatcher.Invoke(() => this.MoveToBackground());
|
||||
Task.Delay(15000).ContinueWith(_ => Hide());
|
||||
}
|
||||
|
||||
public void SetTitle(string title)
|
||||
{
|
||||
Dispatcher.Invoke(() => Title = title ?? "");
|
||||
}
|
||||
|
||||
public new void Show()
|
||||
{
|
||||
Dispatcher.Invoke(base.Show);
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Visibility == Visibility.Visible)
|
||||
{
|
||||
base.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeWindow(IProctoringControl control)
|
||||
{
|
||||
if (control is UIElement element)
|
||||
{
|
||||
Content = element;
|
||||
control.FullScreenChanged += Control_FullScreenChanged;
|
||||
}
|
||||
|
||||
Closed += (o, args) => closed?.Invoke();
|
||||
Closing += ProctoringWindow_Closing;
|
||||
Loaded += ProctoringWindow_Loaded;
|
||||
Top = SystemParameters.WorkArea.Height - Height - 15;
|
||||
Left = SystemParameters.WorkArea.Width - Width - 20;
|
||||
}
|
||||
|
||||
private void Control_FullScreenChanged(bool fullScreen)
|
||||
{
|
||||
if (fullScreen)
|
||||
{
|
||||
WindowState = WindowState.Maximized;
|
||||
WindowStyle = WindowStyle.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
WindowStyle = WindowStyle.ToolWindow;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProctoringWindow_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
|
||||
private void ProctoringWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.HideCloseButton();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.RuntimeWindow" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
|
||||
xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||
mc:Ignorable="d" Background="White" Foreground="Black" Height="500" Width="850" WindowStyle="None" WindowStartupLocation="CenterScreen"
|
||||
Icon="../Images/SafeExamBrowser.ico" ResizeMode="NoResize" Title="Safe Exam Browser" Topmost="True">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Border BorderBrush="DodgerBlue" BorderThickness="1">
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="3*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="400" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Column="0" Grid.ColumnSpan="2" Margin="-25,0,0,0" Source="pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/SplashScreen.png" />
|
||||
<TextBlock x:Name="InfoTextBlock" Grid.Column="1" FontSize="10" Foreground="DimGray" Margin="10,70,225,10" TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
<ProgressBar x:Name="ProgressBar" Grid.Row="1" Background="WhiteSmoke" BorderThickness="0" Foreground="DodgerBlue"
|
||||
IsIndeterminate="{Binding IsIndeterminate}" Maximum="{Binding MaxProgress}" Value="{Binding CurrentProgress}"
|
||||
Visibility="{Binding ProgressBarVisibility}" />
|
||||
<TextBlock x:Name="StatusTextBlock" Grid.Row="1" FontSize="12" FontWeight="Bold" HorizontalAlignment="Center"
|
||||
Text="{Binding Status}" VerticalAlignment="Center" />
|
||||
<Border Grid.Row="2" BorderBrush="DodgerBlue" BorderThickness="0,0.5,0,0">
|
||||
<ScrollViewer x:Name="LogScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Margin="0,10,0,0"
|
||||
Template="{StaticResource SmallBarScrollViewer}" Visibility="Collapsed">
|
||||
<TextBlock x:Name="LogTextBlock" Background="Transparent" FontFamily="Consolas" FontSize="10" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
|
||||
using SafeExamBrowser.UserInterface.Mobile.ViewModels;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class RuntimeWindow : Window, IRuntimeWindow
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly IText text;
|
||||
|
||||
private bool allowClose;
|
||||
private RuntimeWindowViewModel model;
|
||||
|
||||
private WindowClosedEventHandler closed;
|
||||
private WindowClosingEventHandler closing;
|
||||
|
||||
public bool ShowLog
|
||||
{
|
||||
set => Dispatcher.Invoke(() => LogScrollViewer.Visibility = value ? Visibility.Visible : Visibility.Collapsed);
|
||||
}
|
||||
|
||||
public bool ShowProgressBar
|
||||
{
|
||||
set => Dispatcher.Invoke(() => model.ProgressBarVisibility = value ? Visibility.Visible : Visibility.Hidden);
|
||||
}
|
||||
|
||||
public bool TopMost
|
||||
{
|
||||
set => Dispatcher.Invoke(() => Topmost = value);
|
||||
}
|
||||
|
||||
event WindowClosedEventHandler IWindow.Closed
|
||||
{
|
||||
add { closed += value; }
|
||||
remove { closed -= value; }
|
||||
}
|
||||
|
||||
event WindowClosingEventHandler IWindow.Closing
|
||||
{
|
||||
add { closing += value; }
|
||||
remove { closing -= value; }
|
||||
}
|
||||
|
||||
internal RuntimeWindow(AppConfig appConfig, IText text)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeRuntimeWindow();
|
||||
}
|
||||
|
||||
public void BringToForeground()
|
||||
{
|
||||
Dispatcher.Invoke(Activate);
|
||||
}
|
||||
|
||||
public new void Close()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
allowClose = true;
|
||||
model.BusyIndication = false;
|
||||
closing?.Invoke();
|
||||
|
||||
base.Close();
|
||||
});
|
||||
}
|
||||
|
||||
public new void Hide()
|
||||
{
|
||||
Dispatcher.Invoke(base.Hide);
|
||||
}
|
||||
|
||||
public void Notify(ILogContent content)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
model.Notify(content);
|
||||
LogScrollViewer.ScrollToEnd();
|
||||
});
|
||||
}
|
||||
|
||||
public void Progress()
|
||||
{
|
||||
model.CurrentProgress += 1;
|
||||
}
|
||||
|
||||
public void Regress()
|
||||
{
|
||||
model.CurrentProgress -= 1;
|
||||
}
|
||||
|
||||
public void SetIndeterminate()
|
||||
{
|
||||
model.IsIndeterminate = true;
|
||||
}
|
||||
|
||||
public void SetMaxValue(int max)
|
||||
{
|
||||
model.IsIndeterminate = false;
|
||||
model.MaxProgress = max;
|
||||
}
|
||||
|
||||
public void SetValue(int value)
|
||||
{
|
||||
model.CurrentProgress = value;
|
||||
}
|
||||
|
||||
public void UpdateStatus(TextKey key, bool busyIndication = false)
|
||||
{
|
||||
model.Status = text.Get(key);
|
||||
model.BusyIndication = busyIndication;
|
||||
}
|
||||
|
||||
public new void Show()
|
||||
{
|
||||
Dispatcher.Invoke(base.Show);
|
||||
}
|
||||
|
||||
private void InitializeRuntimeWindow()
|
||||
{
|
||||
Title = $"{appConfig.ProgramTitle} - Version {appConfig.ProgramInformationalVersion}";
|
||||
|
||||
InfoTextBlock.Inlines.Add(new Run($"Version {appConfig.ProgramInformationalVersion}") { FontSize = 12 });
|
||||
InfoTextBlock.Inlines.Add(new LineBreak());
|
||||
InfoTextBlock.Inlines.Add(new Run($"Build {appConfig.ProgramBuildVersion}") { FontSize = 8, Foreground = Brushes.Gray });
|
||||
InfoTextBlock.Inlines.Add(new LineBreak());
|
||||
InfoTextBlock.Inlines.Add(new LineBreak());
|
||||
InfoTextBlock.Inlines.Add(new Run(appConfig.ProgramCopyright) { FontSize = 10, Foreground = Brushes.Gray });
|
||||
|
||||
model = new RuntimeWindowViewModel(LogTextBlock);
|
||||
ProgressBar.DataContext = model;
|
||||
StatusTextBlock.DataContext = model;
|
||||
|
||||
Closed += (o, args) => closed?.Invoke();
|
||||
Closing += (o, args) => args.Cancel = !allowClose;
|
||||
|
||||
#if DEBUG
|
||||
Topmost = false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.ServerFailureDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Windows"
|
||||
mc:Ignorable="d" Background="Transparent" Height="750" Width="1000" FontSize="16" ResizeMode="NoResize" Topmost="True" AllowsTransparency="True" WindowStyle="None">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid Background="#66000000">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" />
|
||||
<Grid Grid.Row="1" Background="White">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<fa:ImageAwesome Grid.Column="0" Foreground="LightGray" Icon="Warning" Margin="50" Width="75" />
|
||||
<WrapPanel Grid.Column="1" Margin="0,0,50,0" Orientation="Vertical" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="Message" TextWrapping="WrapWithOverflow" />
|
||||
<TextBlock x:Name="Info" FontFamily="Courier New" Margin="0,10,0,0" TextWrapping="WrapWithOverflow" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<WrapPanel Orientation="Horizontal" Margin="50,25" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button x:Name="RetryButton" Cursor="Hand" Margin="10,0" Padding="10,5" MinWidth="75" />
|
||||
<Button x:Name="FallbackButton" Cursor="Hand" Margin="0,0,10,0" Padding="10,5" MinWidth="75" />
|
||||
<Button x:Name="AbortButton" Cursor="Hand" Padding="10,5" MinWidth="75" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.ComponentModel;
|
||||
using System.Windows;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
public partial class ServerFailureDialog : Window, IServerFailureDialog
|
||||
{
|
||||
private readonly IText text;
|
||||
|
||||
public ServerFailureDialog(string info, bool showFallback, IText text)
|
||||
{
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeDialog(info, showFallback);
|
||||
}
|
||||
|
||||
public ServerFailureDialogResult Show(IWindow parent = null)
|
||||
{
|
||||
return Dispatcher.Invoke(() =>
|
||||
{
|
||||
var result = new ServerFailureDialogResult { Success = false };
|
||||
|
||||
if (parent is Window)
|
||||
{
|
||||
Owner = parent as Window;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
}
|
||||
|
||||
InitializeBounds();
|
||||
|
||||
if (ShowDialog() is true)
|
||||
{
|
||||
result.Abort = Tag as string == nameof(AbortButton);
|
||||
result.Fallback = Tag as string == nameof(FallbackButton);
|
||||
result.Retry = Tag as string == nameof(RetryButton);
|
||||
result.Success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Abort = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeBounds()
|
||||
{
|
||||
Left = 0;
|
||||
Top = 0;
|
||||
Height = SystemParameters.PrimaryScreenHeight;
|
||||
Width = SystemParameters.PrimaryScreenWidth;
|
||||
}
|
||||
|
||||
private void InitializeDialog(string info, bool showFallback)
|
||||
{
|
||||
InitializeBounds();
|
||||
|
||||
Info.Text = info;
|
||||
Message.Text = text.Get(TextKey.ServerFailureDialog_Message);
|
||||
Title = text.Get(TextKey.ServerFailureDialog_Title);
|
||||
|
||||
AbortButton.Click += AbortButton_Click;
|
||||
AbortButton.Content = text.Get(TextKey.ServerFailureDialog_Abort);
|
||||
|
||||
FallbackButton.Click += FallbackButton_Click;
|
||||
FallbackButton.Content = text.Get(TextKey.ServerFailureDialog_Fallback);
|
||||
FallbackButton.Visibility = showFallback ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
Loaded += (o, args) => Activate();
|
||||
|
||||
RetryButton.Click += RetryButton_Click;
|
||||
RetryButton.Content = text.Get(TextKey.ServerFailureDialog_Retry);
|
||||
|
||||
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
|
||||
}
|
||||
|
||||
private void AbortButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Tag = nameof(AbortButton);
|
||||
Close();
|
||||
}
|
||||
|
||||
private void FallbackButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Tag = nameof(FallbackButton);
|
||||
Close();
|
||||
}
|
||||
|
||||
private void RetryButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Tag = nameof(RetryButton);
|
||||
Close();
|
||||
}
|
||||
|
||||
private void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(SystemParameters.WorkArea))
|
||||
{
|
||||
Dispatcher.InvokeAsync(InitializeBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.SplashScreen" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
|
||||
mc:Ignorable="d"
|
||||
Title="SplashScreen" Background="White" FontSize="16" Height="300" Width="500" WindowStyle="None" WindowStartupLocation="CenterScreen"
|
||||
Cursor="Wait" Icon="../Images/SafeExamBrowser.ico" ResizeMode="NoResize" Topmost="True">
|
||||
<Border BorderBrush="DodgerBlue" BorderThickness="1">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="50" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" Margin="0,25">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="225" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Column="0" Grid.ColumnSpan="2" Source="pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/SplashScreen.png" />
|
||||
<TextBlock x:Name="InfoTextBlock" Grid.Column="1" Foreground="Gray" Margin="10,85,10,10" TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
<ProgressBar x:Name="ProgressBar" Grid.Row="1" Minimum="0" Maximum="{Binding Path=MaxProgress}" Value="{Binding Path=CurrentProgress}" IsIndeterminate="{Binding Path=IsIndeterminate}" BorderThickness="0" />
|
||||
<TextBlock x:Name="StatusTextBlock" Grid.Row="1" Text="{Binding Path=Status}" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
|
||||
using SafeExamBrowser.UserInterface.Mobile.ViewModels;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class SplashScreen : Window, ISplashScreen
|
||||
{
|
||||
private readonly ProgressIndicatorViewModel model;
|
||||
private readonly IText text;
|
||||
|
||||
private bool allowClose;
|
||||
private AppConfig appConfig;
|
||||
|
||||
private WindowClosedEventHandler closed;
|
||||
private WindowClosingEventHandler closing;
|
||||
|
||||
public AppConfig AppConfig
|
||||
{
|
||||
set
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
appConfig = value;
|
||||
UpdateAppInfo();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
event WindowClosedEventHandler IWindow.Closed
|
||||
{
|
||||
add { closed += value; }
|
||||
remove { closed -= value; }
|
||||
}
|
||||
|
||||
event WindowClosingEventHandler IWindow.Closing
|
||||
{
|
||||
add { closing += value; }
|
||||
remove { closing -= value; }
|
||||
}
|
||||
|
||||
internal SplashScreen(IText text, AppConfig appConfig = null)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.model = new ProgressIndicatorViewModel();
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeSplashScreen();
|
||||
}
|
||||
|
||||
public void BringToForeground()
|
||||
{
|
||||
Dispatcher.Invoke(Activate);
|
||||
}
|
||||
|
||||
public new void Close()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
allowClose = true;
|
||||
model.BusyIndication = false;
|
||||
closing?.Invoke();
|
||||
|
||||
base.Close();
|
||||
});
|
||||
}
|
||||
|
||||
public new void Hide()
|
||||
{
|
||||
Dispatcher.Invoke(base.Hide);
|
||||
}
|
||||
|
||||
public new void Show()
|
||||
{
|
||||
Dispatcher.Invoke(base.Show);
|
||||
}
|
||||
|
||||
public void Progress()
|
||||
{
|
||||
model.CurrentProgress += 1;
|
||||
}
|
||||
|
||||
public void Regress()
|
||||
{
|
||||
model.CurrentProgress -= 1;
|
||||
}
|
||||
|
||||
public void SetIndeterminate()
|
||||
{
|
||||
model.IsIndeterminate = true;
|
||||
}
|
||||
|
||||
public void SetMaxValue(int max)
|
||||
{
|
||||
model.IsIndeterminate = false;
|
||||
model.MaxProgress = max;
|
||||
}
|
||||
|
||||
public void SetValue(int value)
|
||||
{
|
||||
model.CurrentProgress = value;
|
||||
}
|
||||
|
||||
public void UpdateStatus(TextKey key, bool busyIndication = false)
|
||||
{
|
||||
model.Status = text.Get(key);
|
||||
model.BusyIndication = busyIndication;
|
||||
}
|
||||
|
||||
private void InitializeSplashScreen()
|
||||
{
|
||||
UpdateAppInfo();
|
||||
|
||||
StatusTextBlock.DataContext = model;
|
||||
ProgressBar.DataContext = model;
|
||||
|
||||
Closed += (o, args) => closed?.Invoke();
|
||||
Closing += (o, args) => args.Cancel = !allowClose;
|
||||
}
|
||||
|
||||
private void UpdateAppInfo()
|
||||
{
|
||||
if (appConfig != null)
|
||||
{
|
||||
InfoTextBlock.Inlines.Add(new Run($"Version {appConfig.ProgramInformationalVersion}") { Foreground = Brushes.DimGray });
|
||||
InfoTextBlock.Inlines.Add(new LineBreak());
|
||||
InfoTextBlock.Inlines.Add(new LineBreak());
|
||||
InfoTextBlock.Inlines.Add(new Run(appConfig.ProgramCopyright) { FontSize = 12 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml
Normal file
35
SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml
Normal file
@@ -0,0 +1,35 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.Taskbar" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar"
|
||||
xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||
mc:Ignorable="d" Title="Taskbar" Background="{DynamicResource BackgroundBrush}" Height="60" FontSize="16" Width="750" WindowStyle="None"
|
||||
KeyDown="Window_KeyDown" KeyUp="Window_KeyUp"
|
||||
Topmost="True" ResizeMode="NoResize" Icon="../Images/SafeExamBrowser.ico">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ScrollViewer Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" Template="{StaticResource SmallBarScrollViewer}">
|
||||
<StackPanel x:Name="ApplicationStackPanel" Orientation="Horizontal" />
|
||||
</ScrollViewer>
|
||||
<StackPanel Grid.Column="1" x:Name="NotificationStackPanel" Orientation="Horizontal" VerticalAlignment="Stretch" />
|
||||
<StackPanel Grid.Column="2" x:Name="SystemControlStackPanel" Orientation="Horizontal" VerticalAlignment="Stretch" />
|
||||
<local:Clock Grid.Column="3" x:Name="Clock" Foreground="{StaticResource SecondaryTextBrush}" Padding="10,0,10,0" />
|
||||
<local:QuitButton Grid.Column="4" x:Name="QuitButton" />
|
||||
</Grid>
|
||||
</Window>
|
262
SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs
Normal file
262
SafeExamBrowser.UserInterface.Mobile/Windows/Taskbar.xaml.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class Taskbar : Window, ITaskbar
|
||||
{
|
||||
private bool allowClose;
|
||||
private readonly ILogger logger;
|
||||
private bool isQuitButtonFocusedAtKeyDown;
|
||||
private bool isFirstChildFocusedAtKeyDown;
|
||||
|
||||
public bool ShowClock
|
||||
{
|
||||
set { Dispatcher.Invoke(() => Clock.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
|
||||
}
|
||||
|
||||
public bool ShowQuitButton
|
||||
{
|
||||
set { Dispatcher.Invoke(() => QuitButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed); }
|
||||
}
|
||||
|
||||
public event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||
public event QuitButtonClickedEventHandler QuitButtonClicked;
|
||||
|
||||
internal Taskbar(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeTaskbar();
|
||||
}
|
||||
|
||||
public void AddApplicationControl(IApplicationControl control, bool atFirstPosition = false)
|
||||
{
|
||||
if (control is UIElement uiElement)
|
||||
{
|
||||
if (atFirstPosition)
|
||||
{
|
||||
ApplicationStackPanel.Children.Insert(0, uiElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplicationStackPanel.Children.Add(uiElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNotificationControl(INotificationControl control)
|
||||
{
|
||||
if (control is UIElement uiElement)
|
||||
{
|
||||
NotificationStackPanel.Children.Add(uiElement);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddSystemControl(ISystemControl control)
|
||||
{
|
||||
if (control is UIElement uiElement)
|
||||
{
|
||||
SystemControlStackPanel.Children.Add(uiElement);
|
||||
}
|
||||
}
|
||||
|
||||
public new void Close()
|
||||
{
|
||||
Dispatcher.Invoke(base.Close);
|
||||
}
|
||||
|
||||
public void Focus(bool fromTop)
|
||||
{
|
||||
Dispatcher.BeginInvoke((Action) (() =>
|
||||
{
|
||||
Activate();
|
||||
|
||||
if (fromTop && ApplicationStackPanel.Children.Count > 0)
|
||||
{
|
||||
ApplicationStackPanel.Children[0].Focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
QuitButton.Focus();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public int GetAbsoluteHeight()
|
||||
{
|
||||
return Dispatcher.Invoke(() =>
|
||||
{
|
||||
var height = (int) this.TransformToPhysical(Width, Height).Y;
|
||||
|
||||
logger.Debug($"Calculated physical taskbar height is {height}px.");
|
||||
|
||||
return height;
|
||||
});
|
||||
}
|
||||
|
||||
public int GetRelativeHeight()
|
||||
{
|
||||
return Dispatcher.Invoke(() =>
|
||||
{
|
||||
var height = (int) Height;
|
||||
|
||||
logger.Debug($"Logical taskbar height is {height}px.");
|
||||
|
||||
return height;
|
||||
});
|
||||
}
|
||||
|
||||
public void InitializeBounds()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
Width = SystemParameters.PrimaryScreenWidth;
|
||||
Left = 0;
|
||||
Top = SystemParameters.PrimaryScreenHeight - Height;
|
||||
|
||||
var position = this.TransformToPhysical(Left, Top);
|
||||
var size = this.TransformToPhysical(Width, Height);
|
||||
|
||||
logger.Debug($"Set taskbar bounds to {Width}x{Height} at ({Left}/{Top}), in physical pixels: {size.X}x{size.Y} at ({position.X}/{position.Y}).");
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeTaskbar()
|
||||
{
|
||||
Closing += Taskbar_Closing;
|
||||
Loaded += (o, args) => InitializeBounds();
|
||||
QuitButton.Clicked += QuitButton_Clicked;
|
||||
}
|
||||
|
||||
public void InitializeText(IText text)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
var txt = text.Get(TextKey.Shell_QuitButton);
|
||||
QuitButton.ToolTip = txt;
|
||||
QuitButton.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, txt);
|
||||
});
|
||||
}
|
||||
|
||||
public void Register(ITaskbarActivator activator)
|
||||
{
|
||||
activator.Activated += Activator_Activated;
|
||||
}
|
||||
|
||||
public new void Show()
|
||||
{
|
||||
Dispatcher.Invoke(base.Show);
|
||||
}
|
||||
|
||||
private void Activator_Activated()
|
||||
{
|
||||
(this as ITaskbar).Focus(true);
|
||||
}
|
||||
|
||||
private void QuitButton_Clicked(CancelEventArgs args)
|
||||
{
|
||||
QuitButtonClicked?.Invoke(args);
|
||||
allowClose = !args.Cancel;
|
||||
}
|
||||
|
||||
private void Taskbar_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
if (allowClose)
|
||||
{
|
||||
foreach (var child in SystemControlStackPanel.Children)
|
||||
{
|
||||
if (child is ISystemControl systemControl)
|
||||
{
|
||||
systemControl.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
isQuitButtonFocusedAtKeyDown = QuitButton.IsKeyboardFocusWithin;
|
||||
isFirstChildFocusedAtKeyDown = ApplicationStackPanel.Children.Count > 0 && ApplicationStackPanel.Children[0].IsKeyboardFocusWithin;
|
||||
}
|
||||
|
||||
private void Window_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == System.Windows.Input.Key.Tab)
|
||||
{
|
||||
var shift = System.Windows.Input.Keyboard.IsKeyDown(System.Windows.Input.Key.LeftShift);
|
||||
var hasFocus = ApplicationStackPanel.Children.Count > 0 && ApplicationStackPanel.Children[0].IsKeyboardFocusWithin;
|
||||
|
||||
if (!shift && hasFocus && isQuitButtonFocusedAtKeyDown)
|
||||
{
|
||||
LoseFocusRequested?.Invoke(true);
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (shift && QuitButton.IsKeyboardFocusWithin && isFirstChildFocusedAtKeyDown)
|
||||
{
|
||||
LoseFocusRequested?.Invoke(false);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
isQuitButtonFocusedAtKeyDown = false;
|
||||
isFirstChildFocusedAtKeyDown = false;
|
||||
}
|
||||
|
||||
private bool SetFocusWithin(UIElement uIElement)
|
||||
{
|
||||
if (uIElement.Focusable)
|
||||
{
|
||||
uIElement.Focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uIElement is System.Windows.Controls.Panel)
|
||||
{
|
||||
var panel = uIElement as System.Windows.Controls.Panel;
|
||||
|
||||
for (var i = 0; i < panel.Children.Count; i++)
|
||||
{
|
||||
if (SetFocusWithin(panel.Children[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (uIElement is System.Windows.Controls.ContentControl)
|
||||
{
|
||||
var control = uIElement as System.Windows.Controls.ContentControl;
|
||||
var content = control.Content as UIElement;
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
return SetFocusWithin(content);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
19
SafeExamBrowser.UserInterface.Mobile/Windows/Taskview.xaml
Normal file
19
SafeExamBrowser.UserInterface.Mobile/Windows/Taskview.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.Windows.Taskview" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
|
||||
mc:Ignorable="d" AllowsTransparency="True" Background="{DynamicResource BackgroundTransparentBrush}" BorderBrush="DodgerBlue" BorderThickness="1"
|
||||
Title="Taskview" Topmost="True" Height="450" SizeToContent="WidthAndHeight" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<StackPanel Name="Rows" Margin="10" Orientation="Vertical" />
|
||||
</Grid>
|
||||
</Window>
|
197
SafeExamBrowser.UserInterface.Mobile/Windows/Taskview.xaml.cs
Normal file
197
SafeExamBrowser.UserInterface.Mobile/Windows/Taskview.xaml.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Mobile.Controls.Taskview;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Mobile.Windows
|
||||
{
|
||||
internal partial class Taskview : Window, ITaskview
|
||||
{
|
||||
private readonly IList<IApplication<IApplicationWindow>> applications;
|
||||
private readonly LinkedList<WindowControl> controls;
|
||||
|
||||
private LinkedListNode<WindowControl> current;
|
||||
|
||||
internal IntPtr Handle { get; private set; }
|
||||
|
||||
internal Taskview()
|
||||
{
|
||||
applications = new List<IApplication<IApplicationWindow>>();
|
||||
controls = new LinkedList<WindowControl>();
|
||||
|
||||
InitializeComponent();
|
||||
InitializeTaskview();
|
||||
}
|
||||
|
||||
public void Add(IApplication<IApplicationWindow> application)
|
||||
{
|
||||
application.WindowsChanged += Application_WindowsChanged;
|
||||
applications.Add(application);
|
||||
}
|
||||
|
||||
public void Register(ITaskviewActivator activator)
|
||||
{
|
||||
activator.Deactivated += Activator_Deactivated;
|
||||
activator.NextActivated += Activator_Next;
|
||||
activator.PreviousActivated += Activator_Previous;
|
||||
}
|
||||
|
||||
private void Application_WindowsChanged()
|
||||
{
|
||||
Dispatcher.InvokeAsync(Update);
|
||||
}
|
||||
|
||||
private void Activator_Deactivated()
|
||||
{
|
||||
Dispatcher.InvokeAsync(ActivateAndHide);
|
||||
}
|
||||
|
||||
private void Activator_Next()
|
||||
{
|
||||
Dispatcher.InvokeAsync(SelectNext);
|
||||
}
|
||||
|
||||
private void Activator_Previous()
|
||||
{
|
||||
Dispatcher.InvokeAsync(SelectPrevious);
|
||||
}
|
||||
|
||||
private void ActivateAndHide()
|
||||
{
|
||||
if (IsVisible)
|
||||
{
|
||||
Activate();
|
||||
current?.Value.Activate();
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeTaskview()
|
||||
{
|
||||
Loaded += (o, args) =>
|
||||
{
|
||||
Handle = new WindowInteropHelper(this).Handle;
|
||||
Update();
|
||||
};
|
||||
}
|
||||
|
||||
private void SelectNext()
|
||||
{
|
||||
ShowConditional();
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
current.Value.Deselect();
|
||||
current = current.Next ?? controls.First;
|
||||
current.Value.Select();
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectPrevious()
|
||||
{
|
||||
ShowConditional();
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
current.Value.Deselect();
|
||||
current = current.Previous ?? controls.Last;
|
||||
current.Value.Select();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowConditional()
|
||||
{
|
||||
if (controls.Any() && Visibility != Visibility.Visible)
|
||||
{
|
||||
Show();
|
||||
Activate();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
ClearTaskview();
|
||||
LoadControls();
|
||||
UpdateLocation();
|
||||
}
|
||||
|
||||
private void ClearTaskview()
|
||||
{
|
||||
foreach (var control in controls)
|
||||
{
|
||||
control.Destroy();
|
||||
}
|
||||
|
||||
controls.Clear();
|
||||
Rows.Children.Clear();
|
||||
}
|
||||
|
||||
private void LoadControls()
|
||||
{
|
||||
var windows = GetAllWindows();
|
||||
var maxColumns = Math.Ceiling(Math.Sqrt(windows.Count));
|
||||
|
||||
while (windows.Any())
|
||||
{
|
||||
var row = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
|
||||
|
||||
Rows.Children.Add(row);
|
||||
|
||||
for (var column = 0; column < maxColumns && windows.Any(); column++)
|
||||
{
|
||||
var window = windows.Pop();
|
||||
var control = new WindowControl(window, this);
|
||||
|
||||
controls.AddLast(control);
|
||||
row.Children.Add(control);
|
||||
}
|
||||
}
|
||||
|
||||
current = controls.First;
|
||||
current?.Value.Select();
|
||||
}
|
||||
|
||||
private void UpdateLocation()
|
||||
{
|
||||
if (controls.Any())
|
||||
{
|
||||
UpdateLayout();
|
||||
|
||||
Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left;
|
||||
Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top;
|
||||
}
|
||||
else
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private Stack<IApplicationWindow> GetAllWindows()
|
||||
{
|
||||
var stack = new Stack<IApplicationWindow>();
|
||||
|
||||
foreach (var application in applications)
|
||||
{
|
||||
foreach (var window in application.GetWindows())
|
||||
{
|
||||
stack.Push(window);
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user