SEBWIN-308: Implemented basic file system dialog and download configuration for browser.
This commit is contained in:
parent
61f369a9a3
commit
0083a87bce
26 changed files with 881 additions and 14 deletions
|
@ -106,6 +106,7 @@ namespace SafeExamBrowser.Browser
|
|||
private void InitializeControl()
|
||||
{
|
||||
var contextMenuHandler = new ContextMenuHandler();
|
||||
var dialogHandler = new DialogHandler();
|
||||
var displayHandler = new DisplayHandler();
|
||||
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} #{Id}");
|
||||
var downloadHandler = new DownloadHandler(appConfig, settings, downloadLogger);
|
||||
|
@ -117,6 +118,7 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
Icon = new BrowserIconResource();
|
||||
|
||||
dialogHandler.DialogRequested += DialogHandler_DialogRequested;
|
||||
displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
|
||||
displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
|
||||
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
|
||||
|
@ -130,7 +132,7 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
InitializeRequestFilter(requestFilter);
|
||||
|
||||
control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, startUrl);
|
||||
control = new BrowserControl(contextMenuHandler, dialogHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, startUrl);
|
||||
control.AddressChanged += Control_AddressChanged;
|
||||
control.LoadingStateChanged += Control_LoadingStateChanged;
|
||||
control.TitleChanged += Control_TitleChanged;
|
||||
|
@ -207,6 +209,23 @@ namespace SafeExamBrowser.Browser
|
|||
TitleChanged?.Invoke(Title);
|
||||
}
|
||||
|
||||
private void DialogHandler_DialogRequested(DialogRequestedEventArgs args)
|
||||
{
|
||||
var dialog = uiFactory.CreateFileSystemDialog(args.Element, args.InitialPath, args.Operation, title: args.Title);
|
||||
var result = dialog.Show(window);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
args.FullPath = result.FullPath;
|
||||
args.Success = result.Success;
|
||||
logger.Debug($"User selected path '{result.FullPath}' when asked to {args.Operation}->{args.Element}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"User aborted file system dialog to {args.Operation}->{args.Element}.");
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayHandler_FaviconChanged(string uri)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Head, uri);
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace SafeExamBrowser.Browser
|
|||
internal class BrowserControl : ChromiumWebBrowser, IBrowserControl
|
||||
{
|
||||
private IContextMenuHandler contextMenuHandler;
|
||||
private IDialogHandler dialogHandler;
|
||||
private IDisplayHandler displayHandler;
|
||||
private IDownloadHandler downloadHandler;
|
||||
private IKeyboardHandler keyboardHandler;
|
||||
|
@ -49,6 +50,7 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
public BrowserControl(
|
||||
IContextMenuHandler contextMenuHandler,
|
||||
IDialogHandler dialogHandler,
|
||||
IDisplayHandler displayHandler,
|
||||
IDownloadHandler downloadHandler,
|
||||
IKeyboardHandler keyboardHandler,
|
||||
|
@ -57,6 +59,7 @@ namespace SafeExamBrowser.Browser
|
|||
string url) : base(url)
|
||||
{
|
||||
this.contextMenuHandler = contextMenuHandler;
|
||||
this.dialogHandler = dialogHandler;
|
||||
this.displayHandler = displayHandler;
|
||||
this.downloadHandler = downloadHandler;
|
||||
this.keyboardHandler = keyboardHandler;
|
||||
|
@ -70,6 +73,7 @@ namespace SafeExamBrowser.Browser
|
|||
LoadingStateChanged += (o, args) => loadingStateChanged?.Invoke(args.IsLoading);
|
||||
TitleChanged += (o, args) => titleChanged?.Invoke(args.Title);
|
||||
|
||||
DialogHandler = dialogHandler;
|
||||
DisplayHandler = displayHandler;
|
||||
DownloadHandler = downloadHandler;
|
||||
KeyboardHandler = keyboardHandler;
|
||||
|
|
22
SafeExamBrowser.Browser/Events/DialogRequestedEventArgs.cs
Normal file
22
SafeExamBrowser.Browser/Events/DialogRequestedEventArgs.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.FileSystemDialog;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal class DialogRequestedEventArgs
|
||||
{
|
||||
internal FileSystemElement Element { get; set; }
|
||||
internal string InitialPath { get; set; }
|
||||
internal FileSystemOperation Operation { get; set; }
|
||||
internal string FullPath { get; set; }
|
||||
internal bool Success { get; set; }
|
||||
internal string Title { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void DialogRequestedEventHandler(DialogRequestedEventArgs args);
|
||||
}
|
73
SafeExamBrowser.Browser/Handlers/DialogHandler.cs
Normal file
73
SafeExamBrowser.Browser/Handlers/DialogHandler.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class DialogHandler : IDialogHandler
|
||||
{
|
||||
internal event DialogRequestedEventHandler DialogRequested;
|
||||
|
||||
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback)
|
||||
{
|
||||
var args = new DialogRequestedEventArgs
|
||||
{
|
||||
Element = ToElement(mode),
|
||||
InitialPath = defaultFilePath,
|
||||
Operation = ToOperation(mode),
|
||||
Title = title
|
||||
};
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
DialogRequested?.Invoke(args);
|
||||
|
||||
using (callback)
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
callback.Continue(selectedAcceptFilter, new List<string> { args.FullPath });
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.Cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private FileSystemElement ToElement(CefFileDialogMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case CefFileDialogMode.OpenFolder:
|
||||
return FileSystemElement.Folder;
|
||||
default:
|
||||
return FileSystemElement.File;
|
||||
}
|
||||
}
|
||||
|
||||
private FileSystemOperation ToOperation(CefFileDialogMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case CefFileDialogMode.Save:
|
||||
return FileSystemOperation.Save;
|
||||
default:
|
||||
return FileSystemOperation.Open;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ using CefSharp;
|
|||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using Syroot.Windows.IO;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
|
@ -25,9 +26,9 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
private ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
|
||||
private ILogger logger;
|
||||
|
||||
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
|
||||
public DownloadHandler(AppConfig appConfig, BrowserSettings settings, ILogger logger)
|
||||
internal DownloadHandler(AppConfig appConfig, BrowserSettings settings, ILogger logger)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.callbacks = new ConcurrentDictionary<int, DownloadFinishedCallback>();
|
||||
|
@ -41,7 +42,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
var extension = Path.GetExtension(uri.AbsolutePath);
|
||||
var isConfigFile = String.Equals(extension, appConfig.ConfigurationFileExtension, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
logger.Debug($"Handling download request for '{uri}'.");
|
||||
logger.Debug($"Detected download request for '{uri}'.");
|
||||
|
||||
if (isConfigFile)
|
||||
{
|
||||
|
@ -49,12 +50,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
}
|
||||
else if (settings.AllowDownloads)
|
||||
{
|
||||
logger.Debug($"Starting download of '{uri}'...");
|
||||
|
||||
using (callback)
|
||||
{
|
||||
callback.Continue(null, true);
|
||||
}
|
||||
Task.Run(() => HandleFileDownload(downloadItem, callback));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -64,6 +60,8 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
|
||||
public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
|
||||
{
|
||||
// TODO: Show download progress in respective window -> event for BrowserApplicationInstance!
|
||||
|
||||
if (downloadItem.IsComplete || downloadItem.IsCancelled)
|
||||
{
|
||||
if (callbacks.TryRemove(downloadItem.Id, out DownloadFinishedCallback finished) && finished != null)
|
||||
|
@ -72,6 +70,39 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
}
|
||||
|
||||
logger.Debug($"Download of '{downloadItem.Url}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}.");
|
||||
|
||||
// TODO: Show success message or download icon like Firefox in respective window!
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleFileDownload(DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
var filePath = default(string);
|
||||
var showDialog = settings.AllowCustomDownloadLocation;
|
||||
|
||||
logger.Debug($"Handling download of file '{downloadItem.SuggestedFileName}'.");
|
||||
|
||||
if (!string.IsNullOrEmpty(settings.DownloadDirectory))
|
||||
{
|
||||
filePath = Path.Combine(Environment.ExpandEnvironmentVariables(settings.DownloadDirectory), downloadItem.SuggestedFileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
filePath = Path.Combine(KnownFolders.Downloads.ExpandedPath, downloadItem.SuggestedFileName);
|
||||
}
|
||||
|
||||
if (showDialog)
|
||||
{
|
||||
logger.Debug($"Allowing user to select custom download location, with '{filePath}' as suggestion.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Automatically downloading file as '{filePath}'.");
|
||||
}
|
||||
|
||||
using (callback)
|
||||
{
|
||||
callback.Continue(filePath, showDialog);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +110,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
var args = new DownloadEventArgs();
|
||||
|
||||
logger.Debug($"Detected download request for configuration file '{downloadItem.Url}'.");
|
||||
logger.Debug($"Handling download of configuration file '{downloadItem.SuggestedFileName}'.");
|
||||
ConfigurationDownloadRequested?.Invoke(downloadItem.SuggestedFileName, args);
|
||||
|
||||
if (args.AllowDownload)
|
||||
|
@ -89,7 +120,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
callbacks[downloadItem.Id] = args.Callback;
|
||||
}
|
||||
|
||||
logger.Debug($"Starting download of configuration file '{downloadItem.Url}'...");
|
||||
logger.Debug($"Starting download of configuration file '{downloadItem.SuggestedFileName}'...");
|
||||
|
||||
using (callback)
|
||||
{
|
||||
|
@ -98,7 +129,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Download of configuration file '{downloadItem.Url}' was cancelled.");
|
||||
logger.Debug($"Download of configuration file '{downloadItem.SuggestedFileName}' was cancelled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,9 @@
|
|||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Syroot.KnownFolders, Version=1.2.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Syroot.Windows.IO.KnownFolders.1.2.1\lib\net452\Syroot.KnownFolders.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
|
@ -63,6 +66,8 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="BrowserApplication.cs" />
|
||||
<Compile Include="BrowserApplicationInstance.cs" />
|
||||
<Compile Include="Events\DialogRequestedEventArgs.cs" />
|
||||
<Compile Include="Events\DialogRequestedEventHandler.cs" />
|
||||
<Compile Include="Events\FaviconChangedEventHandler.cs" />
|
||||
<Compile Include="Events\InstanceTerminatedEventHandler.cs" />
|
||||
<Compile Include="Events\PopupRequestedEventArgs.cs" />
|
||||
|
@ -78,6 +83,7 @@
|
|||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="BrowserIconResource.cs" />
|
||||
<Compile Include="Handlers\DialogHandler.cs" />
|
||||
<Compile Include="Handlers\DisplayHandler.cs" />
|
||||
<Compile Include="Handlers\DownloadHandler.cs" />
|
||||
<Compile Include="Handlers\KeyboardHandler.cs" />
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
<package id="cef.redist.x86" version="75.1.14" targetFramework="net472" />
|
||||
<package id="CefSharp.Common" version="75.1.142" targetFramework="net472" />
|
||||
<package id="CefSharp.WinForms" version="75.1.142" targetFramework="net472" />
|
||||
<package id="Syroot.Windows.IO.KnownFolders" version="1.2.1" targetFramework="net472" />
|
||||
</packages>
|
|
@ -24,6 +24,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
|||
case Keys.Browser.AllowConfigurationDownloads:
|
||||
MapAllowConfigurationDownloads(settings, value);
|
||||
break;
|
||||
case Keys.Browser.AllowCustomDownloadLocation:
|
||||
MapAllowCustomDownloadLocation(settings, value);
|
||||
break;
|
||||
case Keys.Browser.AllowDeveloperConsole:
|
||||
MapAllowDeveloperConsole(settings, value);
|
||||
break;
|
||||
|
@ -54,6 +57,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
|||
case Keys.Browser.AdditionalWindow.WindowWidth:
|
||||
MapWindowWidthAdditionalWindow(settings, value);
|
||||
break;
|
||||
case Keys.Browser.DownloadDirectory:
|
||||
MapDownloadDirectory(settings, value);
|
||||
break;
|
||||
case Keys.Browser.EnableBrowser:
|
||||
MapEnableBrowser(settings, value);
|
||||
break;
|
||||
|
@ -139,6 +145,14 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
|||
}
|
||||
}
|
||||
|
||||
private void MapAllowCustomDownloadLocation(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool allow)
|
||||
{
|
||||
settings.Browser.AllowCustomDownloadLocation = allow;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapAllowDeveloperConsole(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool allow)
|
||||
|
@ -198,6 +212,14 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
|||
}
|
||||
}
|
||||
|
||||
private void MapDownloadDirectory(AppSettings settings, object value)
|
||||
{
|
||||
if (value is string directory)
|
||||
{
|
||||
settings.Browser.DownloadDirectory = directory;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapEnableBrowser(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool enable)
|
||||
|
|
|
@ -42,11 +42,13 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
|
|||
internal static class Browser
|
||||
{
|
||||
internal const string AllowConfigurationDownloads = "downloadAndOpenSebConfig";
|
||||
internal const string AllowCustomDownloadLocation = "allowCustomDownloadLocation";
|
||||
internal const string AllowDeveloperConsole = "allowDeveloperConsole";
|
||||
internal const string AllowDownloads = "allowDownUploads";
|
||||
internal const string AllowPageZoom = "enableZoomPage";
|
||||
internal const string CustomUserAgentDesktop = "browserUserAgentWinDesktopModeCustom";
|
||||
internal const string CustomUserAgentMobile = "browserUserAgentWinTouchModeCustom";
|
||||
internal const string DownloadDirectory = "downloadDirectoryWin";
|
||||
internal const string EnableBrowser = "enableSebBrowser";
|
||||
internal const string PopupPolicy = "newBrowserWindowByLinkPolicy";
|
||||
internal const string PopupBlockForeignHost = "newBrowserWindowByLinkBlockForeign";
|
||||
|
|
|
@ -23,6 +23,18 @@ namespace SafeExamBrowser.I18n.Contracts
|
|||
BrowserWindow_DeveloperConsoleMenuItem,
|
||||
BrowserWindow_ZoomMenuItem,
|
||||
Build,
|
||||
FileSystemDialog_Cancel,
|
||||
FileSystemDialog_LoadError,
|
||||
FileSystemDialog_Loading,
|
||||
FileSystemDialog_OpenFileMessage,
|
||||
FileSystemDialog_OpenFolderMessage,
|
||||
FileSystemDialog_OverwriteWarning,
|
||||
FileSystemDialog_OverwriteWarningTitle,
|
||||
FileSystemDialog_SaveAs,
|
||||
FileSystemDialog_SaveFileMessage,
|
||||
FileSystemDialog_SaveFolderMessage,
|
||||
FileSystemDialog_Select,
|
||||
FileSystemDialog_Title,
|
||||
FolderDialog_ApplicationLocation,
|
||||
LockScreen_AllowOption,
|
||||
LockScreen_Message,
|
||||
|
|
|
@ -27,6 +27,42 @@
|
|||
<Entry key="Build">
|
||||
Build
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_Cancel">
|
||||
Cancel
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_LoadError">
|
||||
Failed to load data!
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_Loading">
|
||||
Loading...
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_OpenFileMessage">
|
||||
Please select a file to open.
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_OpenFolderMessage">
|
||||
Please select a folder.
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_OverwriteWarning">
|
||||
The selected file already exists! Would you really like to overwrite it?
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_OverwriteWarningTitle">
|
||||
Overwrite?
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_SaveAs">
|
||||
Save as:
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_SaveFileMessage">
|
||||
Please select a location to save the file.
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_SaveFolderMessage">
|
||||
Please select a location to save the folder.
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_Select">
|
||||
Select
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_Title">
|
||||
File System Access
|
||||
</Entry>
|
||||
<Entry key="FolderDialog_ApplicationLocation">
|
||||
Application "%%NAME%%" could not be found on the system! Please locate the folder containing the main executable "%%EXECUTABLE%%".
|
||||
</Entry>
|
||||
|
|
|
@ -26,6 +26,11 @@ namespace SafeExamBrowser.Settings.Browser
|
|||
/// </summary>
|
||||
public bool AllowConfigurationDownloads { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the user will be allowed to select a custom location when downloading a file (excluding configuration files).
|
||||
/// </summary>
|
||||
public bool AllowCustomDownloadLocation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the user will be allowed to download files (excluding configuration files).
|
||||
/// </summary>
|
||||
|
@ -46,6 +51,11 @@ namespace SafeExamBrowser.Settings.Browser
|
|||
/// </summary>
|
||||
public string CustomUserAgent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines a custom directory for file downloads. If not defined, all downloads will be saved in the current user's download directory.
|
||||
/// </summary>
|
||||
public string DownloadDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the user is allowed to use the integrated browser application.
|
||||
/// </summary>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Contracts.FileSystemDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the user interaction result of an <see cref="IFileSystemDialog"/>.
|
||||
/// </summary>
|
||||
public class FileSystemDialogResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The full path of the item selected by the user, or <c>null</c> if the interaction was unsuccessful.
|
||||
/// </summary>
|
||||
public string FullPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the user confirmed the dialog or not.
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Contracts.FileSystemDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines all elements supported by an <see cref="IFileSystemDialog"/>
|
||||
/// </summary>
|
||||
public enum FileSystemElement
|
||||
{
|
||||
/// <summary>
|
||||
/// A dialog to perform an operation with a file.
|
||||
/// </summary>
|
||||
File,
|
||||
|
||||
/// <summary>
|
||||
/// A dialog to perform an operation with a folder.
|
||||
/// </summary>
|
||||
Folder
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Contracts.FileSystemDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines all operations supported by an <see cref="IFileSystemDialog"/>
|
||||
/// </summary>
|
||||
public enum FileSystemOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// A dialog to open a file system element.
|
||||
/// </summary>
|
||||
Open,
|
||||
|
||||
/// <summary>
|
||||
/// A dialog to save a file system element.
|
||||
/// </summary>
|
||||
Save
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.Windows;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Contracts.FileSystemDialog
|
||||
{
|
||||
public interface IFileSystemDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows the dialog to the user. If a parent window is specified, the dialog is rendered modally for the given parent.
|
||||
/// </summary>
|
||||
FileSystemDialogResult Show(IWindow parent = null);
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
|||
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||
|
@ -49,6 +50,11 @@ namespace SafeExamBrowser.UserInterface.Contracts
|
|||
/// </summary>
|
||||
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>
|
||||
/// Creates a folder dialog with the given message.
|
||||
/// </summary>
|
||||
|
|
|
@ -60,6 +60,10 @@
|
|||
<Compile Include="Browser\IBrowserControl.cs" />
|
||||
<Compile Include="Browser\IBrowserWindow.cs" />
|
||||
<Compile Include="Events\ActionRequestedEventHandler.cs" />
|
||||
<Compile Include="FileSystemDialog\FileSystemDialogResult.cs" />
|
||||
<Compile Include="FileSystemDialog\FileSystemElement.cs" />
|
||||
<Compile Include="FileSystemDialog\FileSystemOperation.cs" />
|
||||
<Compile Include="FileSystemDialog\IFileSystemDialog.cs" />
|
||||
<Compile Include="IProgressIndicator.cs" />
|
||||
<Compile Include="IUserInterfaceFactory.cs" />
|
||||
<Compile Include="MessageBox\IMessageBox.cs" />
|
||||
|
|
45
SafeExamBrowser.UserInterface.Desktop/FileSystemDialog.xaml
Normal file
45
SafeExamBrowser.UserInterface.Desktop/FileSystemDialog.xaml
Normal file
|
@ -0,0 +1,45 @@
|
|||
<Window x:Class="SafeExamBrowser.UserInterface.Desktop.FileSystemDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop"
|
||||
mc:Ignorable="d" Height="500" Width="750" ResizeMode="NoResize">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="./Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<WrapPanel Grid.Row="0" Margin="20,10">
|
||||
<fa:ImageAwesome Name="OperationIcon" Foreground="LightGray" Height="25" Icon="FileOutline"/>
|
||||
<TextBlock Name="Message" Margin="10,0,0,0" VerticalAlignment="Center" />
|
||||
</WrapPanel>
|
||||
<TreeView Grid.Row="1" Name="FileSystem" Margin="10,0" TreeViewItem.Expanded="FileSystem_Expanded" />
|
||||
<TextBlock Grid.Row="2" Name="SelectedElement" Margin="10,5,10,5" TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<Grid Grid.Row="3" Name="NewElement" Margin="10,0,10,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Name="NewElementLabel" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Column="1" Name="NewElementName" Height="25" Margin="5,0,0,0" VerticalContentAlignment="Center" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<WrapPanel Orientation="Horizontal" Margin="20" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button Name="SelectButton" Cursor="Hand" IsEnabled="False" Margin="10,0" Padding="10,5" MinWidth="75" />
|
||||
<Button Name="CancelButton" Cursor="Hand" Padding="20,5" MinWidth="75" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
382
SafeExamBrowser.UserInterface.Desktop/FileSystemDialog.xaml.cs
Normal file
382
SafeExamBrowser.UserInterface.Desktop/FileSystemDialog.xaml.cs
Normal file
|
@ -0,0 +1,382 @@
|
|||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using FontAwesome.WPF;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop
|
||||
{
|
||||
public partial class FileSystemDialog : Window, IFileSystemDialog
|
||||
{
|
||||
private FileSystemElement element;
|
||||
private string initialPath;
|
||||
private string message;
|
||||
private FileSystemOperation operation;
|
||||
private IText text;
|
||||
private string title;
|
||||
|
||||
public FileSystemDialog(
|
||||
FileSystemElement element,
|
||||
string initialPath,
|
||||
FileSystemOperation operation,
|
||||
IText text,
|
||||
string message = default(string),
|
||||
string title = default(string))
|
||||
{
|
||||
this.element = element;
|
||||
this.initialPath = initialPath;
|
||||
this.message = message;
|
||||
this.operation = operation;
|
||||
this.text = text;
|
||||
this.title = title;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeDialog();
|
||||
}
|
||||
|
||||
public FileSystemDialogResult Show(IWindow parent = null)
|
||||
{
|
||||
return Dispatcher.Invoke(() =>
|
||||
{
|
||||
var result = new FileSystemDialogResult();
|
||||
|
||||
if (parent is Window)
|
||||
{
|
||||
Owner = parent as Window;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
}
|
||||
|
||||
if (ShowDialog() == true)
|
||||
{
|
||||
result.FullPath = BuildFullPath();
|
||||
result.Success = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void FileSystem_Expanded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (e.Source is TreeViewItem item && item.Items.Count == 1 && !(item.Items[0] is TreeViewItem))
|
||||
{
|
||||
Load(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void FileSystem_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
if (e.NewValue is TreeViewItem item)
|
||||
{
|
||||
if (item.Tag is DirectoryInfo directory)
|
||||
{
|
||||
SelectButton.IsEnabled = element == FileSystemElement.Folder || operation == FileSystemOperation.Save;
|
||||
SelectedElement.Text = directory.FullName;
|
||||
SelectedElement.ToolTip = directory.FullName;
|
||||
}
|
||||
else if (item.Tag is FileInfo file)
|
||||
{
|
||||
SelectButton.IsEnabled = element == FileSystemElement.File;
|
||||
SelectedElement.Text = file.FullName;
|
||||
SelectedElement.ToolTip = file.FullName;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectButton.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NewElementName_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && IsValid())
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildFullPath()
|
||||
{
|
||||
var fullPath = SelectedElement.Text;
|
||||
|
||||
if (operation == FileSystemOperation.Save)
|
||||
{
|
||||
fullPath = Path.Combine(SelectedElement.Text, NewElementName.Text);
|
||||
|
||||
if (element == FileSystemElement.File)
|
||||
{
|
||||
var extension = Path.GetExtension(initialPath);
|
||||
|
||||
if (!fullPath.EndsWith(extension))
|
||||
{
|
||||
fullPath = $"{fullPath}{extension}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
var fullPath = BuildFullPath();
|
||||
var isValid = true;
|
||||
|
||||
if (element == FileSystemElement.File && operation == FileSystemOperation.Save && File.Exists(fullPath))
|
||||
{
|
||||
var message = text.Get(TextKey.FileSystemDialog_OverwriteWarning);
|
||||
var title = text.Get(TextKey.FileSystemDialog_OverwriteWarningTitle);
|
||||
var result = System.Windows.MessageBox.Show(this, message, title, MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||
|
||||
isValid = result == MessageBoxResult.Yes;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private void Load(TreeViewItem item)
|
||||
{
|
||||
item.Items.Clear();
|
||||
|
||||
if (item.Tag is DirectoryInfo directory)
|
||||
{
|
||||
FileSystem.Cursor = Cursors.Wait;
|
||||
item.BeginInit();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var subDirectory in directory.GetDirectories())
|
||||
{
|
||||
if (!subDirectory.Attributes.HasFlag(FileAttributes.Hidden))
|
||||
{
|
||||
item.Items.Add(CreateItem(subDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var file in directory.GetFiles())
|
||||
{
|
||||
if (!file.Attributes.HasFlag(FileAttributes.Hidden))
|
||||
{
|
||||
item.Items.Add(CreateItem(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
item.Items.Add(CreateErrorItem(e));
|
||||
}
|
||||
|
||||
item.EndInit();
|
||||
FileSystem.Cursor = Cursors.Arrow;
|
||||
}
|
||||
}
|
||||
|
||||
private TreeViewItem CreateErrorItem(Exception e)
|
||||
{
|
||||
var item = new TreeViewItem();
|
||||
|
||||
item.Foreground = Brushes.Red;
|
||||
item.Header = $"{text.Get(TextKey.FileSystemDialog_LoadError)} {e.Message}";
|
||||
item.ToolTip = e.GetType() + Environment.NewLine + e.StackTrace;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private TreeViewItem CreateItem(DirectoryInfo directory)
|
||||
{
|
||||
var header = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(2) };
|
||||
var image = new Image
|
||||
{
|
||||
Height = 16,
|
||||
Source = IconLoader.LoadIconFor(directory)
|
||||
};
|
||||
var item = new TreeViewItem();
|
||||
var textBlock = new TextBlock { Margin = new Thickness(5, 0, 0, 0), Text = directory.Name, VerticalAlignment = VerticalAlignment.Center };
|
||||
|
||||
header.Children.Add(image);
|
||||
header.Children.Add(textBlock);
|
||||
|
||||
item.Cursor = Cursors.Hand;
|
||||
item.Header = header;
|
||||
item.Tag = directory;
|
||||
item.ToolTip = directory.FullName;
|
||||
item.Items.Add(text.Get(TextKey.FileSystemDialog_Loading));
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private TreeViewItem CreateItem(FileInfo file)
|
||||
{
|
||||
var header = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(2) };
|
||||
var image = new Image
|
||||
{
|
||||
Height = 20,
|
||||
Source = IconLoader.LoadIconFor(file)
|
||||
};
|
||||
var item = new TreeViewItem();
|
||||
var textBlock = new TextBlock { Margin = new Thickness(5, 0, 0, 0), Text = file.Name, VerticalAlignment = VerticalAlignment.Center };
|
||||
|
||||
header.Children.Add(image);
|
||||
header.Children.Add(textBlock);
|
||||
|
||||
item.Header = header;
|
||||
item.Tag = file;
|
||||
item.ToolTip = file.FullName;
|
||||
|
||||
if (element == FileSystemElement.File && operation == FileSystemOperation.Open)
|
||||
{
|
||||
item.Cursor = Cursors.Hand;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Cursor = Cursors.No;
|
||||
item.Focusable = false;
|
||||
textBlock.Foreground = Brushes.Gray;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private void InitializeDialog()
|
||||
{
|
||||
CancelButton.Click += CancelButton_Click;
|
||||
CancelButton.Content = text.Get(TextKey.FileSystemDialog_Cancel);
|
||||
FileSystem.SelectedItemChanged += FileSystem_SelectedItemChanged;
|
||||
NewElement.Visibility = operation == FileSystemOperation.Save ? Visibility.Visible : Visibility.Collapsed;
|
||||
NewElementLabel.Text = text.Get(TextKey.FileSystemDialog_SaveAs);
|
||||
NewElementName.KeyUp += NewElementName_KeyUp;
|
||||
OperationIcon.Icon = operation == FileSystemOperation.Save ? FontAwesomeIcon.Download : FontAwesomeIcon.Search;
|
||||
SelectButton.Click += SelectButton_Click;
|
||||
SelectButton.Content = text.Get(TextKey.FileSystemDialog_Select);
|
||||
|
||||
InitializeText();
|
||||
InitializeFileSystem();
|
||||
}
|
||||
|
||||
private void InitializeFileSystem()
|
||||
{
|
||||
foreach (var drive in DriveInfo.GetDrives())
|
||||
{
|
||||
FileSystem.Items.Add(CreateItem(drive.RootDirectory));
|
||||
}
|
||||
|
||||
if (FileSystem.HasItems && FileSystem.Items[0] is TreeViewItem item)
|
||||
{
|
||||
item.IsSelected = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(initialPath))
|
||||
{
|
||||
var pathRoot = Path.GetPathRoot(initialPath);
|
||||
var directories = initialPath.Replace(pathRoot, "").Split(Path.DirectorySeparatorChar);
|
||||
var segments = new List<string>();
|
||||
|
||||
segments.Add(pathRoot);
|
||||
segments.AddRange(directories);
|
||||
|
||||
AutoSelect(FileSystem.Items, segments);
|
||||
|
||||
if (element == FileSystemElement.File && operation == FileSystemOperation.Save)
|
||||
{
|
||||
NewElementName.Text = Path.GetFileName(initialPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoSelect(ItemCollection items, List<string> segments)
|
||||
{
|
||||
var segment = segments.FirstOrDefault();
|
||||
|
||||
if (segment != default(string))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item is TreeViewItem i && i.Tag is DirectoryInfo d && d.Name.Equals(segment))
|
||||
{
|
||||
i.IsExpanded = true;
|
||||
i.IsSelected = true;
|
||||
i.BringIntoView();
|
||||
|
||||
AutoSelect(i.Items, segments.Skip(1).ToList());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeText()
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
if (element == FileSystemElement.File)
|
||||
{
|
||||
if (operation == FileSystemOperation.Open)
|
||||
{
|
||||
Message.Text = text.Get(TextKey.FileSystemDialog_OpenFileMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Message.Text = text.Get(TextKey.FileSystemDialog_SaveFileMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (operation == FileSystemOperation.Open)
|
||||
{
|
||||
Message.Text = text.Get(TextKey.FileSystemDialog_OpenFolderMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Message.Text = text.Get(TextKey.FileSystemDialog_SaveFolderMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Message.Text = message;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(title))
|
||||
{
|
||||
Title = text.Get(TextKey.FileSystemDialog_Title);
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = title;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -145,6 +145,9 @@
|
|||
<Compile Include="Controls\TaskviewWindowControl.xaml.cs">
|
||||
<DependentUpon>TaskviewWindowControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="FileSystemDialog.xaml.cs">
|
||||
<DependentUpon>FileSystemDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="FolderDialog.cs" />
|
||||
<Compile Include="LockScreen.xaml.cs">
|
||||
<DependentUpon>LockScreen.xaml</DependentUpon>
|
||||
|
@ -322,6 +325,10 @@
|
|||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="FileSystemDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="LockScreen.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
|
|
@ -23,6 +23,7 @@ using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
|||
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||
|
@ -72,7 +73,12 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
|
||||
public IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow)
|
||||
{
|
||||
return 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)
|
||||
|
|
|
@ -23,6 +23,7 @@ using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
|||
using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||
|
@ -75,6 +76,11 @@ namespace SafeExamBrowser.UserInterface.Mobile
|
|||
return new BrowserWindow(control, settings, isMainWindow, text);
|
||||
}
|
||||
|
||||
public IFileSystemDialog CreateFileSystemDialog(FileSystemElement element, string initialPath, FileSystemOperation operation, string message = default(string), string title = default(string))
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IFolderDialog CreateFolderDialog(string message)
|
||||
{
|
||||
return new FolderDialog(message);
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Utilities\IconLoader.cs" />
|
||||
<Compile Include="Utilities\IconResourceLoader.cs" />
|
||||
<Compile Include="Utilities\Thumbnail.cs" />
|
||||
<Compile Include="Utilities\VisualExtensions.cs" />
|
||||
|
|
62
SafeExamBrowser.UserInterface.Shared/Utilities/IconLoader.cs
Normal file
62
SafeExamBrowser.UserInterface.Shared/Utilities/IconLoader.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Shared.Utilities
|
||||
{
|
||||
public static class IconLoader
|
||||
{
|
||||
private const uint SHGFI_ICON = 0x100;
|
||||
private const uint SHGFI_LARGEICON = 0x0;
|
||||
private const uint SHGFI_SMALLICON = 0x1;
|
||||
|
||||
public static ImageSource LoadIconFor(DirectoryInfo directory)
|
||||
{
|
||||
var fileInfo = new SHFILEINFO();
|
||||
var result = SHGetFileInfo(directory.FullName, 0, ref fileInfo, (uint) Marshal.SizeOf(fileInfo), SHGFI_ICON | SHGFI_SMALLICON);
|
||||
var imageSource = Imaging.CreateBitmapSourceFromHIcon(fileInfo.hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
DestroyIcon(fileInfo.hIcon);
|
||||
|
||||
return imageSource;
|
||||
}
|
||||
|
||||
public static ImageSource LoadIconFor(FileInfo file)
|
||||
{
|
||||
using (var icon = System.Drawing.Icon.ExtractAssociatedIcon(file.FullName))
|
||||
{
|
||||
return Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int DestroyIcon(IntPtr hIcon);
|
||||
|
||||
[DllImport("shell32.dll")]
|
||||
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct SHFILEINFO
|
||||
{
|
||||
public IntPtr hIcon;
|
||||
public IntPtr iIcon;
|
||||
public uint dwAttributes;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string szDisplayName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
|
||||
public string szTypeName;
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue