Brought basic functionality of new (classic) UI to life.

This commit is contained in:
dbuechel 2017-08-22 10:29:00 +02:00
parent 080bf4e5ff
commit 862d00b07a
28 changed files with 1112 additions and 33 deletions

View file

@ -61,10 +61,11 @@ namespace SafeExamBrowser.Core.Behaviour.Operations
CreateAboutNotification();
if (systemInfo.HasBattery)
{
CreatePowerSupplyComponent();
}
// TODO:
//if (systemInfo.HasBattery)
//{
// CreatePowerSupplyComponent();
//}
}
public void Revert()

View file

@ -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>

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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;
}
}
}
}

View file

@ -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>

View file

@ -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);
}
}
}

View file

@ -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>

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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);
}
}
}

View file

@ -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>

View file

@ -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();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -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>

View file

@ -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>

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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();
}
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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));
}
}
}

View file

@ -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)));
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}