diff --git a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs index 62d46f10..ddea35c5 100644 --- a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs +++ b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs @@ -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); diff --git a/SafeExamBrowser.Browser/BrowserControl.cs b/SafeExamBrowser.Browser/BrowserControl.cs index fa22c94f..4a70e324 100644 --- a/SafeExamBrowser.Browser/BrowserControl.cs +++ b/SafeExamBrowser.Browser/BrowserControl.cs @@ -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; diff --git a/SafeExamBrowser.Browser/Events/DialogRequestedEventArgs.cs b/SafeExamBrowser.Browser/Events/DialogRequestedEventArgs.cs new file mode 100644 index 00000000..95867ca7 --- /dev/null +++ b/SafeExamBrowser.Browser/Events/DialogRequestedEventArgs.cs @@ -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; } + } +} diff --git a/SafeExamBrowser.Browser/Events/DialogRequestedEventHandler.cs b/SafeExamBrowser.Browser/Events/DialogRequestedEventHandler.cs new file mode 100644 index 00000000..35f342f5 --- /dev/null +++ b/SafeExamBrowser.Browser/Events/DialogRequestedEventHandler.cs @@ -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); +} diff --git a/SafeExamBrowser.Browser/Handlers/DialogHandler.cs b/SafeExamBrowser.Browser/Handlers/DialogHandler.cs new file mode 100644 index 00000000..eb0c3354 --- /dev/null +++ b/SafeExamBrowser.Browser/Handlers/DialogHandler.cs @@ -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 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 { 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; + } + } + } +} diff --git a/SafeExamBrowser.Browser/Handlers/DownloadHandler.cs b/SafeExamBrowser.Browser/Handlers/DownloadHandler.cs index b98d0762..ceef04f2 100644 --- a/SafeExamBrowser.Browser/Handlers/DownloadHandler.cs +++ b/SafeExamBrowser.Browser/Handlers/DownloadHandler.cs @@ -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 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(); @@ -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."); } } } diff --git a/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj b/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj index 8b4324f4..a5f87f95 100644 --- a/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj +++ b/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj @@ -54,6 +54,9 @@ prompt + + ..\packages\Syroot.Windows.IO.KnownFolders.1.2.1\lib\net452\Syroot.KnownFolders.dll + @@ -63,6 +66,8 @@ + + @@ -78,6 +83,7 @@ Component + diff --git a/SafeExamBrowser.Browser/packages.config b/SafeExamBrowser.Browser/packages.config index 6639b420..a13c329b 100644 --- a/SafeExamBrowser.Browser/packages.config +++ b/SafeExamBrowser.Browser/packages.config @@ -4,4 +4,5 @@ + \ No newline at end of file diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/BrowserDataMapper.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/BrowserDataMapper.cs index 2e0ef3fb..c075d18f 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/BrowserDataMapper.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/BrowserDataMapper.cs @@ -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) diff --git a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs index d028ab10..23546863 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs @@ -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"; diff --git a/SafeExamBrowser.I18n.Contracts/TextKey.cs b/SafeExamBrowser.I18n.Contracts/TextKey.cs index ecfdfa1e..9016edc0 100644 --- a/SafeExamBrowser.I18n.Contracts/TextKey.cs +++ b/SafeExamBrowser.I18n.Contracts/TextKey.cs @@ -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, diff --git a/SafeExamBrowser.I18n/Text.xml b/SafeExamBrowser.I18n/Text.xml index 3524a0eb..26d186ae 100644 --- a/SafeExamBrowser.I18n/Text.xml +++ b/SafeExamBrowser.I18n/Text.xml @@ -27,6 +27,42 @@ Build + + Cancel + + + Failed to load data! + + + Loading... + + + Please select a file to open. + + + Please select a folder. + + + The selected file already exists! Would you really like to overwrite it? + + + Overwrite? + + + Save as: + + + Please select a location to save the file. + + + Please select a location to save the folder. + + + Select + + + File System Access + Application "%%NAME%%" could not be found on the system! Please locate the folder containing the main executable "%%EXECUTABLE%%". diff --git a/SafeExamBrowser.Settings/Browser/BrowserSettings.cs b/SafeExamBrowser.Settings/Browser/BrowserSettings.cs index 1a7cf322..418ede6a 100644 --- a/SafeExamBrowser.Settings/Browser/BrowserSettings.cs +++ b/SafeExamBrowser.Settings/Browser/BrowserSettings.cs @@ -26,6 +26,11 @@ namespace SafeExamBrowser.Settings.Browser /// public bool AllowConfigurationDownloads { get; set; } + /// + /// Determines whether the user will be allowed to select a custom location when downloading a file (excluding configuration files). + /// + public bool AllowCustomDownloadLocation { get; set; } + /// /// Determines whether the user will be allowed to download files (excluding configuration files). /// @@ -46,6 +51,11 @@ namespace SafeExamBrowser.Settings.Browser /// public string CustomUserAgent { get; set; } + /// + /// Defines a custom directory for file downloads. If not defined, all downloads will be saved in the current user's download directory. + /// + public string DownloadDirectory { get; set; } + /// /// Determines whether the user is allowed to use the integrated browser application. /// diff --git a/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/FileSystemDialogResult.cs b/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/FileSystemDialogResult.cs new file mode 100644 index 00000000..162587c5 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/FileSystemDialogResult.cs @@ -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 +{ + /// + /// Defines the user interaction result of an . + /// + public class FileSystemDialogResult + { + /// + /// The full path of the item selected by the user, or null if the interaction was unsuccessful. + /// + public string FullPath { get; set; } + + /// + /// Indicates whether the user confirmed the dialog or not. + /// + public bool Success { get; set; } + } +} diff --git a/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/FileSystemElement.cs b/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/FileSystemElement.cs new file mode 100644 index 00000000..d44ec9f6 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/FileSystemElement.cs @@ -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 +{ + /// + /// Defines all elements supported by an + /// + public enum FileSystemElement + { + /// + /// A dialog to perform an operation with a file. + /// + File, + + /// + /// A dialog to perform an operation with a folder. + /// + Folder + } +} diff --git a/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/FileSystemOperation.cs b/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/FileSystemOperation.cs new file mode 100644 index 00000000..db18a1c2 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/FileSystemOperation.cs @@ -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 +{ + /// + /// Defines all operations supported by an + /// + public enum FileSystemOperation + { + /// + /// A dialog to open a file system element. + /// + Open, + + /// + /// A dialog to save a file system element. + /// + Save + } +} diff --git a/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/IFileSystemDialog.cs b/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/IFileSystemDialog.cs new file mode 100644 index 00000000..48976d4e --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/FileSystemDialog/IFileSystemDialog.cs @@ -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 + { + /// + /// Shows the dialog to the user. If a parent window is specified, the dialog is rendered modally for the given parent. + /// + FileSystemDialogResult Show(IWindow parent = null); + } +} diff --git a/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs index 9f1c92c4..5747f115 100644 --- a/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs +++ b/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs @@ -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 /// IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow); + /// + /// Creates a file system dialog according to the given parameters. + /// + IFileSystemDialog CreateFileSystemDialog(FileSystemElement element, string initialPath, FileSystemOperation operation, string message = default(string), string title = default(string)); + /// /// Creates a folder dialog with the given message. /// diff --git a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj index b1a43d79..12351f12 100644 --- a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj +++ b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj @@ -60,6 +60,10 @@ + + + + diff --git a/SafeExamBrowser.UserInterface.Desktop/FileSystemDialog.xaml b/SafeExamBrowser.UserInterface.Desktop/FileSystemDialog.xaml new file mode 100644 index 00000000..2875f176 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Desktop/FileSystemDialog.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +