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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
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 instances = new List();
+
+ 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 @@
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
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 @@
+
+
+
+
+
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 @@
+
4.0
+
+
+
+ BrowserWindow.xaml
+
+
+ ApplicationButton.xaml
+
+
+ ApplicationInstanceButton.xaml
+
+
+ DateTimeControl.xaml
+
+
+ NotificationButton.xaml
+
+
+ QuitButton.xaml
+
+
+ SplashScreen.xaml
+
+
+
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
@@ -99,5 +156,17 @@
SafeExamBrowser.Contracts
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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">
-
+
+
+
+
+
+
+
+
+
+ 5
+
+
+
+
+
+
+
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
+ {
+ ///
+ /// 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
+ ///
+ 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;
+ }
+ }
+}