diff --git a/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs b/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs index b72b8fcd..fec8cda7 100644 --- a/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs +++ b/SafeExamBrowser.Core/Behaviour/Operations/TaskbarOperation.cs @@ -61,10 +61,11 @@ namespace SafeExamBrowser.Core.Behaviour.Operations CreateAboutNotification(); - if (systemInfo.HasBattery) - { - CreatePowerSupplyComponent(); - } + // TODO: + //if (systemInfo.HasBattery) + //{ + // CreatePowerSupplyComponent(); + //} } public void Revert() diff --git a/SafeExamBrowser.UserInterface.Classic/BrowserWindow.xaml b/SafeExamBrowser.UserInterface.Classic/BrowserWindow.xaml new file mode 100644 index 00000000..af38aab6 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/BrowserWindow.xaml @@ -0,0 +1,28 @@ +<Window x:Class="SafeExamBrowser.UserInterface.Classic.BrowserWindow" + 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.Classic" + mc:Ignorable="d" + Title="BrowserWindow" Height="500" Width="500" WindowState="Maximized" Icon=".\Images\Chromium.ico"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="30" /> + <RowDefinition Height="*" /> + </Grid.RowDefinitions> + <Grid Grid.Row="0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <TextBox Grid.Column="0" x:Name="UrlTextBox" Margin="5,5,2.5,5" Height="20" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" /> + <Button Grid.Column="1" x:Name="ReloadButton" Margin="2.5,5,2.5,5" HorizontalAlignment="Center" VerticalAlignment="Center">Reload</Button> + <Button Grid.Column="2" x:Name="BackButton" Margin="2.5,5,2.5,5" HorizontalAlignment="Center" VerticalAlignment="Center">Back</Button> + <Button Grid.Column="3" x:Name="ForwardButton" Margin="2.5,5,5,5" HorizontalAlignment="Center" VerticalAlignment="Center">Forward</Button> + </Grid> + <WindowsFormsHost Grid.Row="1" x:Name="BrowserControlHost" /> + </Grid> +</Window> diff --git a/SafeExamBrowser.UserInterface.Classic/BrowserWindow.xaml.cs b/SafeExamBrowser.UserInterface.Classic/BrowserWindow.xaml.cs new file mode 100644 index 00000000..fa1b7114 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/BrowserWindow.xaml.cs @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System.Windows; +using System.Windows.Input; +using SafeExamBrowser.Contracts.Configuration.Settings; +using SafeExamBrowser.Contracts.UserInterface; + +namespace SafeExamBrowser.UserInterface.Classic +{ + public partial class BrowserWindow : Window, IBrowserWindow + { + private bool isMainWindow; + private IBrowserSettings settings; + public WindowClosingEventHandler closing; + + public bool IsMainWindow + { + get + { + return isMainWindow; + } + set + { + isMainWindow = value; + ApplySettings(); + } + } + + public event AddressChangedEventHandler AddressChanged; + public event ActionRequestedEventHandler BackwardNavigationRequested; + public event ActionRequestedEventHandler ForwardNavigationRequested; + public event ActionRequestedEventHandler ReloadRequested; + + event WindowClosingEventHandler IWindow.Closing + { + add { closing += value; } + remove { closing -= value; } + } + + public BrowserWindow(IBrowserControl browserControl, IBrowserSettings settings) + { + this.settings = settings; + + InitializeComponent(); + InitializeBrowserWindow(browserControl); + } + + public void BringToForeground() + { + if (WindowState == WindowState.Minimized) + { + WindowState = WindowState.Normal; + } + + Activate(); + } + + public void UpdateAddress(string url) + { + Dispatcher.Invoke(() => UrlTextBox.Text = url); + } + + public void UpdateTitle(string title) + { + Dispatcher.Invoke(() => Title = title); + } + + private void InitializeBrowserWindow(IBrowserControl browserControl) + { + if (browserControl is System.Windows.Forms.Control) + { + BrowserControlHost.Child = browserControl as System.Windows.Forms.Control; + } + + Closing += (o, args) => closing?.Invoke(); + KeyUp += BrowserWindow_KeyUp; + UrlTextBox.KeyUp += UrlTextBox_KeyUp; + ReloadButton.Click += (o, args) => ReloadRequested?.Invoke(); + BackButton.Click += (o, args) => BackwardNavigationRequested?.Invoke(); + ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke(); + + ApplySettings(); + } + + private void BrowserWindow_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.F5) + { + ReloadRequested?.Invoke(); + } + } + + private void UrlTextBox_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + AddressChanged?.Invoke(UrlTextBox.Text); + } + } + + private void ApplySettings() + { + if (IsMainWindow && settings.FullScreenMode) + { + MaxHeight = SystemParameters.WorkArea.Height; + ResizeMode = ResizeMode.NoResize; + WindowState = WindowState.Maximized; + WindowStyle = WindowStyle.None; + } + + UrlTextBox.IsEnabled = settings.AllowAddressBar; + + ReloadButton.IsEnabled = settings.AllowReloading; + ReloadButton.Visibility = settings.AllowReloading ? Visibility.Visible : Visibility.Collapsed; + + BackButton.IsEnabled = settings.AllowBackwardNavigation; + BackButton.Visibility = settings.AllowBackwardNavigation ? Visibility.Visible : Visibility.Collapsed; + + ForwardButton.IsEnabled = settings.AllowForwardNavigation; + ForwardButton.Visibility = settings.AllowForwardNavigation ? Visibility.Visible : Visibility.Collapsed; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationButton.xaml b/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationButton.xaml new file mode 100644 index 00000000..722e4346 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationButton.xaml @@ -0,0 +1,23 @@ +<UserControl x:Class="SafeExamBrowser.UserInterface.Classic.Controls.ApplicationButton" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic.Controls" + xmlns:s="clr-namespace:System;assembly=mscorlib" + mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="50"> + <Grid> + <Popup x:Name="InstancePopup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}"> + <ScrollViewer x:Name="InstanceScrollViewer" VerticalScrollBarVisibility="Auto"> + <ScrollViewer.Resources> + <s:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">5</s:Double> + </ScrollViewer.Resources> + <StackPanel x:Name="InstanceStackPanel" /> + </ScrollViewer> + </Popup> + <Button x:Name="Button" Click="Button_Click" Padding="5" Width="50" /> + <Grid Panel.ZIndex="10"> + <Rectangle x:Name="ActiveBar" Height="2" Width="40" VerticalAlignment="Bottom" Fill="LightSteelBlue" Visibility="Collapsed" /> + </Grid> + </Grid> +</UserControl> diff --git a/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationButton.xaml.cs new file mode 100644 index 00000000..c2f3b530 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationButton.xaml.cs @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.UserInterface.Taskbar; +using SafeExamBrowser.UserInterface.Classic.Utilities; + +namespace SafeExamBrowser.UserInterface.Classic.Controls +{ + public partial class ApplicationButton : UserControl, IApplicationButton + { + private IApplicationInfo info; + private IList<IApplicationInstance> instances = new List<IApplicationInstance>(); + + public event ApplicationButtonClickedEventHandler Clicked; + + public ApplicationButton(IApplicationInfo info) + { + this.info = info; + + InitializeComponent(); + InitializeApplicationButton(); + } + + public void RegisterInstance(IApplicationInstance instance) + { + var instanceButton = new ApplicationInstanceButton(instance, info); + + instanceButton.Clicked += (id) => Clicked?.Invoke(id); + instance.Terminated += (id) => Instance_OnTerminated(id, instanceButton); + + instances.Add(instance); + InstanceStackPanel.Children.Add(instanceButton); + + ActiveBar.Visibility = Visibility.Visible; + } + + private void InitializeApplicationButton() + { + Button.ToolTip = info.Tooltip; + Button.Content = IconResourceLoader.Load(info.IconResource); + + Button.MouseEnter += (o, args) => InstancePopup.IsOpen = instances.Count > 1; + Button.MouseLeave += (o, args) => InstancePopup.IsOpen &= InstancePopup.IsMouseOver || ActiveBar.IsMouseOver; + ActiveBar.MouseLeave += (o, args) => InstancePopup.IsOpen &= InstancePopup.IsMouseOver || Button.IsMouseOver; + InstancePopup.MouseLeave += (o, args) => InstancePopup.IsOpen = false; + InstancePopup.Opened += (o, args) => ActiveBar.Width = Double.NaN; + InstancePopup.Closed += (o, args) => ActiveBar.Width = 40; + InstanceStackPanel.SizeChanged += (o, args) => + { + if (instances.Count > 9) + { + InstanceScrollViewer.MaxHeight = InstanceScrollViewer.ActualHeight; + } + }; + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + if (instances.Count <= 1) + { + Clicked?.Invoke(instances.FirstOrDefault()?.Id); + } + else + { + InstancePopup.IsOpen = true; + } + } + + private void Instance_OnTerminated(Guid id, ApplicationInstanceButton instanceButton) + { + instances.Remove(instances.FirstOrDefault(i => i.Id == id)); + InstanceStackPanel.Children.Remove(instanceButton); + + if (!instances.Any()) + { + ActiveBar.Visibility = Visibility.Collapsed; + } + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationInstanceButton.xaml b/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationInstanceButton.xaml new file mode 100644 index 00000000..19d961af --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationInstanceButton.xaml @@ -0,0 +1,16 @@ +<UserControl x:Class="SafeExamBrowser.UserInterface.Classic.Controls.ApplicationInstanceButton" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic.Controls" + mc:Ignorable="d" d:DesignWidth="250"> + <Grid> + <Button x:Name="Button" Click="Button_Click" Height="25"> + <StackPanel Orientation="Horizontal"> + <ContentControl x:Name="Icon" /> + <TextBlock x:Name="Text" Padding="5,0,5,0" /> + </StackPanel> + </Button> + </Grid> +</UserControl> diff --git a/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationInstanceButton.xaml.cs b/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationInstanceButton.xaml.cs new file mode 100644 index 00000000..fe16b0f1 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Controls/ApplicationInstanceButton.xaml.cs @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; +using System.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.UserInterface.Classic.Utilities; + +namespace SafeExamBrowser.UserInterface.Classic.Controls +{ + internal delegate void InstanceButtonClickedEventHandler(Guid instanceId); + + public partial class ApplicationInstanceButton : UserControl + { + private IApplicationInfo info; + private IApplicationInstance instance; + + internal event InstanceButtonClickedEventHandler Clicked; + + public ApplicationInstanceButton(IApplicationInstance instance, IApplicationInfo info) + { + this.info = info; + this.instance = instance; + + InitializeComponent(); + InitializeApplicationInstanceButton(); + } + + private void InitializeApplicationInstanceButton() + { + Icon.Content = IconResourceLoader.Load(info.IconResource); + Text.Text = instance.Name; + Button.ToolTip = instance.Name; + + instance.NameChanged += (name) => + { + Dispatcher.Invoke(() => + { + Text.Text = name; + Button.ToolTip = name; + }); + }; + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + Clicked?.Invoke(instance.Id); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/Controls/DateTimeControl.xaml b/SafeExamBrowser.UserInterface.Classic/Controls/DateTimeControl.xaml new file mode 100644 index 00000000..4c901650 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Controls/DateTimeControl.xaml @@ -0,0 +1,17 @@ +<UserControl x:Class="SafeExamBrowser.UserInterface.Classic.Controls.DateTimeControl" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic.Controls" + mc:Ignorable="d" + d:DesignHeight="300" d:DesignWidth="300"> + <Grid Margin="5,0"> + <Grid.RowDefinitions> + <RowDefinition Height="1*" /> + <RowDefinition Height="1*" /> + </Grid.RowDefinitions> + <TextBlock x:Name="TimeTextBlock" Grid.Row="0" Text="{Binding Path=Time}" HorizontalAlignment="Center" VerticalAlignment="Center" /> + <TextBlock x:Name="DateTextBlock" Grid.Row="1" Text="{Binding Path=Date}" HorizontalAlignment="Center" VerticalAlignment="Center" /> + </Grid> +</UserControl> diff --git a/SafeExamBrowser.UserInterface.Classic/Controls/DateTimeControl.xaml.cs b/SafeExamBrowser.UserInterface.Classic/Controls/DateTimeControl.xaml.cs new file mode 100644 index 00000000..d8c01031 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Controls/DateTimeControl.xaml.cs @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System.Windows.Controls; +using SafeExamBrowser.UserInterface.Classic.ViewModels; + +namespace SafeExamBrowser.UserInterface.Classic.Controls +{ + public partial class DateTimeControl : UserControl + { + private DateTimeViewModel model = new DateTimeViewModel(); + + public DateTimeControl() + { + InitializeComponent(); + + DataContext = model; + TimeTextBlock.DataContext = model; + DateTextBlock.DataContext = model; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/Controls/NotificationButton.xaml b/SafeExamBrowser.UserInterface.Classic/Controls/NotificationButton.xaml new file mode 100644 index 00000000..69048230 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Controls/NotificationButton.xaml @@ -0,0 +1,11 @@ +<UserControl x:Class="SafeExamBrowser.UserInterface.Classic.Controls.NotificationButton" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic.Controls" + mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="28"> + <Grid> + <Button x:Name="IconButton" Click="Icon_Click" Padding="5,0" Width="28"/> + </Grid> +</UserControl> diff --git a/SafeExamBrowser.UserInterface.Classic/Controls/NotificationButton.xaml.cs b/SafeExamBrowser.UserInterface.Classic/Controls/NotificationButton.xaml.cs new file mode 100644 index 00000000..d3e0a42e --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Controls/NotificationButton.xaml.cs @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System.Windows; +using System.Windows.Controls; +using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.UserInterface.Taskbar; +using SafeExamBrowser.UserInterface.Classic.Utilities; + +namespace SafeExamBrowser.UserInterface.Classic.Controls +{ + public partial class NotificationButton : UserControl, INotificationButton + { + public event NotificationButtonClickedEventHandler Clicked; + + public NotificationButton(INotificationInfo info) + { + InitializeComponent(); + InitializeNotificationIcon(info); + } + + private void Icon_Click(object sender, RoutedEventArgs e) + { + Clicked?.Invoke(); + } + + private void InitializeNotificationIcon(INotificationInfo info) + { + IconButton.ToolTip = info.Tooltip; + IconButton.Content = IconResourceLoader.Load(info.IconResource); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/Controls/QuitButton.xaml b/SafeExamBrowser.UserInterface.Classic/Controls/QuitButton.xaml new file mode 100644 index 00000000..fc759e4e --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Controls/QuitButton.xaml @@ -0,0 +1,11 @@ +<UserControl x:Class="SafeExamBrowser.UserInterface.Classic.Controls.QuitButton" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic.Controls" + mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40"> + <Grid> + <Button Click="Button_Click">Quit</Button> + </Grid> +</UserControl> diff --git a/SafeExamBrowser.UserInterface.Classic/Controls/QuitButton.xaml.cs b/SafeExamBrowser.UserInterface.Classic/Controls/QuitButton.xaml.cs new file mode 100644 index 00000000..52ee9c66 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Controls/QuitButton.xaml.cs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System.Windows; +using System.Windows.Controls; + +namespace SafeExamBrowser.UserInterface.Classic.Controls +{ + public partial class QuitButton : UserControl + { + public QuitButton() + { + InitializeComponent(); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + Application.Current.MainWindow.Close(); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/Images/Chromium.ico b/SafeExamBrowser.UserInterface.Classic/Images/Chromium.ico new file mode 100644 index 00000000..46025b80 Binary files /dev/null and b/SafeExamBrowser.UserInterface.Classic/Images/Chromium.ico differ diff --git a/SafeExamBrowser.UserInterface.Classic/Images/LogNotification.ico b/SafeExamBrowser.UserInterface.Classic/Images/LogNotification.ico new file mode 100644 index 00000000..1c7fb20f Binary files /dev/null and b/SafeExamBrowser.UserInterface.Classic/Images/LogNotification.ico differ diff --git a/SafeExamBrowser.UserInterface.Classic/Images/SafeExamBrowser.ico b/SafeExamBrowser.UserInterface.Classic/Images/SafeExamBrowser.ico new file mode 100644 index 00000000..abdc4635 Binary files /dev/null and b/SafeExamBrowser.UserInterface.Classic/Images/SafeExamBrowser.ico differ diff --git a/SafeExamBrowser.UserInterface.Classic/Images/SplashScreen.png b/SafeExamBrowser.UserInterface.Classic/Images/SplashScreen.png new file mode 100644 index 00000000..c56dd2b0 Binary files /dev/null and b/SafeExamBrowser.UserInterface.Classic/Images/SplashScreen.png differ diff --git a/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj b/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj index d4714cbd..0e2c10a6 100644 --- a/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj +++ b/SafeExamBrowser.UserInterface.Classic/SafeExamBrowser.UserInterface.Classic.csproj @@ -52,15 +52,72 @@ <ItemGroup> <Reference Include="System" /> <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Windows.Forms" /> <Reference Include="System.Xaml"> <RequiredTargetFramework>4.0</RequiredTargetFramework> </Reference> + <Reference Include="System.Xml" /> <Reference Include="WindowsBase" /> <Reference Include="PresentationCore" /> <Reference Include="PresentationFramework" /> + <Reference Include="WindowsFormsIntegration" /> </ItemGroup> <ItemGroup> + <Compile Include="BrowserWindow.xaml.cs"> + <DependentUpon>BrowserWindow.xaml</DependentUpon> + </Compile> + <Compile Include="Controls\ApplicationButton.xaml.cs"> + <DependentUpon>ApplicationButton.xaml</DependentUpon> + </Compile> + <Compile Include="Controls\ApplicationInstanceButton.xaml.cs"> + <DependentUpon>ApplicationInstanceButton.xaml</DependentUpon> + </Compile> + <Compile Include="Controls\DateTimeControl.xaml.cs"> + <DependentUpon>DateTimeControl.xaml</DependentUpon> + </Compile> + <Compile Include="Controls\NotificationButton.xaml.cs"> + <DependentUpon>NotificationButton.xaml</DependentUpon> + </Compile> + <Compile Include="Controls\QuitButton.xaml.cs"> + <DependentUpon>QuitButton.xaml</DependentUpon> + </Compile> + <Compile Include="SplashScreen.xaml.cs"> + <DependentUpon>SplashScreen.xaml</DependentUpon> + </Compile> <Compile Include="UserInterfaceFactory.cs" /> + <Compile Include="Utilities\IconResourceLoader.cs" /> + <Compile Include="Utilities\VisualExtensions.cs" /> + <Compile Include="ViewModels\DateTimeViewModel.cs" /> + <Compile Include="ViewModels\LogViewModel.cs" /> + <Compile Include="ViewModels\SplashScreenViewModel.cs" /> + <Page Include="BrowserWindow.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Controls\ApplicationButton.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Controls\ApplicationInstanceButton.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Controls\DateTimeControl.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Controls\NotificationButton.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Controls\QuitButton.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="SplashScreen.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> <Page Include="Taskbar.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> @@ -99,5 +156,17 @@ <Name>SafeExamBrowser.Contracts</Name> </ProjectReference> </ItemGroup> + <ItemGroup> + <Resource Include="Images\Chromium.ico" /> + </ItemGroup> + <ItemGroup> + <Resource Include="Images\LogNotification.ico" /> + </ItemGroup> + <ItemGroup> + <Resource Include="Images\SafeExamBrowser.ico" /> + </ItemGroup> + <ItemGroup> + <Resource Include="Images\SplashScreen.png" /> + </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </Project> \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml b/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml new file mode 100644 index 00000000..a4206ec1 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml @@ -0,0 +1,28 @@ +<Window x:Class="SafeExamBrowser.UserInterface.Classic.SplashScreen" + 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.Classic" + mc:Ignorable="d" + Title="SplashScreen" Height="200" Width="350" WindowStyle="None" AllowsTransparency="True" WindowStartupLocation="CenterScreen" + Cursor="Wait" Icon="./Images/SafeExamBrowser.ico" ResizeMode="NoResize" Topmost="True"> + <Border BorderBrush="DodgerBlue" BorderThickness="1"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="*" /> + <RowDefinition Height="25" /> + </Grid.RowDefinitions> + <Grid Grid.Row="0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="155" /> + </Grid.ColumnDefinitions> + <Image Grid.Column="0" Grid.ColumnSpan="2" Source="pack://application:,,,/SafeExamBrowser.UserInterface.Classic;component/Images/SplashScreen.png" /> + <TextBlock x:Name="InfoTextBlock" Grid.Column="1" Margin="10,75,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}" FontSize="12" HorizontalAlignment="Center" VerticalAlignment="Center" /> + </Grid> + </Border> +</Window> diff --git a/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs new file mode 100644 index 00000000..4f87cfad --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/SplashScreen.xaml.cs @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System.Windows; +using System.Windows.Documents; +using SafeExamBrowser.Contracts.Configuration.Settings; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.UserInterface.Classic.ViewModels; + +namespace SafeExamBrowser.UserInterface.Classic +{ + public partial class SplashScreen : Window, ISplashScreen + { + private SplashScreenViewModel model = new SplashScreenViewModel(); + private ISettings settings; + private IText text; + + public SplashScreen(ISettings settings, IText text) + { + this.settings = settings; + this.text = text; + + InitializeComponent(); + InitializeSplashScreen(); + } + + public void InvokeClose() + { + Dispatcher.Invoke(Close); + } + + public void InvokeShow() + { + Dispatcher.Invoke(Show); + } + + public void Progress(int amount = 1) + { + model.CurrentProgress += amount; + } + + public void Regress(int amount = 1) + { + model.CurrentProgress -= amount; + } + + public void SetIndeterminate() + { + model.IsIndeterminate = true; + } + + public void SetMaxProgress(int max) + { + model.MaxProgress = max; + } + + public void UpdateText(TextKey key, bool showBusyIndication = false) + { + model.StopBusyIndication(); + model.Status = text.Get(key); + + if (showBusyIndication) + { + model.StartBusyIndication(); + } + } + + private void InitializeSplashScreen() + { + InfoTextBlock.Inlines.Add(new Run($"{text.Get(TextKey.Version)} {settings.ProgramVersion}") { FontStyle = FontStyles.Italic }); + InfoTextBlock.Inlines.Add(new LineBreak()); + InfoTextBlock.Inlines.Add(new LineBreak()); + InfoTextBlock.Inlines.Add(new Run(settings.ProgramCopyright) { FontSize = 10 }); + + StatusTextBlock.DataContext = model; + ProgressBar.DataContext = model; + + // To prevent the progress bar going from max to min value at startup... + model.MaxProgress = 1; + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/Taskbar.xaml b/SafeExamBrowser.UserInterface.Classic/Taskbar.xaml index fe0f092f..cc06a05b 100644 --- a/SafeExamBrowser.UserInterface.Classic/Taskbar.xaml +++ b/SafeExamBrowser.UserInterface.Classic/Taskbar.xaml @@ -3,10 +3,28 @@ 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.Classic" + xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic.Controls" + xmlns:s="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" - Title="Taskbar" Height="300" Width="300"> + Title="Taskbar" Height="40" Width="750" WindowStyle="None" AllowsTransparency="True" Topmost="True" Visibility="Visible" + ResizeMode="NoResize" Icon="./Images/SafeExamBrowser.ico"> <Grid> - + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="40" /> + </Grid.ColumnDefinitions> + <ScrollViewer Grid.Column="0" x:Name="ApplicationScrollViewer" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"> + <ScrollViewer.Resources> + <s:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">5</s:Double> + </ScrollViewer.Resources> + <StackPanel x:Name="ApplicationStackPanel" Orientation="Horizontal" /> + </ScrollViewer> + <StackPanel Grid.Column="1" x:Name="NotificationStackPanel" Margin="5,0,0,0" Orientation="Horizontal" VerticalAlignment="Stretch" /> + <StackPanel Grid.Column="2" x:Name="SystemControlStackPanel" Orientation="Horizontal" VerticalAlignment="Stretch" /> + <local:DateTimeControl Grid.Column="3" /> + <local:QuitButton Grid.Column="4" /> </Grid> </Window> diff --git a/SafeExamBrowser.UserInterface.Classic/Taskbar.xaml.cs b/SafeExamBrowser.UserInterface.Classic/Taskbar.xaml.cs index edb99269..3423f4a2 100644 --- a/SafeExamBrowser.UserInterface.Classic/Taskbar.xaml.cs +++ b/SafeExamBrowser.UserInterface.Classic/Taskbar.xaml.cs @@ -9,6 +9,7 @@ using System.Windows; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.UserInterface.Taskbar; +using SafeExamBrowser.UserInterface.Classic.Utilities; namespace SafeExamBrowser.UserInterface.Classic { @@ -30,7 +31,7 @@ namespace SafeExamBrowser.UserInterface.Classic { if (button is UIElement) { - // TODO: ApplicationStackPanel.Children.Add(button as UIElement); + ApplicationStackPanel.Children.Add(button as UIElement); } } @@ -38,7 +39,7 @@ namespace SafeExamBrowser.UserInterface.Classic { if (button is UIElement) { - // TODO: NotificationStackPanel.Children.Add(button as UIElement); + NotificationStackPanel.Children.Add(button as UIElement); } } @@ -46,7 +47,7 @@ namespace SafeExamBrowser.UserInterface.Classic { if (control is UIElement) { - // TODO: SystemControlStackPanel.Children.Add(control as UIElement); + SystemControlStackPanel.Children.Add(control as UIElement); } } @@ -54,13 +55,11 @@ namespace SafeExamBrowser.UserInterface.Classic { return Dispatcher.Invoke(() => { - //var height = (int) this.TransformToPhysical(Width, Height).Y; + var height = (int) this.TransformToPhysical(Width, Height).Y; - //logger.Info($"Calculated physical taskbar height is {height}px."); + logger.Info($"Calculated physical taskbar height is {height}px."); - //return height; - - return 40; + return height; }); } @@ -68,26 +67,26 @@ namespace SafeExamBrowser.UserInterface.Classic { Dispatcher.Invoke(() => { - //Width = SystemParameters.WorkArea.Right; - //Left = SystemParameters.WorkArea.Right - Width; - //Top = SystemParameters.WorkArea.Bottom; + Width = SystemParameters.WorkArea.Right; + Left = SystemParameters.WorkArea.Right - Width; + Top = SystemParameters.WorkArea.Bottom; - //var position = this.TransformToPhysical(Left, Top); - //var size = this.TransformToPhysical(Width, Height); + var position = this.TransformToPhysical(Left, Top); + var size = this.TransformToPhysical(Width, Height); - //logger.Info($"Set taskbar bounds to {Width}x{Height} at ({Left}/{Top}), in physical pixels: {size.X}x{size.Y} at ({position.X}/{position.Y})."); + logger.Info($"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 Taskbar_Closing(object sender, System.ComponentModel.CancelEventArgs e) { - //foreach (var child in SystemControlStackPanel.Children) - //{ - // if (child is ISystemControl) - // { - // (child as ISystemControl).Close(); - // } - //} + foreach (var child in SystemControlStackPanel.Children) + { + if (child is ISystemControl) + { + (child as ISystemControl).Close(); + } + } } } } diff --git a/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs index edb34380..86e841b1 100644 --- a/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs +++ b/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs @@ -7,12 +7,15 @@ */ using System; +using System.Threading; +using System.Windows; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface.Taskbar; +using SafeExamBrowser.UserInterface.Classic.Controls; namespace SafeExamBrowser.UserInterface.Classic { @@ -20,42 +23,87 @@ namespace SafeExamBrowser.UserInterface.Classic { public IWindow CreateAboutWindow(ISettings settings, IText text) { + // TODO: throw new NotImplementedException(); } public IApplicationButton CreateApplicationButton(IApplicationInfo info) { - throw new NotImplementedException(); + return new ApplicationButton(info); } public IBrowserWindow CreateBrowserWindow(IBrowserControl control, IBrowserSettings settings) { - throw new NotImplementedException(); + return new BrowserWindow(control, settings); } public IWindow CreateLogWindow(ILogger logger, IText text) { + // TODO: throw new NotImplementedException(); } public INotificationButton CreateNotification(INotificationInfo info) { - throw new NotImplementedException(); + return new NotificationButton(info); } public ISystemPowerSupplyControl CreatePowerSupplyControl() { + // TODO: throw new NotImplementedException(); } public ISplashScreen CreateSplashScreen(ISettings settings, IText text) { - throw new NotImplementedException(); + SplashScreen splashScreen = null; + var splashReadyEvent = new AutoResetEvent(false); + var splashScreenThread = new Thread(() => + { + splashScreen = new SplashScreen(settings, text); + splashScreen.Closed += (o, args) => splashScreen.Dispatcher.InvokeShutdown(); + splashScreen.Show(); + + splashReadyEvent.Set(); + + System.Windows.Threading.Dispatcher.Run(); + }); + + splashScreenThread.SetApartmentState(ApartmentState.STA); + splashScreenThread.Name = nameof(SplashScreen); + splashScreenThread.IsBackground = true; + splashScreenThread.Start(); + + splashReadyEvent.WaitOne(); + + return splashScreen; } public void Show(string message, string title, MessageBoxAction action = MessageBoxAction.Confirm, MessageBoxIcon icon = MessageBoxIcon.Information) { - throw new NotImplementedException(); + MessageBox.Show(message, title, ToButton(action), ToImage(icon)); + } + + private MessageBoxButton ToButton(MessageBoxAction action) + { + switch (action) + { + default: + return MessageBoxButton.OK; + } + } + + private MessageBoxImage ToImage(MessageBoxIcon icon) + { + switch (icon) + { + case MessageBoxIcon.Warning: + return MessageBoxImage.Warning; + case MessageBoxIcon.Error: + return MessageBoxImage.Error; + default: + return MessageBoxImage.Information; + } } } } diff --git a/SafeExamBrowser.UserInterface.Classic/Utilities/IconResourceLoader.cs b/SafeExamBrowser.UserInterface.Classic/Utilities/IconResourceLoader.cs new file mode 100644 index 00000000..cedad564 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Utilities/IconResourceLoader.cs @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using SafeExamBrowser.Contracts.Configuration; + +namespace SafeExamBrowser.UserInterface.Classic.Utilities +{ + internal static class IconResourceLoader + { + internal static UIElement Load(IIconResource resource) + { + try + { + if (resource.IsBitmapResource) + { + return LoadBitmapResource(resource); + } + else if (resource.IsXamlResource) + { + return LoadXamlResource(resource); + } + } + catch (Exception) + { + return new TextBlock(new Run("X") { Foreground = Brushes.Red, FontWeight = FontWeights.Bold }); + } + + throw new NotSupportedException($"Application icon resource of type '{resource.GetType()}' is not supported!"); + } + + private static UIElement LoadBitmapResource(IIconResource resource) + { + return new Image + { + Source = new BitmapImage(resource.Uri) + }; + } + + private static UIElement LoadXamlResource(IIconResource resource) + { + using (var stream = Application.GetResourceStream(resource.Uri)?.Stream) + { + return XamlReader.Load(stream) as UIElement; + } + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/Utilities/VisualExtensions.cs b/SafeExamBrowser.UserInterface.Classic/Utilities/VisualExtensions.cs new file mode 100644 index 00000000..4e73ef67 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/Utilities/VisualExtensions.cs @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; + +namespace SafeExamBrowser.UserInterface.Classic.Utilities +{ + internal static class VisualExtensions + { + /// <summary> + /// WPF works with device-independent pixels. This method is required to + /// transform such values to their absolute, device-specific pixel value. + /// Source: https://stackoverflow.com/questions/3286175/how-do-i-convert-a-wpf-size-to-physical-pixels + /// </summary> + internal static Vector TransformToPhysical(this Visual visual, double x, double y) + { + Matrix transformToDevice; + var source = PresentationSource.FromVisual(visual); + + if (source != null) + { + transformToDevice = source.CompositionTarget.TransformToDevice; + } + else + { + using (var newSource = new HwndSource(new HwndSourceParameters())) + { + transformToDevice = newSource.CompositionTarget.TransformToDevice; + } + } + + return transformToDevice.Transform(new Vector(x, y)); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/ViewModels/DateTimeViewModel.cs b/SafeExamBrowser.UserInterface.Classic/ViewModels/DateTimeViewModel.cs new file mode 100644 index 00000000..df0c7fc7 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/ViewModels/DateTimeViewModel.cs @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; +using System.ComponentModel; +using System.Timers; + +namespace SafeExamBrowser.UserInterface.Classic.ViewModels +{ + class DateTimeViewModel : INotifyPropertyChanged + { + private Timer timer; + + public string Date { get; private set; } + public string Time { get; private set; } + public string ToolTip { get; private set; } + + public event PropertyChangedEventHandler PropertyChanged; + + public DateTimeViewModel() + { + timer = new Timer(1000); + timer.Elapsed += Timer_Elapsed; + timer.Start(); + } + + private void Timer_Elapsed(object sender, ElapsedEventArgs e) + { + var date = DateTime.Now; + + Date = date.ToShortDateString(); + Time = date.ToShortTimeString(); + ToolTip = $"{date.ToLongDateString()} {date.ToLongTimeString()}"; + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Time))); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Date))); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ToolTip))); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/ViewModels/LogViewModel.cs b/SafeExamBrowser.UserInterface.Classic/ViewModels/LogViewModel.cs new file mode 100644 index 00000000..0caa53d6 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/ViewModels/LogViewModel.cs @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; +using SafeExamBrowser.Contracts.I18n; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.UserInterface.Classic.ViewModels +{ + internal class LogViewModel : ILogObserver + { + private IText text; + private ScrollViewer scrollViewer; + private TextBlock textBlock; + + public string WindowTitle => text.Get(TextKey.LogWindow_Title); + + public LogViewModel(IText text, ScrollViewer scrollViewer, TextBlock textBlock) + { + this.text = text; + this.scrollViewer = scrollViewer; + this.textBlock = textBlock; + } + + public void Notify(ILogContent content) + { + if (content is ILogText) + { + AppendLogText(content as ILogText); + } + else if (content is ILogMessage) + { + AppendLogMessage(content as ILogMessage); + } + else + { + throw new NotImplementedException($"The default formatter is not yet implemented for log content of type {content.GetType()}!"); + } + + scrollViewer.Dispatcher.Invoke(scrollViewer.ScrollToEnd); + } + + private void AppendLogText(ILogText logText) + { + textBlock.Dispatcher.Invoke(() => + { + var isHeader = logText.Text.StartsWith("/* "); + var isComment = logText.Text.StartsWith("# "); + var brush = isHeader || isComment ? Brushes.ForestGreen : textBlock.Foreground; + + textBlock.Inlines.Add(new Run($"{logText.Text}{Environment.NewLine}") + { + FontWeight = isHeader ? FontWeights.Bold : FontWeights.Normal, + Foreground = brush + }); + }); + } + + private void AppendLogMessage(ILogMessage message) + { + textBlock.Dispatcher.Invoke(() => + { + var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"); + var severity = message.Severity.ToString().ToUpper(); + var threadInfo = $"{message.ThreadInfo.Id}{(message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty)}"; + + var infoRun = new Run($"{date} [{threadInfo}] - ") { Foreground = Brushes.Gray }; + var messageRun = new Run($"{severity}: {message.Message}{Environment.NewLine}") { Foreground = GetBrushFor(message.Severity) }; + + textBlock.Inlines.Add(infoRun); + textBlock.Inlines.Add(messageRun); + }); + } + + private Brush GetBrushFor(LogLevel severity) + { + switch (severity) + { + case LogLevel.Error: + return Brushes.Red; + case LogLevel.Warning: + return Brushes.Yellow; + default: + return Brushes.White; + } + } + } +} diff --git a/SafeExamBrowser.UserInterface.Classic/ViewModels/SplashScreenViewModel.cs b/SafeExamBrowser.UserInterface.Classic/ViewModels/SplashScreenViewModel.cs new file mode 100644 index 00000000..9453887f --- /dev/null +++ b/SafeExamBrowser.UserInterface.Classic/ViewModels/SplashScreenViewModel.cs @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System.ComponentModel; +using System.Timers; + +namespace SafeExamBrowser.UserInterface.Classic.ViewModels +{ + class SplashScreenViewModel : INotifyPropertyChanged + { + private int currentProgress; + private bool isIndeterminate; + private int maxProgress; + private string status; + private Timer busyTimer; + + public event PropertyChangedEventHandler PropertyChanged; + + public int CurrentProgress + { + get + { + return currentProgress; + } + set + { + currentProgress = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentProgress))); + } + } + + public bool IsIndeterminate + { + get + { + return isIndeterminate; + } + set + { + isIndeterminate = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsIndeterminate))); + } + } + + public int MaxProgress + { + get + { + return maxProgress; + } + set + { + maxProgress = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MaxProgress))); + } + } + + public string Status + { + get + { + return status; + } + set + { + status = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status))); + } + } + + public void StartBusyIndication() + { + StopBusyIndication(); + + busyTimer = new Timer + { + AutoReset = true, + Interval = 750 + }; + + busyTimer.Elapsed += BusyTimer_Elapsed; + busyTimer.Start(); + } + + public void StopBusyIndication() + { + busyTimer?.Stop(); + busyTimer?.Close(); + } + + private void BusyTimer_Elapsed(object sender, ElapsedEventArgs e) + { + var next = Status ?? string.Empty; + + if (next.EndsWith("...")) + { + next = Status.Substring(0, Status.Length - 3); + } + else + { + next += "."; + } + + Status = next; + } + } +}