SEBWIN-308: Implemented basic download overview for browser.

This commit is contained in:
dbuechel 2020-01-22 15:16:11 +01:00
parent b9536c6a1b
commit 97f3fb4a02
30 changed files with 582 additions and 133 deletions

View file

@ -23,6 +23,7 @@ using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser.Proxy; using SafeExamBrowser.Settings.Browser.Proxy;
using SafeExamBrowser.Settings.Logging; using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.MessageBox;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings; using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
@ -34,6 +35,7 @@ namespace SafeExamBrowser.Browser
private AppConfig appConfig; private AppConfig appConfig;
private List<BrowserApplicationInstance> instances; private List<BrowserApplicationInstance> instances;
private IFileSystemDialog fileSystemDialog;
private IMessageBox messageBox; private IMessageBox messageBox;
private IModuleLogger logger; private IModuleLogger logger;
private BrowserSettings settings; private BrowserSettings settings;
@ -53,12 +55,14 @@ namespace SafeExamBrowser.Browser
public BrowserApplication( public BrowserApplication(
AppConfig appConfig, AppConfig appConfig,
BrowserSettings settings, BrowserSettings settings,
IFileSystemDialog fileSystemDialog,
IMessageBox messageBox, IMessageBox messageBox,
IModuleLogger logger, IModuleLogger logger,
IText text, IText text,
IUserInterfaceFactory uiFactory) IUserInterfaceFactory uiFactory)
{ {
this.appConfig = appConfig; this.appConfig = appConfig;
this.fileSystemDialog = fileSystemDialog;
this.instances = new List<BrowserApplicationInstance>(); this.instances = new List<BrowserApplicationInstance>();
this.logger = logger; this.logger = logger;
this.messageBox = messageBox; this.messageBox = messageBox;
@ -122,7 +126,7 @@ namespace SafeExamBrowser.Browser
var isMainInstance = instances.Count == 0; var isMainInstance = instances.Count == 0;
var instanceLogger = logger.CloneFor($"Browser Instance #{id}"); var instanceLogger = logger.CloneFor($"Browser Instance #{id}");
var startUrl = url ?? settings.StartUrl; var startUrl = url ?? settings.StartUrl;
var instance = new BrowserApplicationInstance(appConfig, settings, id, isMainInstance, messageBox, instanceLogger, text, uiFactory, startUrl); var instance = new BrowserApplicationInstance(appConfig, settings, id, isMainInstance, fileSystemDialog, messageBox, instanceLogger, text, uiFactory, startUrl);
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args); instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
instance.PopupRequested += Instance_PopupRequested; instance.PopupRequested += Instance_PopupRequested;

View file

@ -24,6 +24,8 @@ using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Filter; using SafeExamBrowser.Settings.Browser.Filter;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.MessageBox;
namespace SafeExamBrowser.Browser namespace SafeExamBrowser.Browser
@ -37,6 +39,7 @@ namespace SafeExamBrowser.Browser
private IBrowserWindow window; private IBrowserWindow window;
private HttpClient httpClient; private HttpClient httpClient;
private bool isMainInstance; private bool isMainInstance;
private IFileSystemDialog fileSystemDialog;
private IMessageBox messageBox; private IMessageBox messageBox;
private IModuleLogger logger; private IModuleLogger logger;
private BrowserSettings settings; private BrowserSettings settings;
@ -69,6 +72,7 @@ namespace SafeExamBrowser.Browser
BrowserSettings settings, BrowserSettings settings,
int id, int id,
bool isMainInstance, bool isMainInstance,
IFileSystemDialog fileSystemDialog,
IMessageBox messageBox, IMessageBox messageBox,
IModuleLogger logger, IModuleLogger logger,
IText text, IText text,
@ -79,6 +83,7 @@ namespace SafeExamBrowser.Browser
this.Id = id; this.Id = id;
this.httpClient = new HttpClient(); this.httpClient = new HttpClient();
this.isMainInstance = isMainInstance; this.isMainInstance = isMainInstance;
this.fileSystemDialog = fileSystemDialog;
this.messageBox = messageBox; this.messageBox = messageBox;
this.logger = logger; this.logger = logger;
this.settings = settings; this.settings = settings;
@ -122,6 +127,7 @@ namespace SafeExamBrowser.Browser
displayHandler.FaviconChanged += DisplayHandler_FaviconChanged; displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
displayHandler.ProgressChanged += DisplayHandler_ProgressChanged; displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested; downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
downloadHandler.DownloadUpdated += DownloadHandler_DownloadUpdated;
keyboardHandler.ReloadRequested += ReloadRequested; keyboardHandler.ReloadRequested += ReloadRequested;
keyboardHandler.ZoomInRequested += ZoomInRequested; keyboardHandler.ZoomInRequested += ZoomInRequested;
keyboardHandler.ZoomOutRequested += ZoomOutRequested; keyboardHandler.ZoomOutRequested += ZoomOutRequested;
@ -211,8 +217,7 @@ namespace SafeExamBrowser.Browser
private void DialogHandler_DialogRequested(DialogRequestedEventArgs args) private void DialogHandler_DialogRequested(DialogRequestedEventArgs args)
{ {
var dialog = uiFactory.CreateFileSystemDialog(args.Element, args.InitialPath, args.Operation, title: args.Title); var result = fileSystemDialog.Show(args.Element, args.InitialPath, args.Operation, title: args.Title, owner: window);
var result = dialog.Show(window);
if (result.Success) if (result.Success)
{ {
@ -269,6 +274,11 @@ namespace SafeExamBrowser.Browser
} }
} }
private void DownloadHandler_DownloadUpdated(DownloadItemState state)
{
window.UpdateDownloadState(state);
}
private void LifeSpanHandler_PopupRequested(PopupRequestedEventArgs args) private void LifeSpanHandler_PopupRequested(PopupRequestedEventArgs args)
{ {
var validCurrentUri = Uri.TryCreate(control.Address, UriKind.Absolute, out var currentUri); var validCurrentUri = Uri.TryCreate(control.Address, UriKind.Absolute, out var currentUri);

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2020 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 SafeExamBrowser.UserInterface.Contracts.Browser.Data;
namespace SafeExamBrowser.Browser.Events
{
internal delegate void DownloadUpdatedEventHandler(DownloadItemState state);
}

View file

@ -12,8 +12,10 @@ using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using CefSharp; using CefSharp;
using SafeExamBrowser.Browser.Contracts.Events; using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using Syroot.Windows.IO; using Syroot.Windows.IO;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings; using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
@ -24,14 +26,17 @@ namespace SafeExamBrowser.Browser.Handlers
private AppConfig appConfig; private AppConfig appConfig;
private BrowserSettings settings; private BrowserSettings settings;
private ConcurrentDictionary<int, DownloadFinishedCallback> callbacks; private ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
private ConcurrentDictionary<int, Guid> downloads;
private ILogger logger; private ILogger logger;
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested; internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
internal event DownloadUpdatedEventHandler DownloadUpdated;
internal DownloadHandler(AppConfig appConfig, BrowserSettings settings, ILogger logger) internal DownloadHandler(AppConfig appConfig, BrowserSettings settings, ILogger logger)
{ {
this.appConfig = appConfig; this.appConfig = appConfig;
this.callbacks = new ConcurrentDictionary<int, DownloadFinishedCallback>(); this.callbacks = new ConcurrentDictionary<int, DownloadFinishedCallback>();
this.downloads = new ConcurrentDictionary<int, Guid>();
this.logger = logger; this.logger = logger;
this.settings = settings; this.settings = settings;
} }
@ -60,18 +65,35 @@ namespace SafeExamBrowser.Browser.Handlers
public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback) public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
{ {
// TODO: Show download progress in respective window -> event for BrowserApplicationInstance! var hasId = downloads.TryGetValue(downloadItem.Id, out var id);
if (hasId)
{
var state = new DownloadItemState(id)
{
Completion = downloadItem.PercentComplete / 100.0,
FullPath = downloadItem.FullPath,
IsCancelled = downloadItem.IsCancelled,
IsComplete = downloadItem.IsComplete,
Url = downloadItem.Url
};
Task.Run(() => DownloadUpdated?.Invoke(state));
}
if (downloadItem.IsComplete || downloadItem.IsCancelled) if (downloadItem.IsComplete || downloadItem.IsCancelled)
{ {
logger.Debug($"Download of '{downloadItem.Url}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}.");
if (callbacks.TryRemove(downloadItem.Id, out DownloadFinishedCallback finished) && finished != null) if (callbacks.TryRemove(downloadItem.Id, out DownloadFinishedCallback finished) && finished != null)
{ {
Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.FullPath)); Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.FullPath));
} }
logger.Debug($"Download of '{downloadItem.Url}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}."); if (hasId)
{
// TODO: Show success message or download icon like Firefox in respective window! downloads.TryRemove(downloadItem.Id, out _);
}
} }
} }
@ -100,6 +122,8 @@ namespace SafeExamBrowser.Browser.Handlers
logger.Debug($"Automatically downloading file as '{filePath}'."); logger.Debug($"Automatically downloading file as '{filePath}'.");
} }
downloads[downloadItem.Id] = Guid.NewGuid();
using (callback) using (callback)
{ {
callback.Continue(filePath, showDialog); callback.Continue(filePath, showDialog);

View file

@ -68,6 +68,7 @@
<Compile Include="BrowserApplicationInstance.cs" /> <Compile Include="BrowserApplicationInstance.cs" />
<Compile Include="Events\DialogRequestedEventArgs.cs" /> <Compile Include="Events\DialogRequestedEventArgs.cs" />
<Compile Include="Events\DialogRequestedEventHandler.cs" /> <Compile Include="Events\DialogRequestedEventHandler.cs" />
<Compile Include="Events\DownloadUpdatedEventHandler.cs" />
<Compile Include="Events\FaviconChangedEventHandler.cs" /> <Compile Include="Events\FaviconChangedEventHandler.cs" />
<Compile Include="Events\InstanceTerminatedEventHandler.cs" /> <Compile Include="Events\InstanceTerminatedEventHandler.cs" />
<Compile Include="Events\PopupRequestedEventArgs.cs" /> <Compile Include="Events\PopupRequestedEventArgs.cs" />

View file

@ -42,6 +42,7 @@ using SafeExamBrowser.SystemComponents.Keyboard;
using SafeExamBrowser.SystemComponents.PowerSupply; using SafeExamBrowser.SystemComponents.PowerSupply;
using SafeExamBrowser.SystemComponents.WirelessNetwork; using SafeExamBrowser.SystemComponents.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.MessageBox;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Shared.Activators; using SafeExamBrowser.UserInterface.Shared.Activators;
@ -197,8 +198,9 @@ namespace SafeExamBrowser.Client
private IOperation BuildBrowserOperation() private IOperation BuildBrowserOperation()
{ {
var fileSystemDialog = BuildFileSystemDialog();
var moduleLogger = ModuleLogger(nameof(BrowserApplication)); var moduleLogger = ModuleLogger(nameof(BrowserApplication));
var browser = new BrowserApplication(context.AppConfig, context.Settings.Browser, messageBox, moduleLogger, text, uiFactory); var browser = new BrowserApplication(context.AppConfig, context.Settings.Browser, fileSystemDialog, messageBox, moduleLogger, text, uiFactory);
var operation = new BrowserOperation(actionCenter, context, logger, taskbar, taskview, uiFactory); var operation = new BrowserOperation(actionCenter, context, logger, taskbar, taskview, uiFactory);
context.Browser = browser; context.Browser = browser;
@ -282,6 +284,17 @@ namespace SafeExamBrowser.Client
} }
} }
private IFileSystemDialog BuildFileSystemDialog()
{
switch (uiMode)
{
case UserInterfaceMode.Mobile:
return new Mobile.FileSystemDialogFactory(text);
default:
return new Desktop.FileSystemDialogFactory(text);
}
}
private IMessageBox BuildMessageBox() private IMessageBox BuildMessageBox()
{ {
switch (uiMode) switch (uiMode)

View file

@ -22,6 +22,9 @@ namespace SafeExamBrowser.I18n.Contracts
Browser_Name, Browser_Name,
Browser_Tooltip, Browser_Tooltip,
BrowserWindow_DeveloperConsoleMenuItem, BrowserWindow_DeveloperConsoleMenuItem,
BrowserWindow_Downloading,
BrowserWindow_DownloadCancelled,
BrowserWindow_DownloadComplete,
BrowserWindow_ZoomMenuItem, BrowserWindow_ZoomMenuItem,
Build, Build,
FileSystemDialog_Cancel, FileSystemDialog_Cancel,

View file

@ -24,6 +24,15 @@
<Entry key="BrowserWindow_DeveloperConsoleMenuItem"> <Entry key="BrowserWindow_DeveloperConsoleMenuItem">
Developer Console Developer Console
</Entry> </Entry>
<Entry key="BrowserWindow_Downloading">
Downloading...
</Entry>
<Entry key="BrowserWindow_DownloadCancelled">
Cancelled.
</Entry>
<Entry key="BrowserWindow_DownloadComplete">
Downloaded.
</Entry>
<Entry key="BrowserWindow_ZoomMenuItem"> <Entry key="BrowserWindow_ZoomMenuItem">
Page Zoom Page Zoom
</Entry> </Entry>

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2020 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;
namespace SafeExamBrowser.UserInterface.Contracts.Browser.Data
{
/// <summary>
/// Defines the state of a download item.
/// </summary>
public class DownloadItemState
{
/// <summary>
/// The current completion of the item, as percentage value from <c>0.0</c> to <c>1.0</c>.
/// </summary>
public double Completion { get; set; }
/// <summary>
/// The full path of the download location for the item.
/// </summary>
public string FullPath { get; set; }
/// <summary>
/// The unique identifier of the item.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Indicates that the download was cancelled.
/// </summary>
public bool IsCancelled { get; set; }
/// <summary>
/// Indicates that the download was completed.
/// </summary>
public bool IsComplete { get; set; }
/// <summary>
/// The download URL of the item.
/// </summary>
public string Url { get; set; }
public DownloadItemState(Guid id)
{
Id = id;
}
}
}

View file

@ -8,6 +8,7 @@
using System; using System;
using SafeExamBrowser.Applications.Contracts.Resources.Icons; using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
@ -83,6 +84,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
/// </summary> /// </summary>
void UpdateIcon(IconResource icon); void UpdateIcon(IconResource icon);
/// <summary>
/// Updates the download state for the given item.
/// </summary>
void UpdateDownloadState(DownloadItemState state);
/// <summary> /// <summary>
/// Updates the loading state according to the given value. /// Updates the loading state according to the given value.
/// </summary> /// </summary>

View file

@ -13,8 +13,8 @@ namespace SafeExamBrowser.UserInterface.Contracts.FileSystemDialog
public interface IFileSystemDialog public interface IFileSystemDialog
{ {
/// <summary> /// <summary>
/// Shows the dialog to the user. If a parent window is specified, the dialog is rendered modally for the given parent. /// Creates a dialog according to the given parameters and shows it to the user.
/// </summary> /// </summary>
FileSystemDialogResult Show(IWindow parent = null); FileSystemDialogResult Show(FileSystemElement element, string initialPath, FileSystemOperation operation, string message = default(string), string title = default(string), IWindow owner = default(IWindow));
} }
} }

View file

@ -18,7 +18,6 @@ using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply; using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data; using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
@ -50,11 +49,6 @@ namespace SafeExamBrowser.UserInterface.Contracts
/// </summary> /// </summary>
IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow); IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow);
/// <summary>
/// Creates a file system dialog according to the given parameters.
/// </summary>
IFileSystemDialog CreateFileSystemDialog(FileSystemElement element, string initialPath, FileSystemOperation operation, string message = default(string), string title = default(string));
/// <summary> /// <summary>
/// Creates a folder dialog with the given message. /// Creates a folder dialog with the given message.
/// </summary> /// </summary>

View file

@ -54,6 +54,7 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Browser\Data\DownloadItemState.cs" />
<Compile Include="Browser\Events\AddressChangedEventHandler.cs" /> <Compile Include="Browser\Events\AddressChangedEventHandler.cs" />
<Compile Include="Browser\Events\LoadingStateChangedEventHandler.cs" /> <Compile Include="Browser\Events\LoadingStateChangedEventHandler.cs" />
<Compile Include="Browser\Events\TitleChangedEventHandler.cs" /> <Compile Include="Browser\Events\TitleChangedEventHandler.cs" />

View file

@ -33,12 +33,21 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Grid.Column="0" x:Name="BackwardButton" Height="30" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" /> <Button Grid.Column="0" x:Name="BackwardButton" Height="30" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
<Button Grid.Column="1" x:Name="ForwardButton" Height="30" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" /> <Button Grid.Column="1" x:Name="ForwardButton" Height="30" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
<Button Grid.Column="2" x:Name="ReloadButton" Height="30" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" /> <Button Grid.Column="2" x:Name="ReloadButton" Height="30" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
<TextBox Grid.Column="3" x:Name="UrlTextBox" Height="25" HorizontalAlignment="Stretch" Margin="5,0" Padding="5,0" VerticalContentAlignment="Center" /> <TextBox Grid.Column="3" x:Name="UrlTextBox" Height="25" HorizontalAlignment="Stretch" Margin="5,0" Padding="5,0" VerticalContentAlignment="Center" />
<Button Grid.Column="4" x:Name="MenuButton" Height="30" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" /> <Button Grid.Column="4" x:Name="DownloadsButton" Height="30" HorizontalAlignment="Center" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" Visibility="Collapsed">
<fa:ImageAwesome Icon="Download" />
</Button>
<Popup x:Name="DownloadsPopup" AllowsTransparency="True" PopupAnimation="Slide" Placement="Custom" PlacementTarget="{Binding ElementName=BrowserControlHost}">
<Border Background="{StaticResource BackgroundBrush}" BorderBrush="LightGray" BorderThickness="1,0,1,1" MinWidth="250">
<StackPanel x:Name="Downloads" Orientation="Vertical" />
</Border>
</Popup>
<Button Grid.Column="5" x:Name="MenuButton" Height="30" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
<Popup x:Name="MenuPopup" IsOpen="False" AllowsTransparency="True" PopupAnimation="Slide" Placement="Custom" PlacementTarget="{Binding ElementName=BrowserControlHost}"> <Popup x:Name="MenuPopup" IsOpen="False" AllowsTransparency="True" PopupAnimation="Slide" Placement="Custom" PlacementTarget="{Binding ElementName=BrowserControlHost}">
<Border Background="{StaticResource BackgroundBrush}" BorderBrush="LightGray" BorderThickness="1,0,1,1" Width="250"> <Border Background="{StaticResource BackgroundBrush}" BorderBrush="LightGray" BorderThickness="1,0,1,1" Width="250">
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">

View file

@ -13,16 +13,17 @@ using System.Windows;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using SafeExamBrowser.Applications.Contracts.Resources.Icons; using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events; using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
using SafeExamBrowser.UserInterface.Desktop.Controls.Browser;
using SafeExamBrowser.UserInterface.Shared.Utilities; using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop namespace SafeExamBrowser.UserInterface.Desktop
@ -117,6 +118,36 @@ namespace SafeExamBrowser.UserInterface.Desktop
}); });
} }
public void UpdateDownloadState(DownloadItemState state)
{
Dispatcher.InvokeAsync(() =>
{
var isNewItem = true;
foreach (var child in Downloads.Children)
{
if (child is DownloadItemControl control && control.Id == state.Id)
{
control.Update(state);
isNewItem = false;
break;
}
}
if (isNewItem)
{
var control = new DownloadItemControl(state.Id, text);
control.Update(state);
Downloads.Children.Add(control);
}
DownloadsButton.Visibility = Visibility.Visible;
DownloadsPopup.IsOpen = IsActive;
});
}
public void UpdateLoadingState(bool isLoading) public void UpdateLoadingState(bool isLoading)
{ {
Dispatcher.Invoke(() => ProgressBar.Visibility = isLoading ? Visibility.Visible : Visibility.Hidden); Dispatcher.Invoke(() => ProgressBar.Visibility = isLoading ? Visibility.Visible : Visibility.Hidden);
@ -167,7 +198,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
} }
} }
private CustomPopupPlacement[] MenuPopup_PlacementCallback(Size popupSize, Size targetSize, Point offset) private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
{ {
return new[] return new[]
{ {
@ -216,21 +247,23 @@ namespace SafeExamBrowser.UserInterface.Desktop
private void RegisterEvents() private void RegisterEvents()
{ {
var originalBrush = MenuButton.Background;
BackwardButton.Click += (o, args) => BackwardNavigationRequested?.Invoke(); BackwardButton.Click += (o, args) => BackwardNavigationRequested?.Invoke();
Closing += BrowserWindow_Closing; Closing += BrowserWindow_Closing;
DeveloperConsoleButton.Click += (o, args) => DeveloperConsoleRequested?.Invoke(); DeveloperConsoleButton.Click += (o, args) => DeveloperConsoleRequested?.Invoke();
DownloadsButton.Click += (o, args) => DownloadsPopup.IsOpen = !DownloadsPopup.IsOpen;
DownloadsButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => DownloadsPopup.IsOpen = DownloadsPopup.IsMouseOver));
DownloadsPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
DownloadsPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => DownloadsPopup.IsOpen = DownloadsPopup.IsMouseOver));
ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke(); ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke();
Loaded += BrowserWindow_Loaded; Loaded += BrowserWindow_Loaded;
MenuButton.Click += (o, args) => MenuPopup.IsOpen = !MenuPopup.IsOpen; MenuButton.Click += (o, args) => MenuPopup.IsOpen = !MenuPopup.IsOpen;
MenuButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver)); MenuButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver));
MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(MenuPopup_PlacementCallback); MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
MenuPopup.Closed += (o, args) => MenuButton.Background = originalBrush; MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver));
MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = IsMouseOver));
MenuPopup.Opened += (o, args) => MenuButton.Background = Brushes.LightGray;
KeyUp += BrowserWindow_KeyUp; KeyUp += BrowserWindow_KeyUp;
LocationChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; };
ReloadButton.Click += (o, args) => ReloadRequested?.Invoke(); ReloadButton.Click += (o, args) => ReloadRequested?.Invoke();
SizeChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; };
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged; SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
UrlTextBox.GotKeyboardFocus += (o, args) => UrlTextBox.SelectAll(); UrlTextBox.GotKeyboardFocus += (o, args) => UrlTextBox.SelectAll();
UrlTextBox.GotMouseCapture += UrlTextBox_GotMouseCapture; UrlTextBox.GotMouseCapture += UrlTextBox_GotMouseCapture;

View file

@ -0,0 +1,22 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Browser.DownloadItemControl"
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.Desktop.Controls.Browser"
mc:Ignorable="d" d:DesignHeight="50" d:DesignWidth="200">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Panel.ZIndex="2" Name="Icon" Margin="10" Width="25" />
<ProgressBar Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Grid.ColumnSpan="2" Panel.ZIndex="1" Name="Progress" BorderThickness="0" />
<TextBlock Grid.Row="0" Grid.Column="1" Panel.ZIndex="2" Name="ItemName" FontWeight="Bold" Margin="0,10,10,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Panel.ZIndex="2" Name="Status" FontStyle="Italic" Margin="0,0,10,10" />
</Grid>
</UserControl>

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2020 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.IO;
using System.Windows.Controls;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Browser
{
public partial class DownloadItemControl : UserControl
{
private IText text;
public Guid Id { get; }
public DownloadItemControl(Guid id, IText text)
{
this.Id = id;
this.text = text;
InitializeComponent();
}
public void Update(DownloadItemState state)
{
ItemName.Text = Uri.TryCreate(state.Url, UriKind.Absolute, out var uri) ? Path.GetFileName(uri.AbsolutePath) : state.Url;
Progress.Value = state.Completion * 100;
Status.Text = $"{text.Get(TextKey.BrowserWindow_Downloading)} ({state.Completion * 100}%)";
if (File.Exists(state.FullPath))
{
ItemName.Text = Path.GetFileName(state.FullPath);
Icon.Content = new Image { Source = IconLoader.LoadIconFor(new FileInfo(state.FullPath)) };
}
if (state.IsCancelled)
{
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = text.Get(TextKey.BrowserWindow_DownloadCancelled);
}
else if (state.IsComplete)
{
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = text.Get(TextKey.BrowserWindow_DownloadComplete);
}
}
}
}

View file

@ -5,7 +5,7 @@
xmlns:fa="http://schemas.fontawesome.io/icons/" xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop"
mc:Ignorable="d" Height="500" Width="750" ResizeMode="NoResize"> mc:Ignorable="d" Height="500" Width="750" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Window.Resources> <Window.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>

View file

@ -22,12 +22,13 @@ using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop namespace SafeExamBrowser.UserInterface.Desktop
{ {
public partial class FileSystemDialog : Window, IFileSystemDialog public partial class FileSystemDialog : Window
{ {
private FileSystemElement element; private FileSystemElement element;
private string initialPath; private string initialPath;
private string message; private string message;
private FileSystemOperation operation; private FileSystemOperation operation;
private IWindow parent;
private IText text; private IText text;
private string title; private string title;
@ -37,12 +38,14 @@ namespace SafeExamBrowser.UserInterface.Desktop
FileSystemOperation operation, FileSystemOperation operation,
IText text, IText text,
string message = default(string), string message = default(string),
string title = default(string)) string title = default(string),
IWindow parent = default(IWindow))
{ {
this.element = element; this.element = element;
this.initialPath = initialPath; this.initialPath = initialPath;
this.message = message; this.message = message;
this.operation = operation; this.operation = operation;
this.parent = parent;
this.text = text; this.text = text;
this.title = title; this.title = title;
@ -50,26 +53,23 @@ namespace SafeExamBrowser.UserInterface.Desktop
InitializeDialog(); InitializeDialog();
} }
public FileSystemDialogResult Show(IWindow parent = null) internal new FileSystemDialogResult Show()
{ {
return Dispatcher.Invoke(() => var result = new FileSystemDialogResult();
if (parent is Window)
{ {
var result = new FileSystemDialogResult(); Owner = parent as Window;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
if (parent is Window) if (ShowDialog() == true)
{ {
Owner = parent as Window; result.FullPath = BuildFullPath();
WindowStartupLocation = WindowStartupLocation.CenterOwner; result.Success = true;
} }
if (ShowDialog() == true) return result;
{
result.FullPath = BuildFullPath();
result.Success = true;
}
return result;
});
} }
private void CancelButton_Click(object sender, RoutedEventArgs e) private void CancelButton_Click(object sender, RoutedEventArgs e)

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2020 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 SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.Windows;
namespace SafeExamBrowser.UserInterface.Desktop
{
public class FileSystemDialogFactory : IFileSystemDialog
{
private IText text;
public FileSystemDialogFactory(IText text)
{
this.text = text;
}
public FileSystemDialogResult Show(FileSystemElement element, string initialPath, FileSystemOperation operation, string message = null, string title = null, IWindow owner = null)
{
if (owner is Window window)
{
return window.Dispatcher.Invoke(() => new FileSystemDialog(element, initialPath, operation, text, message, title, owner).Show());
}
else
{
return new FileSystemDialog(element, initialPath, operation, text, message, title).Show();
}
}
}
}

View file

@ -109,6 +109,9 @@
<Compile Include="Controls\ActionCenterWirelessNetworkControl.xaml.cs"> <Compile Include="Controls\ActionCenterWirelessNetworkControl.xaml.cs">
<DependentUpon>ActionCenterWirelessNetworkControl.xaml</DependentUpon> <DependentUpon>ActionCenterWirelessNetworkControl.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Controls\Browser\DownloadItemControl.xaml.cs">
<DependentUpon>DownloadItemControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarApplicationControl.xaml.cs"> <Compile Include="Controls\TaskbarApplicationControl.xaml.cs">
<DependentUpon>TaskbarApplicationControl.xaml</DependentUpon> <DependentUpon>TaskbarApplicationControl.xaml</DependentUpon>
</Compile> </Compile>
@ -148,6 +151,7 @@
<Compile Include="FileSystemDialog.xaml.cs"> <Compile Include="FileSystemDialog.xaml.cs">
<DependentUpon>FileSystemDialog.xaml</DependentUpon> <DependentUpon>FileSystemDialog.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="FileSystemDialogFactory.cs" />
<Compile Include="FolderDialog.cs" /> <Compile Include="FolderDialog.cs" />
<Compile Include="LockScreen.xaml.cs"> <Compile Include="LockScreen.xaml.cs">
<DependentUpon>LockScreen.xaml</DependentUpon> <DependentUpon>LockScreen.xaml</DependentUpon>
@ -229,6 +233,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Controls\Browser\DownloadItemControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\TaskbarApplicationControl.xaml"> <Page Include="Controls\TaskbarApplicationControl.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View file

@ -23,7 +23,6 @@ using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data; using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
@ -76,11 +75,6 @@ namespace SafeExamBrowser.UserInterface.Desktop
return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text)); return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text));
} }
public IFileSystemDialog CreateFileSystemDialog(FileSystemElement element, string initialPath, FileSystemOperation operation, string message = default(string), string title = default(string))
{
return Application.Current.Dispatcher.Invoke(() => new FileSystemDialog(element, initialPath, operation, text, message, title));
}
public IFolderDialog CreateFolderDialog(string message) public IFolderDialog CreateFolderDialog(string message)
{ {
return new FolderDialog(message); return new FolderDialog(message);
@ -105,26 +99,26 @@ namespace SafeExamBrowser.UserInterface.Desktop
public IWindow CreateLogWindow(ILogger logger) public IWindow CreateLogWindow(ILogger logger)
{ {
LogWindow logWindow = null; var window = default(LogWindow);
var logWindowReadyEvent = new AutoResetEvent(false); var windowReadyEvent = new AutoResetEvent(false);
var logWindowThread = new Thread(() => var windowThread = new Thread(() =>
{ {
logWindow = new LogWindow(logger, text); window = new LogWindow(logger, text);
logWindow.Closed += (o, args) => logWindow.Dispatcher.InvokeShutdown(); window.Closed += (o, args) => window.Dispatcher.InvokeShutdown();
logWindow.Show(); window.Show();
logWindowReadyEvent.Set(); windowReadyEvent.Set();
System.Windows.Threading.Dispatcher.Run(); System.Windows.Threading.Dispatcher.Run();
}); });
logWindowThread.SetApartmentState(ApartmentState.STA); windowThread.SetApartmentState(ApartmentState.STA);
logWindowThread.IsBackground = true; windowThread.IsBackground = true;
logWindowThread.Start(); windowThread.Start();
logWindowReadyEvent.WaitOne(); windowReadyEvent.WaitOne();
return logWindow; return window;
} }
public INotificationControl CreateNotificationControl(INotificationController controller, INotificationInfo info, Location location) public INotificationControl CreateNotificationControl(INotificationController controller, INotificationInfo info, Location location)
@ -168,27 +162,26 @@ namespace SafeExamBrowser.UserInterface.Desktop
public ISplashScreen CreateSplashScreen(AppConfig appConfig = null) public ISplashScreen CreateSplashScreen(AppConfig appConfig = null)
{ {
SplashScreen splashScreen = null; var window = default(SplashScreen);
var splashReadyEvent = new AutoResetEvent(false); var windowReadyEvent = new AutoResetEvent(false);
var splashScreenThread = new Thread(() => var windowThread = new Thread(() =>
{ {
splashScreen = new SplashScreen(text, appConfig); window = new SplashScreen(text, appConfig);
splashScreen.Closed += (o, args) => splashScreen.Dispatcher.InvokeShutdown(); window.Closed += (o, args) => window.Dispatcher.InvokeShutdown();
splashScreen.Show(); window.Show();
splashReadyEvent.Set(); windowReadyEvent.Set();
System.Windows.Threading.Dispatcher.Run(); System.Windows.Threading.Dispatcher.Run();
}); });
splashScreenThread.SetApartmentState(ApartmentState.STA); windowThread.SetApartmentState(ApartmentState.STA);
splashScreenThread.Name = nameof(SplashScreen); windowThread.IsBackground = true;
splashScreenThread.IsBackground = true; windowThread.Start();
splashScreenThread.Start();
splashReadyEvent.WaitOne(); windowReadyEvent.WaitOne();
return splashScreen; return window;
} }
public ISystemControl CreateWirelessNetworkControl(IWirelessAdapter wirelessAdapter, Location location) public ISystemControl CreateWirelessNetworkControl(IWirelessAdapter wirelessAdapter, Location location)

View file

@ -33,12 +33,21 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Grid.Column="0" x:Name="BackwardButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" /> <Button Grid.Column="0" x:Name="BackwardButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
<Button Grid.Column="1" x:Name="ForwardButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" /> <Button Grid.Column="1" x:Name="ForwardButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
<Button Grid.Column="2" x:Name="ReloadButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" /> <Button Grid.Column="2" x:Name="ReloadButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
<TextBox Grid.Column="3" x:Name="UrlTextBox" Height="50" HorizontalAlignment="Stretch" Margin="5,10" Padding="8" VerticalContentAlignment="Center" /> <TextBox Grid.Column="3" x:Name="UrlTextBox" Height="50" HorizontalAlignment="Stretch" Margin="5,10" Padding="8" VerticalContentAlignment="Center" />
<Button Grid.Column="4" x:Name="MenuButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" /> <Button Grid.Column="4" x:Name="DownloadsButton" Height="50" HorizontalAlignment="Center" Margin="5" Padding="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" Visibility="Collapsed">
<fa:ImageAwesome Icon="Download" />
</Button>
<Popup x:Name="DownloadsPopup" AllowsTransparency="True" PopupAnimation="Slide" Placement="Custom" PlacementTarget="{Binding ElementName=BrowserControlHost}">
<Border Background="{StaticResource BackgroundBrush}" BorderBrush="LightGray" BorderThickness="1,0,1,1" MinWidth="250">
<StackPanel x:Name="Downloads" Orientation="Vertical" />
</Border>
</Popup>
<Button Grid.Column="5" x:Name="MenuButton" Height="50" HorizontalAlignment="Center" Margin="5" Template="{StaticResource BrowserButton}" VerticalAlignment="Center" />
<Popup x:Name="MenuPopup" IsOpen="False" AllowsTransparency="True" PopupAnimation="Slide" Placement="Custom" PlacementTarget="{Binding ElementName=BrowserControlHost}"> <Popup x:Name="MenuPopup" IsOpen="False" AllowsTransparency="True" PopupAnimation="Slide" Placement="Custom" PlacementTarget="{Binding ElementName=BrowserControlHost}">
<Border Background="{StaticResource BackgroundBrush}" BorderBrush="LightGray" BorderThickness="1,0,1,1" Width="350"> <Border Background="{StaticResource BackgroundBrush}" BorderBrush="LightGray" BorderThickness="1,0,1,1" Width="350">
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">

View file

@ -13,16 +13,17 @@ using System.Windows;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using SafeExamBrowser.Applications.Contracts.Resources.Icons; using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events; using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events; using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
using SafeExamBrowser.UserInterface.Mobile.Controls.Browser;
using SafeExamBrowser.UserInterface.Shared.Utilities; using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Mobile namespace SafeExamBrowser.UserInterface.Mobile
@ -117,6 +118,36 @@ namespace SafeExamBrowser.UserInterface.Mobile
}); });
} }
public void UpdateDownloadState(DownloadItemState state)
{
Dispatcher.InvokeAsync(() =>
{
var isNewItem = true;
foreach (var child in Downloads.Children)
{
if (child is DownloadItemControl control && control.Id == state.Id)
{
control.Update(state);
isNewItem = false;
break;
}
}
if (isNewItem)
{
var control = new DownloadItemControl(state.Id, text);
control.Update(state);
Downloads.Children.Add(control);
}
DownloadsButton.Visibility = Visibility.Visible;
DownloadsPopup.IsOpen = IsActive;
});
}
public void UpdateLoadingState(bool isLoading) public void UpdateLoadingState(bool isLoading)
{ {
Dispatcher.Invoke(() => ProgressBar.Visibility = isLoading ? Visibility.Visible : Visibility.Hidden); Dispatcher.Invoke(() => ProgressBar.Visibility = isLoading ? Visibility.Visible : Visibility.Hidden);
@ -167,7 +198,7 @@ namespace SafeExamBrowser.UserInterface.Mobile
} }
} }
private CustomPopupPlacement[] MenuPopup_PlacementCallback(Size popupSize, Size targetSize, Point offset) private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
{ {
return new[] return new[]
{ {
@ -216,21 +247,23 @@ namespace SafeExamBrowser.UserInterface.Mobile
private void RegisterEvents() private void RegisterEvents()
{ {
var originalBrush = MenuButton.Background;
BackwardButton.Click += (o, args) => BackwardNavigationRequested?.Invoke(); BackwardButton.Click += (o, args) => BackwardNavigationRequested?.Invoke();
Closing += BrowserWindow_Closing; Closing += BrowserWindow_Closing;
DeveloperConsoleButton.Click += (o, args) => DeveloperConsoleRequested?.Invoke(); DeveloperConsoleButton.Click += (o, args) => DeveloperConsoleRequested?.Invoke();
DownloadsButton.Click += (o, args) => DownloadsPopup.IsOpen = !DownloadsPopup.IsOpen;
DownloadsButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => DownloadsPopup.IsOpen = DownloadsPopup.IsMouseOver));
DownloadsPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
DownloadsPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => DownloadsPopup.IsOpen = DownloadsPopup.IsMouseOver));
ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke(); ForwardButton.Click += (o, args) => ForwardNavigationRequested?.Invoke();
Loaded += BrowserWindow_Loaded; Loaded += BrowserWindow_Loaded;
MenuButton.Click += (o, args) => MenuPopup.IsOpen = !MenuPopup.IsOpen; MenuButton.Click += (o, args) => MenuPopup.IsOpen = !MenuPopup.IsOpen;
MenuButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver)); MenuButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver));
MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(MenuPopup_PlacementCallback); MenuPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
MenuPopup.Closed += (o, args) => MenuButton.Background = originalBrush; MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = MenuPopup.IsMouseOver));
MenuPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => MenuPopup.IsOpen = IsMouseOver));
MenuPopup.Opened += (o, args) => MenuButton.Background = Brushes.LightGray;
KeyUp += BrowserWindow_KeyUp; KeyUp += BrowserWindow_KeyUp;
LocationChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; };
ReloadButton.Click += (o, args) => ReloadRequested?.Invoke(); ReloadButton.Click += (o, args) => ReloadRequested?.Invoke();
SizeChanged += (o, args) => { DownloadsPopup.IsOpen = false; MenuPopup.IsOpen = false; };
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged; SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
UrlTextBox.GotKeyboardFocus += (o, args) => UrlTextBox.SelectAll(); UrlTextBox.GotKeyboardFocus += (o, args) => UrlTextBox.SelectAll();
UrlTextBox.GotMouseCapture += UrlTextBox_GotMouseCapture; UrlTextBox.GotMouseCapture += UrlTextBox_GotMouseCapture;
@ -270,6 +303,7 @@ namespace SafeExamBrowser.UserInterface.Mobile
ForwardButton.Height = 35; ForwardButton.Height = 35;
ReloadButton.Height = 35; ReloadButton.Height = 35;
UrlTextBox.Height = 20; UrlTextBox.Height = 20;
DownloadsButton.Height = 35;
MenuButton.Height = 35; MenuButton.Height = 35;
} }
} }

View file

@ -0,0 +1,22 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Mobile.Controls.Browser.DownloadItemControl"
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.Mobile.Controls.Browser"
mc:Ignorable="d" d:DesignHeight="50" d:DesignWidth="200">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Panel.ZIndex="2" Name="Icon" Margin="10" Width="25" />
<ProgressBar Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Grid.ColumnSpan="2" Panel.ZIndex="1" Name="Progress" BorderThickness="0" />
<TextBlock Grid.Row="0" Grid.Column="1" Panel.ZIndex="2" Name="ItemName" FontWeight="Bold" Margin="0,10,10,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Panel.ZIndex="2" Name="Status" FontStyle="Italic" Margin="0,0,10,10" />
</Grid>
</UserControl>

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2020 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.IO;
using System.Windows.Controls;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Mobile.Controls.Browser
{
public partial class DownloadItemControl : UserControl
{
private IText text;
public Guid Id { get; }
public DownloadItemControl(Guid id, IText text)
{
this.Id = id;
this.text = text;
InitializeComponent();
}
public void Update(DownloadItemState state)
{
ItemName.Text = Uri.TryCreate(state.Url, UriKind.Absolute, out var uri) ? Path.GetFileName(uri.AbsolutePath) : state.Url;
Progress.Value = state.Completion * 100;
Status.Text = $"{text.Get(TextKey.BrowserWindow_Downloading)} ({state.Completion * 100}%)";
if (File.Exists(state.FullPath))
{
ItemName.Text = Path.GetFileName(state.FullPath);
Icon.Content = new Image { Source = IconLoader.LoadIconFor(new FileInfo(state.FullPath)) };
}
if (state.IsCancelled)
{
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = text.Get(TextKey.BrowserWindow_DownloadCancelled);
}
else if (state.IsComplete)
{
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = text.Get(TextKey.BrowserWindow_DownloadComplete);
}
}
}
}

View file

@ -22,7 +22,7 @@ using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Mobile namespace SafeExamBrowser.UserInterface.Mobile
{ {
public partial class FileSystemDialog : Window, IFileSystemDialog public partial class FileSystemDialog : Window
{ {
private FileSystemElement element; private FileSystemElement element;
private string initialPath; private string initialPath;
@ -30,6 +30,7 @@ namespace SafeExamBrowser.UserInterface.Mobile
private FileSystemOperation operation; private FileSystemOperation operation;
private IText text; private IText text;
private string title; private string title;
private IWindow parent;
public FileSystemDialog( public FileSystemDialog(
FileSystemElement element, FileSystemElement element,
@ -37,12 +38,14 @@ namespace SafeExamBrowser.UserInterface.Mobile
FileSystemOperation operation, FileSystemOperation operation,
IText text, IText text,
string message = default(string), string message = default(string),
string title = default(string)) string title = default(string),
IWindow parent = default(IWindow))
{ {
this.element = element; this.element = element;
this.initialPath = initialPath; this.initialPath = initialPath;
this.message = message; this.message = message;
this.operation = operation; this.operation = operation;
this.parent = parent;
this.text = text; this.text = text;
this.title = title; this.title = title;
@ -50,26 +53,23 @@ namespace SafeExamBrowser.UserInterface.Mobile
InitializeDialog(); InitializeDialog();
} }
public FileSystemDialogResult Show(IWindow parent = null) internal new FileSystemDialogResult Show()
{ {
return Dispatcher.Invoke(() => var result = new FileSystemDialogResult();
if (parent is Window)
{ {
var result = new FileSystemDialogResult(); Owner = parent as Window;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
if (parent is Window) if (ShowDialog() == true)
{ {
Owner = parent as Window; result.FullPath = BuildFullPath();
WindowStartupLocation = WindowStartupLocation.CenterOwner; result.Success = true;
} }
if (ShowDialog() == true) return result;
{
result.FullPath = BuildFullPath();
result.Success = true;
}
return result;
});
} }
private void CancelButton_Click(object sender, RoutedEventArgs e) private void CancelButton_Click(object sender, RoutedEventArgs e)

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2020 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 SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.Windows;
namespace SafeExamBrowser.UserInterface.Mobile
{
public class FileSystemDialogFactory : IFileSystemDialog
{
private IText text;
public FileSystemDialogFactory(IText text)
{
this.text = text;
}
public FileSystemDialogResult Show(FileSystemElement element, string initialPath, FileSystemOperation operation, string message = null, string title = null, IWindow owner = null)
{
if (owner is Window window)
{
return window.Dispatcher.Invoke(() => new FileSystemDialog(element, initialPath, operation, text, message, title, owner).Show());
}
else
{
return new FileSystemDialog(element, initialPath, operation, text, message, title).Show();
}
}
}
}

View file

@ -110,6 +110,9 @@
<Compile Include="Controls\ActionCenterWirelessNetworkControl.xaml.cs"> <Compile Include="Controls\ActionCenterWirelessNetworkControl.xaml.cs">
<DependentUpon>ActionCenterWirelessNetworkControl.xaml</DependentUpon> <DependentUpon>ActionCenterWirelessNetworkControl.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Controls\Browser\DownloadItemControl.xaml.cs">
<DependentUpon>DownloadItemControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarApplicationControl.xaml.cs"> <Compile Include="Controls\TaskbarApplicationControl.xaml.cs">
<DependentUpon>TaskbarApplicationControl.xaml</DependentUpon> <DependentUpon>TaskbarApplicationControl.xaml</DependentUpon>
</Compile> </Compile>
@ -149,6 +152,7 @@
<Compile Include="FileSystemDialog.xaml.cs"> <Compile Include="FileSystemDialog.xaml.cs">
<DependentUpon>FileSystemDialog.xaml</DependentUpon> <DependentUpon>FileSystemDialog.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="FileSystemDialogFactory.cs" />
<Compile Include="FolderDialog.cs" /> <Compile Include="FolderDialog.cs" />
<Compile Include="LockScreen.xaml.cs"> <Compile Include="LockScreen.xaml.cs">
<DependentUpon>LockScreen.xaml</DependentUpon> <DependentUpon>LockScreen.xaml</DependentUpon>
@ -279,6 +283,10 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="Controls\Browser\DownloadItemControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Controls\TaskbarApplicationControl.xaml"> <Page Include="Controls\TaskbarApplicationControl.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>

View file

@ -23,7 +23,6 @@ using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data; using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
@ -76,11 +75,6 @@ namespace SafeExamBrowser.UserInterface.Mobile
return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text)); return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text));
} }
public IFileSystemDialog CreateFileSystemDialog(FileSystemElement element, string initialPath, FileSystemOperation operation, string message = default(string), string title = default(string))
{
return Application.Current.Dispatcher.Invoke(() => new FileSystemDialog(element, initialPath, operation, text, message, title));
}
public IFolderDialog CreateFolderDialog(string message) public IFolderDialog CreateFolderDialog(string message)
{ {
return new FolderDialog(message); return new FolderDialog(message);
@ -105,26 +99,26 @@ namespace SafeExamBrowser.UserInterface.Mobile
public IWindow CreateLogWindow(ILogger logger) public IWindow CreateLogWindow(ILogger logger)
{ {
LogWindow logWindow = null; var window = default(LogWindow);
var logWindowReadyEvent = new AutoResetEvent(false); var windowReadyEvent = new AutoResetEvent(false);
var logWindowThread = new Thread(() => var windowThread = new Thread(() =>
{ {
logWindow = new LogWindow(logger, text); window = new LogWindow(logger, text);
logWindow.Closed += (o, args) => logWindow.Dispatcher.InvokeShutdown(); window.Closed += (o, args) => window.Dispatcher.InvokeShutdown();
logWindow.Show(); window.Show();
logWindowReadyEvent.Set(); windowReadyEvent.Set();
System.Windows.Threading.Dispatcher.Run(); System.Windows.Threading.Dispatcher.Run();
}); });
logWindowThread.SetApartmentState(ApartmentState.STA); windowThread.SetApartmentState(ApartmentState.STA);
logWindowThread.IsBackground = true; windowThread.IsBackground = true;
logWindowThread.Start(); windowThread.Start();
logWindowReadyEvent.WaitOne(); windowReadyEvent.WaitOne();
return logWindow; return window;
} }
public INotificationControl CreateNotificationControl(INotificationController controller, INotificationInfo info, Location location) public INotificationControl CreateNotificationControl(INotificationController controller, INotificationInfo info, Location location)
@ -168,27 +162,26 @@ namespace SafeExamBrowser.UserInterface.Mobile
public ISplashScreen CreateSplashScreen(AppConfig appConfig = null) public ISplashScreen CreateSplashScreen(AppConfig appConfig = null)
{ {
SplashScreen splashScreen = null; var window = default(SplashScreen);
var splashReadyEvent = new AutoResetEvent(false); var windowReadyEvent = new AutoResetEvent(false);
var splashScreenThread = new Thread(() => var windowThread = new Thread(() =>
{ {
splashScreen = new SplashScreen(text, appConfig); window = new SplashScreen(text, appConfig);
splashScreen.Closed += (o, args) => splashScreen.Dispatcher.InvokeShutdown(); window.Closed += (o, args) => window.Dispatcher.InvokeShutdown();
splashScreen.Show(); window.Show();
splashReadyEvent.Set(); windowReadyEvent.Set();
System.Windows.Threading.Dispatcher.Run(); System.Windows.Threading.Dispatcher.Run();
}); });
splashScreenThread.SetApartmentState(ApartmentState.STA); windowThread.SetApartmentState(ApartmentState.STA);
splashScreenThread.Name = nameof(SplashScreen); windowThread.IsBackground = true;
splashScreenThread.IsBackground = true; windowThread.Start();
splashScreenThread.Start();
splashReadyEvent.WaitOne(); windowReadyEvent.WaitOne();
return splashScreen; return window;
} }
public ISystemControl CreateWirelessNetworkControl(IWirelessAdapter wirelessAdapter, Location location) public ISystemControl CreateWirelessNetworkControl(IWirelessAdapter wirelessAdapter, Location location)