Skip to content

Commit

Permalink
SEBWIN-970: Fixed bug leading to user interface freezing and caused b…
Browse files Browse the repository at this point in the history
…y bad implementation of the downloads popup for browser windows.
  • Loading branch information
dbuechel committed Oct 10, 2024
1 parent 489af38 commit f31662a
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 73 deletions.
2 changes: 2 additions & 0 deletions SafeExamBrowser.Browser/Handlers/DownloadHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, Download
FullPath = downloadItem.FullPath,
IsCancelled = downloadItem.IsCancelled,
IsComplete = downloadItem.IsComplete,
IsIndeterminate = downloadItem.PercentComplete < 0,
Size = downloadItem.ReceivedBytes,
Url = downloadItem.Url
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser.Data
public class DownloadItemState
{
/// <summary>
/// The current completion of the item, as percentage value from <c>0.0</c> to <c>1.0</c>.
/// The current completion of the item (if available, see <see cref="IsIndeterminate"/>), as percentage value from <c>0.0</c> to <c>1.0</c>.
/// </summary>
public double Completion { get; set; }

Expand All @@ -40,6 +40,16 @@ public class DownloadItemState
/// </summary>
public bool IsComplete { get; set; }

/// <summary>
/// Indicates whether the <see cref="Completion"/> is known or not.
/// </summary>
public bool IsIndeterminate { get; set; }

/// <summary>
/// The current size of the download item in bytes.
/// </summary>
public long Size { get; set; }

/// <summary>
/// The download URL of the item.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
<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" />
<TextBlock Grid.Row="1" Grid.Column="1" Panel.ZIndex="2" Name="Status" Margin="0,0,10,10" />
</Grid>
</UserControl>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Browser
{
internal partial class DownloadItemControl : UserControl
{
private IText text;
private readonly IText text;

internal Guid Id { get; }

Expand All @@ -31,26 +31,75 @@ internal DownloadItemControl(Guid id, IText text)

internal 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}%)";
InitializeIcon(state);
InitializeName(state);

if (File.Exists(state.FullPath))
if (state.IsCancelled)
{
ShowCancelled();
}
else if (state.IsComplete)
{
ShowCompleted(state);
}
else
{
ShowProgress(state);
}
}

private string BuildSizeInfo(DownloadItemState state)
{
return state.Size > 1000000 ? $"{state.Size / 1000000.0:N1} MB" : $"{state.Size / 1000.0:N1} kB";
}

private void InitializeIcon(DownloadItemState state)
{
if (Icon.Content == default && File.Exists(state.FullPath))
{
ItemName.Text = Path.GetFileName(state.FullPath);
Icon.Content = new Image { Source = IconLoader.LoadIconFor(new FileInfo(state.FullPath)) };
}
}

if (state.IsCancelled)
private void InitializeName(DownloadItemState state)
{
var fileName = Path.GetFileName(state.FullPath);

if (ItemName.Text != fileName && File.Exists(state.FullPath))
{
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = text.Get(TextKey.BrowserWindow_DownloadCancelled);
ItemName.Text = fileName;
}
else if (state.IsComplete)
else if (string.IsNullOrEmpty(ItemName.Text))
{
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = text.Get(TextKey.BrowserWindow_DownloadComplete);
ItemName.Text = Uri.TryCreate(state.Url, UriKind.Absolute, out var uri) ? Path.GetFileName(uri.AbsolutePath) : state.Url;
}
}

private void ShowCancelled()
{
Progress.IsIndeterminate = false;
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = text.Get(TextKey.BrowserWindow_DownloadCancelled);
}

private void ShowCompleted(DownloadItemState state)
{
Progress.IsIndeterminate = false;
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = $"{BuildSizeInfo(state)}{text.Get(TextKey.BrowserWindow_DownloadComplete)}";

if (File.Exists(state.FullPath))
{
Icon.Content = new Image { Source = IconLoader.LoadIconFor(new FileInfo(state.FullPath)) };
ItemName.Text = Path.GetFileName(state.FullPath);
}
}

private void ShowProgress(DownloadItemState state)
{
Progress.IsIndeterminate = state.IsIndeterminate;
Progress.Value = state.Completion * 100;
Status.Text = $"{BuildSizeInfo(state)}{text.Get(TextKey.BrowserWindow_Downloading)}{(state.IsIndeterminate ? "" : $" ({state.Completion * 100}%)")}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

using System;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
Expand Down Expand Up @@ -42,8 +44,8 @@ internal partial class BrowserWindow : Window, IBrowserWindow

private WindowClosedEventHandler closed;
private WindowClosingEventHandler closing;
private bool browserControlGetsFocusFromTaskbar = false;
private IInputElement tabKeyDownFocusElement = null;
private bool browserControlGetsFocusFromTaskbar;
private IInputElement tabKeyDownFocusElement;

private WindowSettings WindowSettings
{
Expand Down Expand Up @@ -197,27 +199,15 @@ public void UpdateDownloadState(DownloadItemState state)
{
Dispatcher.InvokeAsync(() =>
{
var isNewItem = true;
var control = Downloads.Children.OfType<DownloadItemControl>().FirstOrDefault(c => c.Id == state.Id);

foreach (var child in Downloads.Children)
if (control == default)
{
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);
control = new DownloadItemControl(state.Id, text);
Downloads.Children.Add(control);
}

control.Update(state);
DownloadsButton.Visibility = Visibility.Visible;
DownloadsPopup.IsOpen = IsActive;
});
Expand Down Expand Up @@ -326,10 +316,8 @@ private void BrowserWindow_KeyUp(object sender, KeyEventArgs e)
else if (MenuPopup.IsKeyboardFocusWithin)
{
var focusedElement = FocusManager.GetFocusedElement(this);
var focusedControl = focusedElement as System.Windows.Controls.Control;
var prevFocusedControl = tabKeyDownFocusElement as System.Windows.Controls.Control;

if (focusedControl != null && prevFocusedControl != null)
if (focusedElement is Control focusedControl && tabKeyDownFocusElement is Control prevFocusedControl)
{
if (!hasShift && focusedControl.TabIndex < prevFocusedControl.TabIndex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
<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" />
<TextBlock Grid.Row="1" Grid.Column="1" Panel.ZIndex="2" Name="Status" Margin="0,0,10,10" />
</Grid>
</UserControl>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Browser
{
public partial class DownloadItemControl : UserControl
{
private IText text;
private readonly IText text;

public Guid Id { get; }

Expand All @@ -29,28 +29,77 @@ public DownloadItemControl(Guid id, IText text)
InitializeComponent();
}

public void Update(DownloadItemState state)
internal 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}%)";
InitializeIcon(state);
InitializeName(state);

if (File.Exists(state.FullPath))
if (state.IsCancelled)
{
ShowCancelled();
}
else if (state.IsComplete)
{
ShowCompleted(state);
}
else
{
ShowProgress(state);
}
}

private string BuildSizeInfo(DownloadItemState state)
{
return state.Size > 1000000 ? $"{state.Size / 1000000.0:N1} MB" : $"{state.Size / 1000.0:N1} kB";
}

private void InitializeIcon(DownloadItemState state)
{
if (Icon.Content == default && File.Exists(state.FullPath))
{
ItemName.Text = Path.GetFileName(state.FullPath);
Icon.Content = new Image { Source = IconLoader.LoadIconFor(new FileInfo(state.FullPath)) };
}
}

if (state.IsCancelled)
private void InitializeName(DownloadItemState state)
{
var fileName = Path.GetFileName(state.FullPath);

if (ItemName.Text != fileName && File.Exists(state.FullPath))
{
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = text.Get(TextKey.BrowserWindow_DownloadCancelled);
ItemName.Text = fileName;
}
else if (state.IsComplete)
else if (string.IsNullOrEmpty(ItemName.Text))
{
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = text.Get(TextKey.BrowserWindow_DownloadComplete);
ItemName.Text = Uri.TryCreate(state.Url, UriKind.Absolute, out var uri) ? Path.GetFileName(uri.AbsolutePath) : state.Url;
}
}

private void ShowCancelled()
{
Progress.IsIndeterminate = false;
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = text.Get(TextKey.BrowserWindow_DownloadCancelled);
}

private void ShowCompleted(DownloadItemState state)
{
Progress.IsIndeterminate = false;
Progress.Visibility = System.Windows.Visibility.Collapsed;
Status.Text = $"{BuildSizeInfo(state)}{text.Get(TextKey.BrowserWindow_DownloadComplete)}";

if (File.Exists(state.FullPath))
{
Icon.Content = new Image { Source = IconLoader.LoadIconFor(new FileInfo(state.FullPath)) };
ItemName.Text = Path.GetFileName(state.FullPath);
}
}

private void ShowProgress(DownloadItemState state)
{
Progress.IsIndeterminate = state.IsIndeterminate;
Progress.Value = state.Completion * 100;
Status.Text = $"{BuildSizeInfo(state)}{text.Get(TextKey.BrowserWindow_Downloading)}{(state.IsIndeterminate ? "" : $" ({state.Completion * 100}%)")}";
}
}
}
37 changes: 14 additions & 23 deletions SafeExamBrowser.UserInterface.Mobile/Windows/BrowserWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

using System;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
Expand Down Expand Up @@ -42,8 +44,8 @@ internal partial class BrowserWindow : Window, IBrowserWindow

private WindowClosedEventHandler closed;
private WindowClosingEventHandler closing;
private bool browserControlGetsFocusFromTaskbar = false;
private IInputElement tabKeyDownFocusElement = null;
private bool browserControlGetsFocusFromTaskbar;
private IInputElement tabKeyDownFocusElement;

private WindowSettings WindowSettings
{
Expand Down Expand Up @@ -197,27 +199,15 @@ public void UpdateDownloadState(DownloadItemState state)
{
Dispatcher.InvokeAsync(() =>
{
var isNewItem = true;
var control = Downloads.Children.OfType<DownloadItemControl>().FirstOrDefault(c => c.Id == state.Id);

foreach (var child in Downloads.Children)
if (control == default)
{
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);
control = new DownloadItemControl(state.Id, text);
Downloads.Children.Add(control);
}

control.Update(state);
DownloadsButton.Visibility = Visibility.Visible;
DownloadsPopup.IsOpen = IsActive;
});
Expand Down Expand Up @@ -265,12 +255,15 @@ private void BrowserWindow_KeyDown(object sender, KeyEventArgs e)
if (e.Key == Key.Tab)
{
var hasShift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;

if (Toolbar.IsKeyboardFocusWithin && hasShift)
{
var firstActiveElementInToolbar = Toolbar.PredictFocus(FocusNavigationDirection.Right);
if (firstActiveElementInToolbar is System.Windows.UIElement)

if (firstActiveElementInToolbar is UIElement)
{
var control = firstActiveElementInToolbar as System.Windows.UIElement;
var control = firstActiveElementInToolbar as UIElement;

if (control.IsKeyboardFocusWithin)
{
LoseFocusRequested?.Invoke(false);
Expand Down Expand Up @@ -322,10 +315,8 @@ private void BrowserWindow_KeyUp(object sender, KeyEventArgs e)
else if (MenuPopup.IsKeyboardFocusWithin)
{
var focusedElement = FocusManager.GetFocusedElement(this);
var focusedControl = focusedElement as System.Windows.Controls.Control;
var prevFocusedControl = tabKeyDownFocusElement as System.Windows.Controls.Control;

if (focusedControl != null && prevFocusedControl != null)
if (focusedElement is Control focusedControl && tabKeyDownFocusElement is Control prevFocusedControl)
{
if (!hasShift && focusedControl.TabIndex < prevFocusedControl.TabIndex)
{
Expand Down
Loading

0 comments on commit f31662a

Please sign in to comment.