SEBWIN-308: Implemented file system dialog for mobile UI.
This commit is contained in:
parent
2fdde50d0e
commit
0ec1a446f8
6 changed files with 445 additions and 13 deletions
|
@ -38,7 +38,7 @@
|
|||
<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" />
|
||||
<Button Name="CancelButton" Cursor="Hand" Padding="10,5" MinWidth="75" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
|
|
@ -172,7 +172,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
|
||||
if (item.Tag is DirectoryInfo directory)
|
||||
{
|
||||
FileSystem.Cursor = Cursors.Wait;
|
||||
item.Cursor = Cursors.Wait;
|
||||
item.BeginInit();
|
||||
|
||||
try
|
||||
|
@ -199,7 +199,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
}
|
||||
|
||||
item.EndInit();
|
||||
FileSystem.Cursor = Cursors.Arrow;
|
||||
item.Cursor = Cursors.Hand;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,14 +299,13 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
|
||||
if (!string.IsNullOrEmpty(initialPath))
|
||||
{
|
||||
var pathRoot = Path.GetPathRoot(initialPath);
|
||||
var directories = initialPath.Replace(pathRoot, "").Split(Path.DirectorySeparatorChar);
|
||||
var segments = new List<string>();
|
||||
var root = Path.GetPathRoot(initialPath);
|
||||
var path = initialPath.Replace(root, "").Split(Path.DirectorySeparatorChar);
|
||||
var segments = new List<string> { root };
|
||||
|
||||
segments.Add(pathRoot);
|
||||
segments.AddRange(directories);
|
||||
segments.AddRange(path);
|
||||
|
||||
AutoSelect(FileSystem.Items, segments);
|
||||
SelectInitialPath(FileSystem.Items, segments);
|
||||
|
||||
if (element == FileSystemElement.File && operation == FileSystemOperation.Save)
|
||||
{
|
||||
|
@ -315,7 +314,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
}
|
||||
}
|
||||
|
||||
private void AutoSelect(ItemCollection items, List<string> segments)
|
||||
private void SelectInitialPath(ItemCollection items, List<string> segments)
|
||||
{
|
||||
var segment = segments.FirstOrDefault();
|
||||
|
||||
|
@ -329,7 +328,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
i.IsSelected = true;
|
||||
i.BringIntoView();
|
||||
|
||||
AutoSelect(i.Items, segments.Skip(1).ToList());
|
||||
SelectInitialPath(i.Items, segments.Skip(1).ToList());
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
45
SafeExamBrowser.UserInterface.Mobile/FileSystemDialog.xaml
Normal file
45
SafeExamBrowser.UserInterface.Mobile/FileSystemDialog.xaml
Normal file
|
@ -0,0 +1,45 @@
|
|||
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.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.Mobile"
|
||||
mc:Ignorable="d" Height="750" Width="750" FontSize="16" ResizeMode="NoResize" Topmost="True" WindowState="Maximized">
|
||||
<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="25,10">
|
||||
<fa:ImageAwesome Name="OperationIcon" Foreground="LightGray" Height="35" 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" Padding="0,8" 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" Margin="5,0,0,0" Padding="8" VerticalContentAlignment="Center" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<WrapPanel Orientation="Horizontal" Margin="25" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button Name="SelectButton" Cursor="Hand" IsEnabled="False" Margin="20,0" Padding="20,10" MinWidth="75" />
|
||||
<Button Name="CancelButton" Cursor="Hand" Padding="20,10" MinWidth="75" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
381
SafeExamBrowser.UserInterface.Mobile/FileSystemDialog.xaml.cs
Normal file
381
SafeExamBrowser.UserInterface.Mobile/FileSystemDialog.xaml.cs
Normal file
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
* 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.Mobile
|
||||
{
|
||||
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)
|
||||
{
|
||||
item.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();
|
||||
item.Cursor = Cursors.Hand;
|
||||
}
|
||||
}
|
||||
|
||||
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 = 24,
|
||||
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 = 24,
|
||||
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 root = Path.GetPathRoot(initialPath);
|
||||
var path = initialPath.Replace(root, "").Split(Path.DirectorySeparatorChar);
|
||||
var segments = new List<string> { root };
|
||||
|
||||
segments.AddRange(path);
|
||||
|
||||
SelectInitialPath(FileSystem.Items, segments);
|
||||
|
||||
if (element == FileSystemElement.File && operation == FileSystemOperation.Save)
|
||||
{
|
||||
NewElementName.Text = Path.GetFileName(initialPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectInitialPath(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();
|
||||
|
||||
SelectInitialPath(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -146,6 +146,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>
|
||||
|
@ -360,6 +363,10 @@
|
|||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="FileSystemDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Images\ZoomPageOut.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
|
|
|
@ -73,12 +73,12 @@ namespace SafeExamBrowser.UserInterface.Mobile
|
|||
|
||||
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))
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
return Application.Current.Dispatcher.Invoke(() => new FileSystemDialog(element, initialPath, operation, text, message, title));
|
||||
}
|
||||
|
||||
public IFolderDialog CreateFolderDialog(string message)
|
||||
|
|
Loading…
Reference in a new issue