Restore SEBPatch
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.ApplicationButton" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Button x:Name="Button" Background="Transparent" Height="40" Padding="10" Template="{StaticResource ActionCenterButton}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ContentControl x:Name="Icon" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,10,0" Width="20" />
|
||||
<TextBlock x:Name="Text" HorizontalAlignment="Left" VerticalAlignment="Center" Padding="5" MaxWidth="350" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class ApplicationButton : UserControl
|
||||
{
|
||||
private readonly IApplication<IApplicationWindow> application;
|
||||
private readonly IApplicationWindow window;
|
||||
|
||||
internal event EventHandler Clicked;
|
||||
|
||||
internal ApplicationButton(IApplication<IApplicationWindow> application, IApplicationWindow window = null)
|
||||
{
|
||||
this.application = application;
|
||||
this.window = window;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeApplicationInstanceButton();
|
||||
}
|
||||
|
||||
private void InitializeApplicationInstanceButton()
|
||||
{
|
||||
var tooltip = window?.Title ?? application.Tooltip;
|
||||
|
||||
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
|
||||
Button.ToolTip = tooltip;
|
||||
Icon.Content = IconResourceLoader.Load(window?.Icon ?? application.Icon);
|
||||
Text.Text = window?.Title ?? application.Name;
|
||||
|
||||
if (tooltip != default)
|
||||
{
|
||||
AutomationProperties.SetName(Button, string.Empty);
|
||||
}
|
||||
|
||||
if (window != default)
|
||||
{
|
||||
window.IconChanged += Window_IconChanged;
|
||||
window.TitleChanged += Window_TitleChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_IconChanged(IconResource icon)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
|
||||
}
|
||||
|
||||
private void Window_TitleChanged(string title)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
Text.Text = title;
|
||||
Button.ToolTip = title;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.ApplicationControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="250" d:DesignWidth="500">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" x:Name="ApplicationName" Background="{StaticResource BackgroundTransparentEmphasisBrush}" FontWeight="Bold" Padding="5" TextAlignment="Center" />
|
||||
<ContentControl Grid.Row="1" x:Name="ApplicationButton" FontWeight="Bold" />
|
||||
<StackPanel Grid.Row="2" x:Name="WindowPanel" Background="{StaticResource BackgroundTransparentEmphasisBrush}" Orientation="Vertical" />
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class ApplicationControl : UserControl, IApplicationControl
|
||||
{
|
||||
private readonly IApplication<IApplicationWindow> application;
|
||||
|
||||
internal ApplicationControl(IApplication<IApplicationWindow> application)
|
||||
{
|
||||
this.application = application;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeApplicationControl();
|
||||
}
|
||||
|
||||
private void InitializeApplicationControl()
|
||||
{
|
||||
var button = new ApplicationButton(application);
|
||||
|
||||
application.WindowsChanged += Application_WindowsChanged;
|
||||
button.Clicked += (o, args) => application.Start();
|
||||
ApplicationName.Text = application.Name;
|
||||
ApplicationName.Visibility = Visibility.Collapsed;
|
||||
ApplicationButton.Content = button;
|
||||
}
|
||||
|
||||
private void Application_WindowsChanged()
|
||||
{
|
||||
Dispatcher.InvokeAsync(Update);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
var windows = application.GetWindows();
|
||||
|
||||
WindowPanel.Children.Clear();
|
||||
|
||||
foreach (var window in windows)
|
||||
{
|
||||
var button = new ApplicationButton(application, window);
|
||||
|
||||
button.Clicked += (o, args) => window.Activate();
|
||||
WindowPanel.Children.Add(button);
|
||||
}
|
||||
|
||||
if (WindowPanel.Children.Count == 0)
|
||||
{
|
||||
ApplicationName.Visibility = Visibility.Collapsed;
|
||||
ApplicationButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplicationName.Visibility = Visibility.Visible;
|
||||
ApplicationButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.AudioControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid x:Name="Grid" Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2">
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="Gray">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock x:Name="AudioDeviceName" Foreground="White" Margin="5" TextAlignment="Center" />
|
||||
<StackPanel Orientation="Horizontal" Height="40">
|
||||
<Button x:Name="MuteButton" Background="Transparent" Foreground="White" Padding="5" Template="{StaticResource ActionCenterButton}" Width="40">
|
||||
<ContentControl x:Name="PopupIcon" Focusable="False" />
|
||||
</Button>
|
||||
<Slider x:Name="Volume" Grid.Column="1" Orientation="Horizontal" TickFrequency="1" Maximum="100" IsSnapToTickEnabled="True"
|
||||
IsMoveToPointEnabled="True" VerticalAlignment="Center" Width="250" Thumb.DragStarted="Volume_DragStarted" Thumb.DragCompleted="Volume_DragCompleted" />
|
||||
<TextBlock Grid.Column="2" FontWeight="DemiBold" FontSize="16" Text="{Binding ElementName=Volume, Path=Value}"
|
||||
TextAlignment="Center" VerticalAlignment="Center" Foreground="White" Width="40" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="Button" Padding="2" Template="{StaticResource ActionCenterButton}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="3*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl x:Name="ButtonIcon" />
|
||||
<TextBlock Grid.Row="1" x:Name="Text" FontSize="11" Foreground="White" TextAlignment="Center" TextTrimming="CharacterEllipsis" TextWrapping="Wrap" VerticalAlignment="Bottom" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class AudioControl : UserControl, ISystemControl
|
||||
{
|
||||
private readonly IAudio audio;
|
||||
private readonly IText text;
|
||||
private bool muted;
|
||||
private IconResource MutedIcon;
|
||||
private IconResource NoDeviceIcon;
|
||||
|
||||
internal AudioControl(IAudio audio, IText text)
|
||||
{
|
||||
this.audio = audio;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeAudioControl();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Popup.IsOpen = false;
|
||||
}
|
||||
|
||||
private void InitializeAudioControl()
|
||||
{
|
||||
var originalBrush = Grid.Background;
|
||||
|
||||
audio.VolumeChanged += Audio_VolumeChanged;
|
||||
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
|
||||
var lastOpenedBySpacePress = false;
|
||||
Button.PreviewKeyDown += (o, args) =>
|
||||
{
|
||||
// For some reason, the popup immediately closes again if opened by a Space Bar key event - as a mitigation,
|
||||
// we record the space bar event and leave the popup open for at least 3 seconds.
|
||||
if (args.Key == System.Windows.Input.Key.Space)
|
||||
{
|
||||
lastOpenedBySpacePress = true;
|
||||
}
|
||||
};
|
||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = Popup.IsMouseOver;
|
||||
}));
|
||||
MuteButton.Click += MuteButton_Click;
|
||||
MutedIcon = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Muted.xaml") };
|
||||
NoDeviceIcon = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Light_NoDevice.xaml") };
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = IsMouseOver;
|
||||
}));
|
||||
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
|
||||
Popup.Closed += (o, args) =>
|
||||
{
|
||||
Grid.Background = originalBrush;
|
||||
lastOpenedBySpacePress = false;
|
||||
};
|
||||
Volume.ValueChanged += Volume_ValueChanged;
|
||||
|
||||
if (audio.HasOutputDevice)
|
||||
{
|
||||
AudioDeviceName.Text = audio.DeviceFullName;
|
||||
Button.IsEnabled = true;
|
||||
UpdateVolume(audio.OutputVolume, audio.OutputMuted);
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioDeviceName.Text = text.Get(TextKey.SystemControl_AudioDeviceNotFound);
|
||||
Button.IsEnabled = false;
|
||||
Button.ToolTip = text.Get(TextKey.SystemControl_AudioDeviceNotFound);
|
||||
ButtonIcon.Content = IconResourceLoader.Load(NoDeviceIcon);
|
||||
Text.Text = text.Get(TextKey.SystemControl_AudioDeviceNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
private void Audio_VolumeChanged(double volume, bool muted)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => UpdateVolume(volume, muted));
|
||||
}
|
||||
|
||||
private void MuteButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (muted)
|
||||
{
|
||||
audio.Unmute();
|
||||
}
|
||||
else
|
||||
{
|
||||
audio.Mute();
|
||||
}
|
||||
}
|
||||
|
||||
private void Volume_DragStarted(object sender, DragStartedEventArgs e)
|
||||
{
|
||||
Volume.ValueChanged -= Volume_ValueChanged;
|
||||
}
|
||||
|
||||
private void Volume_DragCompleted(object sender, DragCompletedEventArgs e)
|
||||
{
|
||||
audio.SetVolume(Volume.Value / 100);
|
||||
Volume.ValueChanged += Volume_ValueChanged;
|
||||
}
|
||||
|
||||
private void Volume_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
audio.SetVolume(Volume.Value / 100);
|
||||
}
|
||||
|
||||
private void UpdateVolume(double volume, bool muted)
|
||||
{
|
||||
var info = BuildInfoText(volume, muted);
|
||||
|
||||
this.muted = muted;
|
||||
|
||||
Button.ToolTip = info;
|
||||
Text.Text = info;
|
||||
Volume.ValueChanged -= Volume_ValueChanged;
|
||||
Volume.Value = Math.Round(volume * 100);
|
||||
Volume.ValueChanged += Volume_ValueChanged;
|
||||
|
||||
AutomationProperties.SetName(Button, info);
|
||||
|
||||
if (muted)
|
||||
{
|
||||
var tooltip = text.Get(TextKey.SystemControl_AudioDeviceUnmuteTooltip);
|
||||
|
||||
MuteButton.ToolTip = tooltip;
|
||||
ButtonIcon.Content = IconResourceLoader.Load(MutedIcon);
|
||||
PopupIcon.Content = IconResourceLoader.Load(MutedIcon);
|
||||
|
||||
AutomationProperties.SetName(MuteButton, tooltip);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tooltip = text.Get(TextKey.SystemControl_AudioDeviceMuteTooltip);
|
||||
|
||||
MuteButton.ToolTip = tooltip;
|
||||
ButtonIcon.Content = LoadIcon(volume);
|
||||
PopupIcon.Content = LoadIcon(volume);
|
||||
|
||||
AutomationProperties.SetName(MuteButton, tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildInfoText(double volume, bool muted)
|
||||
{
|
||||
var info = text.Get(muted ? TextKey.SystemControl_AudioDeviceInfoMuted : TextKey.SystemControl_AudioDeviceInfo);
|
||||
|
||||
info = info.Replace("%%NAME%%", audio.DeviceShortName);
|
||||
info = info.Replace("%%VOLUME%%", Convert.ToString(Math.Round(volume * 100)));
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private UIElement LoadIcon(double volume)
|
||||
{
|
||||
var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33");
|
||||
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Light_{icon}.xaml");
|
||||
var resource = new XamlIconResource { Uri = uri };
|
||||
|
||||
return IconResourceLoader.Load(resource);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.Clock" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2" ToolTip="{Binding Path=ToolTip}">
|
||||
<Button x:Name="Button" IsEnabled="False" Padding="2" Template="{StaticResource ActionCenterButton}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="3*" />
|
||||
</Grid.RowDefinitions>
|
||||
<fa:ImageAwesome Grid.Row="0" Foreground="Black" Icon="ClockOutline" Margin="2" VerticalAlignment="Center" />
|
||||
<Grid Grid.Row="1" Background="Transparent" Margin="5,0" VerticalAlignment="Bottom">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="1*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Name="TimeTextBlock" Grid.Row="0" Text="{Binding Path=Time}" FontSize="11" Foreground="White" HorizontalAlignment="Center" />
|
||||
<TextBlock x:Name="DateTextBlock" Grid.Row="1" Text="{Binding Path=Date}" FontSize="11" Foreground="White" HorizontalAlignment="Center" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.UserInterface.Desktop.ViewModels;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class Clock : UserControl
|
||||
{
|
||||
private DateTimeViewModel model;
|
||||
|
||||
public Clock()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeControl();
|
||||
}
|
||||
|
||||
private void InitializeControl()
|
||||
{
|
||||
model = new DateTimeViewModel(true);
|
||||
DataContext = model;
|
||||
TimeTextBlock.DataContext = model;
|
||||
DateTextBlock.DataContext = model;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.KeyboardLayoutButton" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="250" IsTabStop="True">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Button x:Name="Button" Background="Transparent" Height="40" Padding="10,0" Template="{StaticResource ActionCenterButton}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="IsCurrentTextBlock" Grid.Column="0" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden">•</TextBlock>
|
||||
<TextBlock x:Name="CultureCodeTextBlock" Grid.Column="1" FontWeight="Bold" HorizontalAlignment="Left" Margin="10,0,5,0" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Column="2" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="CultureNameTextBlock" Foreground="White" HorizontalAlignment="Left" Margin="5,0,10,0" TextDecorations="Underline" VerticalAlignment="Center" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<fa:ImageAwesome Foreground="LightGray" Height="10" Icon="KeyboardOutline" Margin="5,0" />
|
||||
<TextBlock x:Name="LayoutNameTextBlock" Foreground="LightGray" HorizontalAlignment="Left" Margin="0,0,10,0" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class KeyboardLayoutButton : UserControl
|
||||
{
|
||||
private readonly IKeyboardLayout layout;
|
||||
|
||||
internal bool IsCurrent
|
||||
{
|
||||
set { IsCurrentTextBlock.Visibility = value ? Visibility.Visible : Visibility.Hidden; }
|
||||
}
|
||||
|
||||
internal Guid LayoutId
|
||||
{
|
||||
get { return layout.Id; }
|
||||
}
|
||||
|
||||
internal event EventHandler LayoutSelected;
|
||||
|
||||
internal KeyboardLayoutButton(IKeyboardLayout layout)
|
||||
{
|
||||
this.layout = layout;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeLayoutButton();
|
||||
}
|
||||
|
||||
private void InitializeLayoutButton()
|
||||
{
|
||||
Button.Click += (o, args) => LayoutSelected?.Invoke(this, EventArgs.Empty);
|
||||
CultureCodeTextBlock.Text = layout.CultureCode;
|
||||
CultureNameTextBlock.Text = layout.CultureName;
|
||||
LayoutNameTextBlock.Text = layout.LayoutName;
|
||||
|
||||
AutomationProperties.SetName(Button, layout.CultureName);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.KeyboardLayoutControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid x:Name="Grid" Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2">
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}" KeyUp="Popup_KeyUp">
|
||||
<Border Background="Gray">
|
||||
<ScrollViewer MaxHeight="250" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
|
||||
<StackPanel x:Name="LayoutsStackPanel" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="Button" Padding="2" Template="{StaticResource ActionCenterButton}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="3*" />
|
||||
</Grid.RowDefinitions>
|
||||
<fa:ImageAwesome Grid.Row="0" Foreground="Black" Icon="KeyboardOutline" Margin="2" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Row="1" x:Name="Text" FontSize="11" Foreground="White" TextAlignment="Center" TextTrimming="CharacterEllipsis" TextWrapping="Wrap" VerticalAlignment="Bottom" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Threading.Tasks;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class KeyboardLayoutControl : UserControl, ISystemControl
|
||||
{
|
||||
private readonly IKeyboard keyboard;
|
||||
private readonly IText text;
|
||||
|
||||
internal KeyboardLayoutControl(IKeyboard keyboard, IText text)
|
||||
{
|
||||
this.keyboard = keyboard;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeKeyboardLayoutControl();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Dispatcher.Invoke(() => Popup.IsOpen = false);
|
||||
}
|
||||
|
||||
private void InitializeKeyboardLayoutControl()
|
||||
{
|
||||
var originalBrush = Grid.Background;
|
||||
|
||||
InitializeLayouts();
|
||||
|
||||
keyboard.LayoutChanged += Keyboard_LayoutChanged;
|
||||
Button.Click += (o, args) =>
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
this.Dispatcher.BeginInvoke((System.Action) (() =>
|
||||
{
|
||||
LayoutsStackPanel.Children[0].Focus();
|
||||
}));
|
||||
};
|
||||
var lastOpenedBySpacePress = false;
|
||||
Button.PreviewKeyDown += (o, args) =>
|
||||
{
|
||||
// For some reason, the popup immediately closes again if opened by a Space Bar key event - as a mitigation,
|
||||
// we record the space bar event and leave the popup open for at least 3 seconds.
|
||||
if (args.Key == System.Windows.Input.Key.Space)
|
||||
{
|
||||
lastOpenedBySpacePress = true;
|
||||
}
|
||||
};
|
||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = Popup.IsMouseOver;
|
||||
}));
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = IsMouseOver;
|
||||
}));
|
||||
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
|
||||
Popup.Closed += (o, args) =>
|
||||
{
|
||||
Grid.Background = originalBrush;
|
||||
lastOpenedBySpacePress = false;
|
||||
};
|
||||
}
|
||||
|
||||
private void Keyboard_LayoutChanged(IKeyboardLayout layout)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => SetCurrent(layout));
|
||||
}
|
||||
|
||||
private void InitializeLayouts()
|
||||
{
|
||||
foreach (var layout in keyboard.GetLayouts())
|
||||
{
|
||||
var button = new KeyboardLayoutButton(layout);
|
||||
|
||||
button.LayoutSelected += (o, args) => ActivateLayout(layout);
|
||||
LayoutsStackPanel.Children.Add(button);
|
||||
|
||||
if (layout.IsCurrent)
|
||||
{
|
||||
SetCurrent(layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateLayout(IKeyboardLayout layout)
|
||||
{
|
||||
Popup.IsOpen = false;
|
||||
keyboard.ActivateLayout(layout.Id);
|
||||
}
|
||||
|
||||
private void SetCurrent(IKeyboardLayout layout)
|
||||
{
|
||||
var tooltip = text.Get(TextKey.SystemControl_KeyboardLayoutTooltip).Replace("%%LAYOUT%%", layout.CultureName);
|
||||
|
||||
foreach (var child in LayoutsStackPanel.Children)
|
||||
{
|
||||
if (child is KeyboardLayoutButton layoutButton)
|
||||
{
|
||||
layoutButton.IsCurrent = layout.Id == layoutButton.LayoutId;
|
||||
}
|
||||
}
|
||||
|
||||
Text.Text = layout.CultureName;
|
||||
Button.ToolTip = tooltip;
|
||||
|
||||
AutomationProperties.SetName(Button, tooltip);
|
||||
}
|
||||
|
||||
private void Popup_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == System.Windows.Input.Key.Enter || e.Key == System.Windows.Input.Key.Escape)
|
||||
{
|
||||
Popup.IsOpen = false;
|
||||
Button.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.NetworkButton" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="250">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Button x:Name="Button" Background="Transparent" Height="40" Padding="10,0" Template="{StaticResource ActionCenterButton}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="IsCurrentTextBlock" Grid.Column="0" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden">•</TextBlock>
|
||||
<TextBlock x:Name="SignalStrengthTextBlock" Grid.Column="1" Foreground="White" HorizontalAlignment="Left" Margin="10,0,5,0" VerticalAlignment="Center" />
|
||||
<TextBlock x:Name="NetworkNameTextBlock" Grid.Column="2" FontWeight="Bold" HorizontalAlignment="Left" Margin="5,0,10,0" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Windows;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Network;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class NetworkButton : UserControl
|
||||
{
|
||||
private readonly IWirelessNetwork network;
|
||||
|
||||
internal event EventHandler NetworkSelected;
|
||||
|
||||
internal NetworkButton(IWirelessNetwork network)
|
||||
{
|
||||
this.network = network;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeNetworkButton();
|
||||
}
|
||||
|
||||
private void InitializeNetworkButton()
|
||||
{
|
||||
Button.Click += (o, args) => NetworkSelected?.Invoke(this, EventArgs.Empty);
|
||||
IsCurrentTextBlock.Visibility = network.Status == ConnectionStatus.Connected ? Visibility.Visible : Visibility.Hidden;
|
||||
NetworkNameTextBlock.Text = network.Name;
|
||||
SignalStrengthTextBlock.Text = $"{network.SignalStrength}%";
|
||||
}
|
||||
|
||||
public void SetFocus()
|
||||
{
|
||||
Button.Focus();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.NetworkControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid x:Name="Grid" Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2">
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="Gray">
|
||||
<ScrollViewer MaxHeight="250" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
|
||||
<StackPanel x:Name="WirelessNetworksStackPanel" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="Button" Padding="2" Template="{StaticResource ActionCenterButton}" ToolTipService.ShowOnDisabled="True">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="3*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Viewbox x:Name="WirelessIcon" Stretch="Uniform" Width="Auto" Visibility="Collapsed" />
|
||||
<fa:ImageAwesome x:Name="WiredIcon" Icon="Tv" Margin="0,2,4,4" Visibility="Collapsed" />
|
||||
<Border Background="White" CornerRadius="6" Height="12" HorizontalAlignment="Right" Margin="-3,0" Panel.ZIndex="10" VerticalAlignment="Bottom">
|
||||
<fa:ImageAwesome x:Name="NetworkStatusIcon" />
|
||||
</Border>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="1" x:Name="Text" FontSize="11" Foreground="White" TextAlignment="Center" TextTrimming="CharacterEllipsis" TextWrapping="Wrap" VerticalAlignment="Bottom" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using FontAwesome.WPF;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Network;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class NetworkControl : UserControl, ISystemControl
|
||||
{
|
||||
private readonly INetworkAdapter adapter;
|
||||
private readonly IText text;
|
||||
|
||||
internal NetworkControl(INetworkAdapter adapter, IText text)
|
||||
{
|
||||
this.adapter = adapter;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeWirelessNetworkControl();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Popup.IsOpen = false);
|
||||
}
|
||||
|
||||
private void InitializeWirelessNetworkControl()
|
||||
{
|
||||
var lastOpenedBySpacePress = false;
|
||||
var originalBrush = Grid.Background;
|
||||
|
||||
adapter.Changed += () => Dispatcher.InvokeAsync(Update);
|
||||
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
|
||||
Button.PreviewKeyDown += (o, args) =>
|
||||
{
|
||||
// For some reason, the popup immediately closes again if opened by a Space Bar key event - as a mitigation,
|
||||
// we record the space bar event and leave the popup open for at least 3 seconds.
|
||||
if (args.Key == System.Windows.Input.Key.Space)
|
||||
{
|
||||
lastOpenedBySpacePress = true;
|
||||
}
|
||||
};
|
||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = Popup.IsMouseOver;
|
||||
}));
|
||||
Popup.Closed += (o, args) =>
|
||||
{
|
||||
adapter.StopWirelessNetworkScanning();
|
||||
Grid.Background = originalBrush;
|
||||
lastOpenedBySpacePress = false;
|
||||
};
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = IsMouseOver;
|
||||
}));
|
||||
Popup.Opened += (o, args) =>
|
||||
{
|
||||
adapter.StartWirelessNetworkScanning();
|
||||
Grid.Background = Brushes.Gray;
|
||||
Task.Delay(100).ContinueWith((task) => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (WirelessNetworksStackPanel.Children.Count > 0)
|
||||
{
|
||||
(WirelessNetworksStackPanel.Children[0] as NetworkButton)?.SetFocus();
|
||||
}
|
||||
}));
|
||||
};
|
||||
WirelessIcon.Child = GetWirelessIcon(0);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
switch (adapter.Type)
|
||||
{
|
||||
case ConnectionType.Wired:
|
||||
Button.IsEnabled = false;
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkWiredConnected));
|
||||
WiredIcon.Visibility = Visibility.Visible;
|
||||
WirelessIcon.Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
case ConnectionType.Wireless:
|
||||
Button.IsEnabled = true;
|
||||
WiredIcon.Visibility = Visibility.Collapsed;
|
||||
WirelessIcon.Visibility = Visibility.Visible;
|
||||
break;
|
||||
default:
|
||||
Button.IsEnabled = false;
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkNotAvailable));
|
||||
WiredIcon.Visibility = Visibility.Visible;
|
||||
WirelessIcon.Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (adapter.Status)
|
||||
{
|
||||
case ConnectionStatus.Connected:
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkWiredConnected));
|
||||
NetworkStatusIcon.Rotation = 0;
|
||||
NetworkStatusIcon.Source = ImageAwesome.CreateImageSource(FontAwesomeIcon.Globe, Brushes.Green);
|
||||
NetworkStatusIcon.Spin = false;
|
||||
break;
|
||||
case ConnectionStatus.Connecting:
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnecting));
|
||||
NetworkStatusIcon.Rotation = 0;
|
||||
NetworkStatusIcon.Source = ImageAwesome.CreateImageSource(FontAwesomeIcon.Cog, Brushes.DimGray);
|
||||
NetworkStatusIcon.Spin = true;
|
||||
NetworkStatusIcon.SpinDuration = 2;
|
||||
break;
|
||||
default:
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkDisconnected));
|
||||
NetworkStatusIcon.Source = ImageAwesome.CreateImageSource(FontAwesomeIcon.Ban, Brushes.DarkOrange);
|
||||
NetworkStatusIcon.Spin = false;
|
||||
WirelessIcon.Child = GetWirelessIcon(0);
|
||||
break;
|
||||
}
|
||||
|
||||
WirelessNetworksStackPanel.Children.Clear();
|
||||
|
||||
foreach (var network in adapter.GetWirelessNetworks())
|
||||
{
|
||||
var button = new NetworkButton(network);
|
||||
|
||||
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
|
||||
|
||||
if (network.Status == ConnectionStatus.Connected)
|
||||
{
|
||||
WirelessIcon.Child = GetWirelessIcon(network.SignalStrength);
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name));
|
||||
}
|
||||
|
||||
WirelessNetworksStackPanel.Children.Add(button);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateText(string text)
|
||||
{
|
||||
Button.ToolTip = text;
|
||||
Text.Text = text;
|
||||
Button.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text);
|
||||
}
|
||||
|
||||
private UIElement GetWirelessIcon(int signalStrength)
|
||||
{
|
||||
var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0"));
|
||||
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/WiFi_Light_{icon}.xaml");
|
||||
var resource = new XamlIconResource { Uri = uri };
|
||||
|
||||
return IconResourceLoader.Load(resource);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.NotificationButton" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2">
|
||||
<Button x:Name="IconButton" Click="IconButton_Click" Padding="2" Template="{StaticResource ActionCenterButton}" ToolTipService.ShowOnDisabled="True">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="3*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl Grid.Row="0" x:Name="Icon" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Row="1" x:Name="Text" FontSize="11" Foreground="White" TextAlignment="Center" TextTrimming="CharacterEllipsis" TextWrapping="Wrap" VerticalAlignment="Bottom" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.Core.Contracts.Notifications;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class NotificationButton : UserControl, INotificationControl
|
||||
{
|
||||
private readonly INotification notification;
|
||||
|
||||
internal NotificationButton(INotification notification)
|
||||
{
|
||||
this.notification = notification;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeNotification();
|
||||
UpdateNotification();
|
||||
}
|
||||
|
||||
private void IconButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (notification.CanActivate)
|
||||
{
|
||||
notification.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeNotification()
|
||||
{
|
||||
notification.NotificationChanged += () => Dispatcher.Invoke(UpdateNotification);
|
||||
}
|
||||
|
||||
private void UpdateNotification()
|
||||
{
|
||||
Icon.Content = IconResourceLoader.Load(notification.IconResource);
|
||||
IconButton.IsEnabled = notification.CanActivate;
|
||||
IconButton.ToolTip = notification.Tooltip;
|
||||
Text.Text = notification.Tooltip;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.PowerSupplyControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2">
|
||||
<Button x:Name="Button" IsEnabled="False" Padding="2" ToolTipService.ShowOnDisabled="True" Template="{StaticResource ActionCenterButton}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="3*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Viewbox Grid.Row="0" Stretch="Uniform" Width="Auto">
|
||||
<Canvas Height="40" Width="40">
|
||||
<Viewbox Stretch="Uniform" Width="40" Panel.ZIndex="2">
|
||||
<Canvas Width="1024.000" Height="1024.000">
|
||||
<Canvas.LayoutTransform>
|
||||
<RotateTransform Angle="180" />
|
||||
</Canvas.LayoutTransform>
|
||||
<Canvas>
|
||||
<Path Data=" M 0.000,0.000 L 1024.000,0.000 L 1024.000,1024.000 L 0.000,1024.000 L 0.000,0.000 Z"/>
|
||||
<Path StrokeThickness="35.0" Stroke="#ff000000" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data=" M 112.000,300.000 L 951.000,300.000 C 970.330,300.000 986.000,315.670 986.000,335.000 L 986.000,689.000 C 986.000,708.330 970.330,724.000 951.000,724.000 L 112.000,724.000 C 92.670,724.000 77.000,708.330 77.000,689.000 L 77.000,335.000 C 77.000,315.670 92.670,300.000 112.000,300.000 Z"/>
|
||||
<Path StrokeThickness="1.0" Stroke="#ff000000" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Fill="#ff000000" Data=" M 25.000,462.500 L 50.000,462.500 C 52.760,462.500 55.000,464.740 55.000,467.500 L 55.000,557.500 C 55.000,560.260 52.760,562.500 50.000,562.500 L 25.000,562.500 C 22.240,562.500 20.000,560.260 20.000,557.500 L 20.000,467.500 C 20.000,464.740 22.240,462.500 25.000,462.500 Z"/>
|
||||
</Canvas>
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
<Rectangle x:Name="BatteryCharge" Canvas.Left="2" Canvas.Top="12" Fill="Green" Height="16" Width="35" Panel.ZIndex="1" />
|
||||
<Canvas x:Name="PowerPlug" Panel.ZIndex="3" Canvas.Left="4" Canvas.Top="-3">
|
||||
<Canvas.LayoutTransform>
|
||||
<ScaleTransform ScaleX="2" ScaleY="2" />
|
||||
</Canvas.LayoutTransform>
|
||||
<Path Stroke="Black" StrokeStartLineCap="Round" Fill="Black" Data="M2.5,17.5 V10 Q5,10 5,6 H4 V4 H4 V6 H1 V4 H1 V6 H0 Q0,10 2.5,10" />
|
||||
</Canvas>
|
||||
<TextBlock x:Name="Warning" FontSize="36" FontWeight="ExtraBold" Foreground="Red" Canvas.Left="13" Canvas.Top="-7" Panel.ZIndex="3">!</TextBlock>
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
<TextBlock Grid.Row="1" x:Name="Text" FontSize="11" Foreground="White" TextAlignment="Center" TextTrimming="CharacterEllipsis" TextWrapping="Wrap" VerticalAlignment="Bottom" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class PowerSupplyControl : UserControl, ISystemControl
|
||||
{
|
||||
private Brush initialBrush;
|
||||
private bool infoShown, warningShown;
|
||||
private double maxWidth;
|
||||
private IPowerSupply powerSupply;
|
||||
private IText text;
|
||||
|
||||
internal PowerSupplyControl(IPowerSupply powerSupply, IText text)
|
||||
{
|
||||
this.powerSupply = powerSupply;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializePowerSupplyControl();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
}
|
||||
|
||||
public void SetInformation(string text)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Text.Text = text);
|
||||
}
|
||||
|
||||
private void InitializePowerSupplyControl()
|
||||
{
|
||||
initialBrush = BatteryCharge.Fill;
|
||||
maxWidth = BatteryCharge.Width;
|
||||
powerSupply.StatusChanged += PowerSupply_StatusChanged;
|
||||
UpdateStatus(powerSupply.GetStatus());
|
||||
}
|
||||
|
||||
private void PowerSupply_StatusChanged(IPowerSupplyStatus status)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => UpdateStatus(status));
|
||||
}
|
||||
|
||||
private void UpdateStatus(IPowerSupplyStatus status)
|
||||
{
|
||||
var percentage = Math.Round(status.BatteryCharge * 100);
|
||||
var tooltip = string.Empty;
|
||||
|
||||
RenderCharge(status.BatteryCharge, status.BatteryChargeStatus);
|
||||
|
||||
if (status.IsOnline)
|
||||
{
|
||||
infoShown = false;
|
||||
warningShown = false;
|
||||
tooltip = text.Get(percentage == 100 ? TextKey.SystemControl_BatteryCharged : TextKey.SystemControl_BatteryCharging);
|
||||
tooltip = tooltip.Replace("%%CHARGE%%", percentage.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
tooltip = text.Get(TextKey.SystemControl_BatteryRemainingCharge);
|
||||
tooltip = tooltip.Replace("%%CHARGE%%", percentage.ToString());
|
||||
tooltip = tooltip.Replace("%%HOURS%%", status.BatteryTimeRemaining.Hours.ToString());
|
||||
tooltip = tooltip.Replace("%%MINUTES%%", status.BatteryTimeRemaining.Minutes.ToString());
|
||||
|
||||
HandleBatteryStatus(status.BatteryChargeStatus);
|
||||
}
|
||||
|
||||
if (!infoShown && !warningShown)
|
||||
{
|
||||
Button.ToolTip = tooltip;
|
||||
}
|
||||
|
||||
PowerPlug.Visibility = status.IsOnline ? Visibility.Visible : Visibility.Collapsed;
|
||||
Text.Text = tooltip;
|
||||
Warning.Visibility = status.BatteryChargeStatus == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed;
|
||||
this.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, tooltip);
|
||||
}
|
||||
|
||||
private void RenderCharge(double charge, BatteryChargeStatus status)
|
||||
{
|
||||
var width = maxWidth * charge;
|
||||
|
||||
BatteryCharge.Width = width > maxWidth ? maxWidth : (width < 0 ? 0 : width);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case BatteryChargeStatus.Critical:
|
||||
BatteryCharge.Fill = Brushes.Red;
|
||||
break;
|
||||
case BatteryChargeStatus.Low:
|
||||
BatteryCharge.Fill = Brushes.Orange;
|
||||
break;
|
||||
default:
|
||||
BatteryCharge.Fill = initialBrush;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleBatteryStatus(BatteryChargeStatus chargeStatus)
|
||||
{
|
||||
if (chargeStatus == BatteryChargeStatus.Low && !infoShown)
|
||||
{
|
||||
Button.ToolTip = text.Get(TextKey.SystemControl_BatteryChargeLowInfo);
|
||||
infoShown = true;
|
||||
}
|
||||
|
||||
if (chargeStatus == BatteryChargeStatus.Critical && !warningShown)
|
||||
{
|
||||
Button.ToolTip = text.Get(TextKey.SystemControl_BatteryChargeCriticalWarning);
|
||||
warningShown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.QuitButton" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2">
|
||||
<Button x:Name="Button" Padding="2" Template="{StaticResource ActionCenterButton}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="3*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl Grid.Row="0" x:Name="Icon" Foreground="Black" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Row="1" x:Name="Text" FontSize="11" Foreground="White" TextAlignment="Center" TextTrimming="CharacterEllipsis" TextWrapping="Wrap" VerticalAlignment="Bottom" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.ComponentModel;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
internal partial class QuitButton : UserControl
|
||||
{
|
||||
internal event QuitButtonClickedEventHandler Clicked;
|
||||
|
||||
public QuitButton()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeControl();
|
||||
}
|
||||
|
||||
private void InitializeControl()
|
||||
{
|
||||
var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml");
|
||||
var resource = new XamlIconResource { Uri = uri };
|
||||
|
||||
Icon.Content = IconResourceLoader.Load(resource);
|
||||
Button.Click += (o, args) => Clicked?.Invoke(new CancelEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter.RaiseHandControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="125">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Name="Grid" Background="{StaticResource ActionCenterDarkBrush}" Height="64" Margin="2">
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="Gray" BorderThickness="0" >
|
||||
<StackPanel>
|
||||
<TextBox Name="Message" AcceptsReturn="True" Height="150" IsReadOnly="False" Margin="5,5,5,0" Width="350" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
|
||||
<Grid>
|
||||
<Button Name="HandButton" Background="Transparent" Height="30" Margin="5" Padding="5" Template="{StaticResource TaskbarButton}" Width="150">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="HandButtonText" FontWeight="Bold" TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="NotificationButton" Padding="2" Template="{StaticResource ActionCenterButton}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="3*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl x:Name="Icon" />
|
||||
<TextBlock Grid.Row="1" x:Name="Text" FontSize="11" Foreground="White" TextAlignment="Center" TextTrimming="CharacterEllipsis" TextWrapping="Wrap" VerticalAlignment="Bottom" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
|
||||
{
|
||||
public partial class RaiseHandControl : UserControl, INotificationControl
|
||||
{
|
||||
private readonly IProctoringController controller;
|
||||
private readonly ProctoringSettings settings;
|
||||
private readonly IText text;
|
||||
|
||||
private IconResource LoweredIcon;
|
||||
private IconResource RaisedIcon;
|
||||
|
||||
public RaiseHandControl(IProctoringController controller, ProctoringSettings settings, IText text)
|
||||
{
|
||||
this.controller = controller;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeRaiseHandControl();
|
||||
}
|
||||
|
||||
private void InitializeRaiseHandControl()
|
||||
{
|
||||
var originalBrush = Grid.Background;
|
||||
|
||||
controller.HandLowered += () => Dispatcher.Invoke(ShowLowered);
|
||||
controller.HandRaised += () => Dispatcher.Invoke(ShowRaised);
|
||||
|
||||
HandButton.Click += RaiseHandButton_Click;
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
|
||||
LoweredIcon = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Hand_Lowered.xaml") };
|
||||
RaisedIcon = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Hand_Raised.xaml") };
|
||||
Icon.Content = IconResourceLoader.Load(LoweredIcon);
|
||||
|
||||
var lastOpenedBySpacePress = false;
|
||||
NotificationButton.PreviewKeyDown += (o, args) =>
|
||||
{
|
||||
if (args.Key == System.Windows.Input.Key.Space) // for some reason, the popup immediately closes again if opened by a Space Bar key event - as a mitigation, we record the space bar event and leave the popup open for at least 3 seconds
|
||||
{
|
||||
lastOpenedBySpacePress = true;
|
||||
}
|
||||
};
|
||||
NotificationButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = Popup.IsMouseOver;
|
||||
}));
|
||||
NotificationButton.PreviewMouseLeftButtonUp += NotificationButton_PreviewMouseLeftButtonUp;
|
||||
NotificationButton.PreviewMouseRightButtonUp += NotificationButton_PreviewMouseRightButtonUp;
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
|
||||
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = IsMouseOver;
|
||||
}));
|
||||
Popup.Opened += (o, args) => Grid.Background = Brushes.Gray;
|
||||
Popup.Closed += (o, args) =>
|
||||
{
|
||||
Grid.Background = originalBrush;
|
||||
lastOpenedBySpacePress = false;
|
||||
};
|
||||
|
||||
Text.Text = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (settings.ForceRaiseHandMessage || Popup.IsOpen)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void RaiseHandButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
|
||||
private void ToggleHand()
|
||||
{
|
||||
if (controller.IsHandRaised)
|
||||
{
|
||||
controller.LowerHand();
|
||||
}
|
||||
else
|
||||
{
|
||||
controller.RaiseHand(Message.Text);
|
||||
Message.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowLowered()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
Icon.Content = IconResourceLoader.Load(LoweredIcon);
|
||||
Message.IsEnabled = true;
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
Popup.IsOpen = false;
|
||||
Text.Text = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
}
|
||||
|
||||
private void ShowRaised()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringLowerHand);
|
||||
Icon.Content = IconResourceLoader.Load(RaisedIcon);
|
||||
Message.IsEnabled = false;
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandRaised);
|
||||
Text.Text = text.Get(TextKey.Notification_ProctoringHandRaised);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Browser.DownloadItemControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls.Browser"
|
||||
mc:Ignorable="d" d:DesignHeight="50" d:DesignWidth="200">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Panel.ZIndex="2" Name="Icon" Margin="10" Width="25" />
|
||||
<ProgressBar Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Grid.ColumnSpan="2" Panel.ZIndex="1" Name="Progress" BorderThickness="0" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Panel.ZIndex="2" Name="ItemName" FontWeight="Bold" Margin="0,10,10,0" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Panel.ZIndex="2" Name="Status" FontStyle="Italic" Margin="0,0,10,10" />
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Browser
|
||||
{
|
||||
internal partial class DownloadItemControl : UserControl
|
||||
{
|
||||
private IText text;
|
||||
|
||||
internal Guid Id { get; }
|
||||
|
||||
internal DownloadItemControl(Guid id, IText text)
|
||||
{
|
||||
this.Id = id;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
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}%)";
|
||||
|
||||
if (File.Exists(state.FullPath))
|
||||
{
|
||||
ItemName.Text = Path.GetFileName(state.FullPath);
|
||||
Icon.Content = new Image { Source = IconLoader.LoadIconFor(new FileInfo(state.FullPath)) };
|
||||
}
|
||||
|
||||
if (state.IsCancelled)
|
||||
{
|
||||
Progress.Visibility = System.Windows.Visibility.Collapsed;
|
||||
Status.Text = text.Get(TextKey.BrowserWindow_DownloadCancelled);
|
||||
}
|
||||
else if (state.IsComplete)
|
||||
{
|
||||
Progress.Visibility = System.Windows.Visibility.Collapsed;
|
||||
Status.Text = text.Get(TextKey.BrowserWindow_DownloadComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.ApplicationControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="50">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Popup x:Name="WindowPopup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0">
|
||||
<ScrollViewer MaxHeight="400" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
|
||||
<StackPanel x:Name="WindowStackPanel" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="Button" Background="{StaticResource BackgroundBrush}" Padding="4" Template="{StaticResource TaskbarButton}" Width="50" />
|
||||
<Grid>
|
||||
<Rectangle x:Name="ActiveBar" Cursor="Hand" Height="2.5" Width="40" VerticalAlignment="Bottom" Fill="DodgerBlue" Visibility="Collapsed" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class ApplicationControl : UserControl, IApplicationControl
|
||||
{
|
||||
private readonly IApplication<IApplicationWindow> application;
|
||||
private IApplicationWindow single;
|
||||
|
||||
internal ApplicationControl(IApplication<IApplicationWindow> application)
|
||||
{
|
||||
this.application = application;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeApplicationControl();
|
||||
}
|
||||
|
||||
private void InitializeApplicationControl()
|
||||
{
|
||||
var originalBrush = Button.Background;
|
||||
|
||||
application.WindowsChanged += Application_WindowsChanged;
|
||||
|
||||
ActiveBar.MouseLeave += (o, args) => WindowPopup.IsOpen &= WindowPopup.IsMouseOver || Button.IsMouseOver;
|
||||
Button.Click += Button_Click;
|
||||
Button.Content = IconResourceLoader.Load(application.Icon);
|
||||
Button.MouseEnter += (o, args) => WindowPopup.IsOpen = WindowStackPanel.Children.Count > 0;
|
||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = WindowPopup.IsMouseOver || ActiveBar.IsMouseOver));
|
||||
Button.ToolTip = application.Tooltip;
|
||||
WindowPopup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(WindowPopup_PlacementCallback);
|
||||
WindowPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = IsMouseOver));
|
||||
|
||||
if (application.Tooltip != default)
|
||||
{
|
||||
AutomationProperties.SetName(Button, application.Tooltip);
|
||||
}
|
||||
|
||||
WindowPopup.Opened += (o, args) =>
|
||||
{
|
||||
ActiveBar.Width = double.NaN;
|
||||
Background = Brushes.LightGray;
|
||||
Button.Background = Brushes.LightGray;
|
||||
};
|
||||
|
||||
WindowPopup.Closed += (o, args) =>
|
||||
{
|
||||
ActiveBar.Width = 40;
|
||||
Background = originalBrush;
|
||||
Button.Background = originalBrush;
|
||||
};
|
||||
}
|
||||
|
||||
private void Application_WindowsChanged()
|
||||
{
|
||||
Dispatcher.InvokeAsync(Update);
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (WindowStackPanel.Children.Count == 0)
|
||||
{
|
||||
application.Start();
|
||||
}
|
||||
else if (WindowStackPanel.Children.Count == 1)
|
||||
{
|
||||
single?.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] WindowPopup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
var windows = application.GetWindows();
|
||||
|
||||
ActiveBar.Visibility = windows.Any() ? Visibility.Visible : Visibility.Collapsed;
|
||||
WindowStackPanel.Children.Clear();
|
||||
|
||||
foreach (var window in windows)
|
||||
{
|
||||
WindowStackPanel.Children.Add(new ApplicationWindowButton(window));
|
||||
}
|
||||
|
||||
if (WindowStackPanel.Children.Count == 1)
|
||||
{
|
||||
single = windows.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
single = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.ApplicationWindowButton" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="250">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Button x:Name="Button" Background="Transparent" Height="40" Padding="10" Template="{StaticResource TaskbarButton}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ContentControl x:Name="Icon" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,10,0" Width="20" />
|
||||
<TextBlock x:Name="Text" HorizontalAlignment="Left" VerticalAlignment="Center" Padding="5" MaxWidth="350" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class ApplicationWindowButton : UserControl
|
||||
{
|
||||
private IApplicationWindow window;
|
||||
|
||||
internal ApplicationWindowButton(IApplicationWindow window)
|
||||
{
|
||||
this.window = window;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeApplicationInstanceButton();
|
||||
}
|
||||
|
||||
private void InitializeApplicationInstanceButton()
|
||||
{
|
||||
Button.Click += Button_Click;
|
||||
Button.ToolTip = window.Title;
|
||||
Icon.Content = IconResourceLoader.Load(window.Icon);
|
||||
window.IconChanged += Instance_IconChanged;
|
||||
window.TitleChanged += Window_TitleChanged;
|
||||
Text.Text = window.Title;
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
private void Instance_IconChanged(IconResource icon)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
|
||||
}
|
||||
|
||||
private void Window_TitleChanged(string title)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
Text.Text = title;
|
||||
Button.ToolTip = title;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.AudioControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}" KeyDown="Popup_KeyDown">
|
||||
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock x:Name="AudioDeviceName" Margin="5" TextAlignment="Center" />
|
||||
<StackPanel Orientation="Horizontal" Height="40">
|
||||
<Button x:Name="MuteButton" Background="Transparent" Padding="5" Template="{StaticResource TaskbarButton}" Width="40">
|
||||
<ContentControl x:Name="PopupIcon" Focusable="False" />
|
||||
</Button>
|
||||
<Slider x:Name="Volume" Grid.Column="1" Orientation="Horizontal" TickFrequency="1" Maximum="100" IsSnapToTickEnabled="True" KeyDown="Volume_KeyDown"
|
||||
IsMoveToPointEnabled="True" VerticalAlignment="Center" Width="250" Thumb.DragStarted="Volume_DragStarted" Thumb.DragCompleted="Volume_DragCompleted" />
|
||||
<TextBlock Grid.Column="2" FontWeight="DemiBold" FontSize="16" Text="{Binding ElementName=Volume, Path=Value}"
|
||||
TextAlignment="Center" VerticalAlignment="Center" Width="40" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="Button" Background="Transparent" Padding="5" Template="{StaticResource TaskbarButton}" ToolTipService.ShowOnDisabled="True" Width="40">
|
||||
<ContentControl x:Name="ButtonIcon" Focusable="False" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class AudioControl : UserControl, ISystemControl
|
||||
{
|
||||
private readonly IAudio audio;
|
||||
private readonly IText text;
|
||||
private bool muted;
|
||||
private IconResource MutedIcon;
|
||||
private IconResource NoDeviceIcon;
|
||||
|
||||
internal AudioControl(IAudio audio, IText text)
|
||||
{
|
||||
this.audio = audio;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeAudioControl();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Popup.IsOpen = false;
|
||||
}
|
||||
|
||||
private void InitializeAudioControl()
|
||||
{
|
||||
var originalBrush = Button.Background;
|
||||
|
||||
audio.VolumeChanged += Audio_VolumeChanged;
|
||||
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
|
||||
var lastOpenedBySpacePress = false;
|
||||
Button.PreviewKeyDown += (o, args) =>
|
||||
{
|
||||
// For some reason, the popup immediately closes again if opened by a Space Bar key event - as a mitigation,
|
||||
// we record the space bar event and leave the popup open for at least 3 seconds.
|
||||
if (args.Key == System.Windows.Input.Key.Space)
|
||||
{
|
||||
lastOpenedBySpacePress = true;
|
||||
}
|
||||
};
|
||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = Popup.IsMouseOver;
|
||||
}));
|
||||
MuteButton.Click += MuteButton_Click;
|
||||
MutedIcon = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_Muted.xaml") };
|
||||
NoDeviceIcon = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_NoDevice.xaml") };
|
||||
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = IsMouseOver;
|
||||
}));
|
||||
Volume.ValueChanged += Volume_ValueChanged;
|
||||
|
||||
Popup.Opened += (o, args) =>
|
||||
{
|
||||
Background = Brushes.LightGray;
|
||||
Button.Background = Brushes.LightGray;
|
||||
Volume.Focus();
|
||||
};
|
||||
|
||||
Popup.Closed += (o, args) =>
|
||||
{
|
||||
Background = originalBrush;
|
||||
Button.Background = originalBrush;
|
||||
lastOpenedBySpacePress = false;
|
||||
};
|
||||
|
||||
if (audio.HasOutputDevice)
|
||||
{
|
||||
AudioDeviceName.Text = audio.DeviceFullName;
|
||||
Button.IsEnabled = true;
|
||||
UpdateVolume(audio.OutputVolume, audio.OutputMuted);
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioDeviceName.Text = text.Get(TextKey.SystemControl_AudioDeviceNotFound);
|
||||
Button.IsEnabled = false;
|
||||
Button.ToolTip = text.Get(TextKey.SystemControl_AudioDeviceNotFound);
|
||||
ButtonIcon.Content = IconResourceLoader.Load(NoDeviceIcon);
|
||||
}
|
||||
}
|
||||
|
||||
private void Audio_VolumeChanged(double volume, bool muted)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => UpdateVolume(volume, muted));
|
||||
}
|
||||
|
||||
private void MuteButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (muted)
|
||||
{
|
||||
audio.Unmute();
|
||||
}
|
||||
else
|
||||
{
|
||||
audio.Mute();
|
||||
}
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void Volume_DragStarted(object sender, DragStartedEventArgs e)
|
||||
{
|
||||
Volume.ValueChanged -= Volume_ValueChanged;
|
||||
}
|
||||
|
||||
private void Volume_DragCompleted(object sender, DragCompletedEventArgs e)
|
||||
{
|
||||
audio.SetVolume(Volume.Value / 100);
|
||||
Volume.ValueChanged += Volume_ValueChanged;
|
||||
}
|
||||
|
||||
private void Volume_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
audio.SetVolume(Volume.Value / 100);
|
||||
}
|
||||
|
||||
private void UpdateVolume(double volume, bool muted)
|
||||
{
|
||||
var info = BuildInfoText(volume, muted);
|
||||
|
||||
this.muted = muted;
|
||||
|
||||
Button.ToolTip = info;
|
||||
Volume.ValueChanged -= Volume_ValueChanged;
|
||||
Volume.Value = Math.Round(volume * 100);
|
||||
Volume.ValueChanged += Volume_ValueChanged;
|
||||
|
||||
AutomationProperties.SetName(Button, info);
|
||||
|
||||
if (muted)
|
||||
{
|
||||
var tooltip = text.Get(TextKey.SystemControl_AudioDeviceUnmuteTooltip);
|
||||
|
||||
MuteButton.ToolTip = tooltip;
|
||||
PopupIcon.Content = IconResourceLoader.Load(MutedIcon);
|
||||
ButtonIcon.Content = IconResourceLoader.Load(MutedIcon);
|
||||
|
||||
AutomationProperties.SetName(MuteButton, tooltip);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tooltip = text.Get(TextKey.SystemControl_AudioDeviceMuteTooltip);
|
||||
|
||||
MuteButton.ToolTip = tooltip;
|
||||
PopupIcon.Content = LoadIcon(volume);
|
||||
ButtonIcon.Content = LoadIcon(volume);
|
||||
|
||||
AutomationProperties.SetName(MuteButton, tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildInfoText(double volume, bool muted)
|
||||
{
|
||||
var info = text.Get(muted ? TextKey.SystemControl_AudioDeviceInfoMuted : TextKey.SystemControl_AudioDeviceInfo);
|
||||
|
||||
info = info.Replace("%%NAME%%", audio.DeviceShortName);
|
||||
info = info.Replace("%%VOLUME%%", Convert.ToString(Math.Round(volume * 100)));
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private UIElement LoadIcon(double volume)
|
||||
{
|
||||
var icon = volume > 0.66 ? "100" : (volume > 0.33 ? "66" : "33");
|
||||
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Audio_{icon}.xaml");
|
||||
var resource = new XamlIconResource { Uri = uri };
|
||||
|
||||
return IconResourceLoader.Load(resource);
|
||||
}
|
||||
|
||||
private void Popup_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == System.Windows.Input.Key.Escape)
|
||||
{
|
||||
Popup.IsOpen = false;
|
||||
Button.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void Volume_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == System.Windows.Input.Key.Enter)
|
||||
{
|
||||
Popup.IsOpen = false;
|
||||
Button.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.Clock" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="300">
|
||||
<Grid Background="Transparent" Margin="5,0" ToolTip="{Binding Path=ToolTip}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="1*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Name="TimeTextBlock" Grid.Row="0" Text="{Binding Path=Time}" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Bottom" Focusable="True" AutomationProperties.Name="{Binding Path=Time}" />
|
||||
<TextBlock x:Name="DateTextBlock" Grid.Row="1" Text="{Binding Path=Date}" HorizontalAlignment="Center" VerticalAlignment="Top" Focusable="True" AutomationProperties.Name="{Binding Path=Date}" />
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.UserInterface.Desktop.ViewModels;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class Clock : UserControl
|
||||
{
|
||||
private DateTimeViewModel model;
|
||||
|
||||
public Clock()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeControl();
|
||||
}
|
||||
|
||||
private void InitializeControl()
|
||||
{
|
||||
model = new DateTimeViewModel(false);
|
||||
DataContext = model;
|
||||
TimeTextBlock.DataContext = model;
|
||||
DateTextBlock.DataContext = model;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.KeyboardLayoutButton" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="250">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Button x:Name="Button" Background="Transparent" Height="40" Padding="10,0" Template="{StaticResource TaskbarButton}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="IsCurrentTextBlock" Grid.Column="0" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden">•</TextBlock>
|
||||
<TextBlock x:Name="CultureCodeTextBlock" Grid.Column="1" FontWeight="Bold" HorizontalAlignment="Left" Margin="10,0,5,0" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Column="2" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="CultureNameTextBlock" HorizontalAlignment="Left" Margin="5,0,10,0" TextDecorations="Underline" VerticalAlignment="Center" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<fa:ImageAwesome Foreground="Gray" Height="10" Icon="KeyboardOutline" Margin="5,0" />
|
||||
<TextBlock x:Name="LayoutNameTextBlock" Foreground="Gray" HorizontalAlignment="Left" Margin="0,0,10,0" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Button>
|
||||
</UserControl>
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class KeyboardLayoutButton : UserControl
|
||||
{
|
||||
private readonly IKeyboardLayout layout;
|
||||
|
||||
internal bool IsCurrent
|
||||
{
|
||||
set { IsCurrentTextBlock.Visibility = value ? Visibility.Visible : Visibility.Hidden; }
|
||||
}
|
||||
|
||||
internal Guid LayoutId
|
||||
{
|
||||
get { return layout.Id; }
|
||||
}
|
||||
|
||||
internal event EventHandler LayoutSelected;
|
||||
|
||||
internal KeyboardLayoutButton(IKeyboardLayout layout)
|
||||
{
|
||||
this.layout = layout;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeLayoutButton();
|
||||
}
|
||||
|
||||
private void InitializeLayoutButton()
|
||||
{
|
||||
Button.Click += (o, args) => LayoutSelected?.Invoke(this, EventArgs.Empty);
|
||||
CultureCodeTextBlock.Text = layout.CultureCode;
|
||||
CultureNameTextBlock.Text = layout.CultureName;
|
||||
LayoutNameTextBlock.Text = layout.LayoutName;
|
||||
|
||||
AutomationProperties.SetName(Button, layout.CultureName);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.KeyboardLayoutControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}" KeyUp="Popup_KeyUp">
|
||||
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0">
|
||||
<ScrollViewer x:Name="LayoutsScrollViewer" MaxHeight="250" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
|
||||
<StackPanel x:Name="LayoutsStackPanel" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="Button" Background="Transparent" Template="{StaticResource TaskbarButton}" Padding="5" Width="40">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="LayoutCultureCode" FontWeight="Bold" Margin="2" TextAlignment="Center" VerticalAlignment="Center" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class KeyboardLayoutControl : UserControl, ISystemControl
|
||||
{
|
||||
private readonly IKeyboard keyboard;
|
||||
private readonly IText text;
|
||||
|
||||
internal KeyboardLayoutControl(IKeyboard keyboard, IText text)
|
||||
{
|
||||
this.keyboard = keyboard;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeKeyboardLayoutControl();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Dispatcher.Invoke(() => Popup.IsOpen = false);
|
||||
}
|
||||
|
||||
private void InitializeKeyboardLayoutControl()
|
||||
{
|
||||
var originalBrush = Button.Background;
|
||||
|
||||
InitializeLayouts();
|
||||
|
||||
keyboard.LayoutChanged += Keyboard_LayoutChanged;
|
||||
Button.Click += (o, args) =>
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
Task.Delay(200).ContinueWith(_ => this.Dispatcher.BeginInvoke((System.Action) (() =>
|
||||
{
|
||||
((LayoutsStackPanel.Children[0] as ContentControl).Content as UIElement).Focus();
|
||||
})));
|
||||
};
|
||||
var lastOpenedBySpacePress = false;
|
||||
Button.PreviewKeyDown += (o, args) =>
|
||||
{
|
||||
// For some reason, the popup immediately closes again if opened by a Space Bar key event - as a mitigation,
|
||||
// we record the space bar event and leave the popup open for at least 3 seconds.
|
||||
if (args.Key == System.Windows.Input.Key.Space)
|
||||
{
|
||||
lastOpenedBySpacePress = true;
|
||||
}
|
||||
};
|
||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = Popup.IsMouseOver;
|
||||
}));
|
||||
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = IsMouseOver;
|
||||
}));
|
||||
|
||||
Popup.Opened += (o, args) =>
|
||||
{
|
||||
Background = Brushes.LightGray;
|
||||
Button.Background = Brushes.LightGray;
|
||||
};
|
||||
|
||||
Popup.Closed += (o, args) =>
|
||||
{
|
||||
Background = originalBrush;
|
||||
Button.Background = originalBrush;
|
||||
lastOpenedBySpacePress = false;
|
||||
};
|
||||
}
|
||||
|
||||
private void Keyboard_LayoutChanged(IKeyboardLayout layout)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => SetCurrent(layout));
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeLayouts()
|
||||
{
|
||||
foreach (var layout in keyboard.GetLayouts())
|
||||
{
|
||||
var button = new KeyboardLayoutButton(layout);
|
||||
|
||||
button.LayoutSelected += (o, args) => ActivateLayout(layout);
|
||||
LayoutsStackPanel.Children.Add(button);
|
||||
|
||||
if (layout.IsCurrent)
|
||||
{
|
||||
SetCurrent(layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateLayout(IKeyboardLayout layout)
|
||||
{
|
||||
Popup.IsOpen = false;
|
||||
keyboard.ActivateLayout(layout.Id);
|
||||
}
|
||||
|
||||
private void SetCurrent(IKeyboardLayout layout)
|
||||
{
|
||||
var name = layout.CultureName?.Length > 3 ? String.Join(string.Empty, layout.CultureName.Split(' ').Where(s => Char.IsLetter(s.First())).Select(s => s.First())) : layout.CultureName;
|
||||
var tooltip = text.Get(TextKey.SystemControl_KeyboardLayoutTooltip).Replace("%%LAYOUT%%", layout.CultureName);
|
||||
|
||||
foreach (var child in LayoutsStackPanel.Children)
|
||||
{
|
||||
if (child is KeyboardLayoutButton layoutButton)
|
||||
{
|
||||
layoutButton.IsCurrent = layout.Id == layoutButton.LayoutId;
|
||||
}
|
||||
}
|
||||
|
||||
LayoutCultureCode.Text = layout.CultureCode;
|
||||
Button.ToolTip = tooltip;
|
||||
|
||||
AutomationProperties.SetName(Button, tooltip);
|
||||
}
|
||||
|
||||
private void Popup_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == System.Windows.Input.Key.Enter || e.Key == System.Windows.Input.Key.Escape)
|
||||
{
|
||||
Popup.IsOpen = false;
|
||||
Button.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.NetworkButton" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="250">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Button x:Name="Button" Background="Transparent" Height="40" Padding="10,0" Template="{StaticResource TaskbarButton}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="IsCurrentTextBlock" Grid.Column="0" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden">•</TextBlock>
|
||||
<TextBlock x:Name="SignalStrengthTextBlock" Grid.Column="1" Foreground="Gray" HorizontalAlignment="Left" Margin="10,0,5,0" VerticalAlignment="Center" />
|
||||
<TextBlock x:Name="NetworkNameTextBlock" Grid.Column="2" FontWeight="Bold" HorizontalAlignment="Left" Margin="5,0,10,0" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Windows;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Network;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class NetworkButton : UserControl
|
||||
{
|
||||
private readonly IWirelessNetwork network;
|
||||
|
||||
internal event EventHandler NetworkSelected;
|
||||
|
||||
internal NetworkButton(IWirelessNetwork network)
|
||||
{
|
||||
this.network = network;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeNetworkButton();
|
||||
}
|
||||
|
||||
private void InitializeNetworkButton()
|
||||
{
|
||||
Button.Click += (o, args) => NetworkSelected?.Invoke(this, EventArgs.Empty);
|
||||
IsCurrentTextBlock.Visibility = network.Status == ConnectionStatus.Connected ? Visibility.Visible : Visibility.Hidden;
|
||||
NetworkNameTextBlock.Text = network.Name;
|
||||
SignalStrengthTextBlock.Text = $"{network.SignalStrength}%";
|
||||
}
|
||||
|
||||
public void SetFocus()
|
||||
{
|
||||
Button.Focus();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.NetworkControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0">
|
||||
<ScrollViewer MaxHeight="250" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
|
||||
<StackPanel x:Name="WirelessNetworksStackPanel" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="Button" Background="Transparent" Padding="5" Template="{StaticResource TaskbarButton}" ToolTipService.ShowOnDisabled="True" Width="40">
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Viewbox x:Name="WirelessIcon" Stretch="Uniform" Width="Auto" Visibility="Collapsed" />
|
||||
<fa:ImageAwesome x:Name="WiredIcon" Icon="Tv" Margin="0,2,4,4" Visibility="Collapsed" />
|
||||
<Border Background="{StaticResource BackgroundBrush}" CornerRadius="6" Height="12" HorizontalAlignment="Right" Margin="0,0,-1,1" Panel.ZIndex="10" VerticalAlignment="Bottom">
|
||||
<fa:ImageAwesome x:Name="NetworkStatusIcon" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using FontAwesome.WPF;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Network;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class NetworkControl : UserControl, ISystemControl
|
||||
{
|
||||
private readonly INetworkAdapter adapter;
|
||||
private readonly IText text;
|
||||
|
||||
internal NetworkControl(INetworkAdapter adapter, IText text)
|
||||
{
|
||||
this.adapter = adapter;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeWirelessNetworkControl();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Popup.IsOpen = false);
|
||||
}
|
||||
|
||||
private void InitializeWirelessNetworkControl()
|
||||
{
|
||||
var lastOpenedBySpacePress = false;
|
||||
var originalBrush = Button.Background;
|
||||
|
||||
adapter.Changed += () => Dispatcher.InvokeAsync(Update);
|
||||
Button.PreviewKeyDown += (o, args) =>
|
||||
{
|
||||
// For some reason, the popup immediately closes again if opened by a Space Bar key event - as a mitigation,
|
||||
// we record the space bar event and leave the popup open for at least 3 seconds
|
||||
if (args.Key == System.Windows.Input.Key.Space)
|
||||
{
|
||||
lastOpenedBySpacePress = true;
|
||||
}
|
||||
};
|
||||
Button.Click += (o, args) => Popup.IsOpen = !Popup.IsOpen;
|
||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = Popup.IsMouseOver;
|
||||
}));
|
||||
Popup.Closed += (o, args) =>
|
||||
{
|
||||
adapter.StopWirelessNetworkScanning();
|
||||
Background = originalBrush;
|
||||
Button.Background = originalBrush;
|
||||
lastOpenedBySpacePress = false;
|
||||
};
|
||||
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = IsMouseOver;
|
||||
}));
|
||||
Popup.Opened += (o, args) =>
|
||||
{
|
||||
adapter.StartWirelessNetworkScanning();
|
||||
Background = Brushes.LightGray;
|
||||
Button.Background = Brushes.LightGray;
|
||||
Task.Delay(100).ContinueWith((task) => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (WirelessNetworksStackPanel.Children.Count > 0)
|
||||
{
|
||||
(WirelessNetworksStackPanel.Children[0] as NetworkButton)?.SetFocus();
|
||||
}
|
||||
}));
|
||||
};
|
||||
WirelessIcon.Child = GetWirelessIcon(0);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
switch (adapter.Type)
|
||||
{
|
||||
case ConnectionType.Wired:
|
||||
Button.IsEnabled = false;
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkWiredConnected));
|
||||
WiredIcon.Visibility = Visibility.Visible;
|
||||
WirelessIcon.Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
case ConnectionType.Wireless:
|
||||
Button.IsEnabled = true;
|
||||
WiredIcon.Visibility = Visibility.Collapsed;
|
||||
WirelessIcon.Visibility = Visibility.Visible;
|
||||
break;
|
||||
default:
|
||||
Button.IsEnabled = false;
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkNotAvailable));
|
||||
WiredIcon.Visibility = Visibility.Visible;
|
||||
WirelessIcon.Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (adapter.Status)
|
||||
{
|
||||
case ConnectionStatus.Connected:
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkWiredConnected));
|
||||
NetworkStatusIcon.Rotation = 0;
|
||||
NetworkStatusIcon.Source = ImageAwesome.CreateImageSource(FontAwesomeIcon.Globe, Brushes.Green);
|
||||
NetworkStatusIcon.Spin = false;
|
||||
break;
|
||||
case ConnectionStatus.Connecting:
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnecting));
|
||||
NetworkStatusIcon.Rotation = 0;
|
||||
NetworkStatusIcon.Source = ImageAwesome.CreateImageSource(FontAwesomeIcon.Cog, Brushes.DimGray);
|
||||
NetworkStatusIcon.Spin = true;
|
||||
NetworkStatusIcon.SpinDuration = 2;
|
||||
break;
|
||||
default:
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkDisconnected));
|
||||
NetworkStatusIcon.Source = ImageAwesome.CreateImageSource(FontAwesomeIcon.Ban, Brushes.DarkOrange);
|
||||
NetworkStatusIcon.Spin = false;
|
||||
WirelessIcon.Child = GetWirelessIcon(0);
|
||||
break;
|
||||
}
|
||||
|
||||
WirelessNetworksStackPanel.Children.Clear();
|
||||
|
||||
foreach (var network in adapter.GetWirelessNetworks())
|
||||
{
|
||||
var button = new NetworkButton(network);
|
||||
|
||||
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
|
||||
|
||||
if (network.Status == ConnectionStatus.Connected)
|
||||
{
|
||||
WirelessIcon.Child = GetWirelessIcon(network.SignalStrength);
|
||||
UpdateText(text.Get(TextKey.SystemControl_NetworkWirelessConnected).Replace("%%NAME%%", network.Name));
|
||||
}
|
||||
|
||||
WirelessNetworksStackPanel.Children.Add(button);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateText(string text)
|
||||
{
|
||||
Button.ToolTip = text;
|
||||
Button.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, text);
|
||||
}
|
||||
|
||||
private UIElement GetWirelessIcon(int signalStrength)
|
||||
{
|
||||
var icon = signalStrength > 66 ? "100" : (signalStrength > 33 ? "66" : (signalStrength > 0 ? "33" : "0"));
|
||||
var uri = new Uri($"pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/WiFi_{icon}.xaml");
|
||||
var resource = new XamlIconResource { Uri = uri };
|
||||
|
||||
return IconResourceLoader.Load(resource);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.NotificationButton" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Button x:Name="IconButton" Background="{StaticResource BackgroundBrush}" Click="IconButton_Click" Padding="7.5"
|
||||
Template="{StaticResource TaskbarButton}" ToolTipService.ShowOnDisabled="True" Width="40" />
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.Core.Contracts.Notifications;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class NotificationButton : UserControl, INotificationControl
|
||||
{
|
||||
private readonly INotification notification;
|
||||
|
||||
internal NotificationButton(INotification notification)
|
||||
{
|
||||
this.notification = notification;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeNotification();
|
||||
UpdateNotification();
|
||||
}
|
||||
|
||||
private void IconButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (notification.CanActivate)
|
||||
{
|
||||
notification.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeNotification()
|
||||
{
|
||||
notification.NotificationChanged += () => Dispatcher.Invoke(UpdateNotification);
|
||||
}
|
||||
|
||||
private void UpdateNotification()
|
||||
{
|
||||
IconButton.Content = IconResourceLoader.Load(notification.IconResource);
|
||||
IconButton.IsEnabled = notification.CanActivate;
|
||||
IconButton.ToolTip = notification.Tooltip;
|
||||
|
||||
AutomationProperties.SetName(this, notification.Tooltip);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.PowerSupplyControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40" Focusable="true" IsTabStop="True">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0">
|
||||
<Grid MaxWidth="250" Margin="20,10,20,20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="1" Background="Transparent" BorderBrush="LightGray" Click="Button_Click" Cursor="Hand" FontWeight="Bold"
|
||||
Foreground="Gray" HorizontalAlignment="Center" Margin="0,0,0,5" Width="20">X</Button>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="1" x:Name="PopupText" TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="Button" Background="Transparent" IsEnabled="False" Padding="5" Template="{StaticResource TaskbarButton}" ToolTipService.ShowOnDisabled="True">
|
||||
<Viewbox Stretch="Uniform" Width="Auto">
|
||||
<Canvas Height="40" Width="40">
|
||||
<Viewbox Stretch="Uniform" Width="40" Panel.ZIndex="2">
|
||||
<Canvas Width="1024.000" Height="1024.000">
|
||||
<Canvas.LayoutTransform>
|
||||
<RotateTransform Angle="180" />
|
||||
</Canvas.LayoutTransform>
|
||||
<Canvas>
|
||||
<Path Data=" M 0.000,0.000 L 1024.000,0.000 L 1024.000,1024.000 L 0.000,1024.000 L 0.000,0.000 Z"/>
|
||||
<Path StrokeThickness="35.0" Stroke="#ff000000" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data=" M 112.000,300.000 L 951.000,300.000 C 970.330,300.000 986.000,315.670 986.000,335.000 L 986.000,689.000 C 986.000,708.330 970.330,724.000 951.000,724.000 L 112.000,724.000 C 92.670,724.000 77.000,708.330 77.000,689.000 L 77.000,335.000 C 77.000,315.670 92.670,300.000 112.000,300.000 Z"/>
|
||||
<Path StrokeThickness="1.0" Stroke="#ff000000" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Fill="#ff000000" Data=" M 25.000,462.500 L 50.000,462.500 C 52.760,462.500 55.000,464.740 55.000,467.500 L 55.000,557.500 C 55.000,560.260 52.760,562.500 50.000,562.500 L 25.000,562.500 C 22.240,562.500 20.000,560.260 20.000,557.500 L 20.000,467.500 C 20.000,464.740 22.240,462.500 25.000,462.500 Z"/>
|
||||
</Canvas>
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
<Rectangle x:Name="BatteryCharge" Canvas.Left="2" Canvas.Top="12" Fill="Green" Height="16" Width="35" Panel.ZIndex="1" />
|
||||
<Canvas x:Name="PowerPlug" Panel.ZIndex="3" Canvas.Left="4" Canvas.Top="-3">
|
||||
<Canvas.LayoutTransform>
|
||||
<ScaleTransform ScaleX="2" ScaleY="2" />
|
||||
</Canvas.LayoutTransform>
|
||||
<Path Stroke="Black" StrokeStartLineCap="Round" Fill="Black" Data="M2.5,17.5 V10 Q5,10 5,6 H4 V4 H4 V6 H1 V4 H1 V6 H0 Q0,10 2.5,10" />
|
||||
</Canvas>
|
||||
<TextBlock x:Name="Warning" FontSize="36" FontWeight="ExtraBold" Foreground="Red" Canvas.Left="13" Canvas.Top="-7" Panel.ZIndex="3">!</TextBlock>
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class PowerSupplyControl : UserControl, ISystemControl
|
||||
{
|
||||
private Brush initialBrush;
|
||||
private bool infoShown, warningShown;
|
||||
private double maxWidth;
|
||||
private IPowerSupply powerSupply;
|
||||
private IText text;
|
||||
|
||||
internal PowerSupplyControl(IPowerSupply powerSupply, IText text)
|
||||
{
|
||||
this.powerSupply = powerSupply;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializePowerSupplyControl();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Dispatcher.InvokeAsync(ClosePopup);
|
||||
}
|
||||
|
||||
public void SetInformation(string text)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => PopupText.Text = text);
|
||||
}
|
||||
|
||||
private void InitializePowerSupplyControl()
|
||||
{
|
||||
initialBrush = BatteryCharge.Fill;
|
||||
maxWidth = BatteryCharge.Width;
|
||||
powerSupply.StatusChanged += PowerSupply_StatusChanged;
|
||||
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
UpdateStatus(powerSupply.GetStatus());
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ClosePopup();
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void PowerSupply_StatusChanged(IPowerSupplyStatus status)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => UpdateStatus(status));
|
||||
}
|
||||
|
||||
private void UpdateStatus(IPowerSupplyStatus status)
|
||||
{
|
||||
var percentage = Math.Round(status.BatteryCharge * 100);
|
||||
var tooltip = string.Empty;
|
||||
|
||||
RenderCharge(status.BatteryCharge, status.BatteryChargeStatus);
|
||||
|
||||
if (status.IsOnline)
|
||||
{
|
||||
infoShown = false;
|
||||
warningShown = false;
|
||||
tooltip = text.Get(percentage == 100 ? TextKey.SystemControl_BatteryCharged : TextKey.SystemControl_BatteryCharging);
|
||||
tooltip = tooltip.Replace("%%CHARGE%%", percentage.ToString());
|
||||
|
||||
ClosePopup();
|
||||
}
|
||||
else
|
||||
{
|
||||
tooltip = text.Get(TextKey.SystemControl_BatteryRemainingCharge);
|
||||
tooltip = tooltip.Replace("%%CHARGE%%", percentage.ToString());
|
||||
tooltip = tooltip.Replace("%%HOURS%%", status.BatteryTimeRemaining.Hours.ToString());
|
||||
tooltip = tooltip.Replace("%%MINUTES%%", status.BatteryTimeRemaining.Minutes.ToString());
|
||||
|
||||
HandleBatteryStatus(status.BatteryChargeStatus);
|
||||
}
|
||||
|
||||
Button.ToolTip = tooltip;
|
||||
PowerPlug.Visibility = status.IsOnline ? Visibility.Visible : Visibility.Collapsed;
|
||||
Warning.Visibility = status.BatteryChargeStatus == BatteryChargeStatus.Critical ? Visibility.Visible : Visibility.Collapsed;
|
||||
this.SetValue(System.Windows.Automation.AutomationProperties.NameProperty, tooltip);
|
||||
}
|
||||
|
||||
private void RenderCharge(double charge, BatteryChargeStatus status)
|
||||
{
|
||||
var width = maxWidth * charge;
|
||||
|
||||
BatteryCharge.Width = width > maxWidth ? maxWidth : (width < 0 ? 0 : width);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case BatteryChargeStatus.Critical:
|
||||
BatteryCharge.Fill = Brushes.Red;
|
||||
break;
|
||||
case BatteryChargeStatus.Low:
|
||||
BatteryCharge.Fill = Brushes.Orange;
|
||||
break;
|
||||
default:
|
||||
BatteryCharge.Fill = initialBrush;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleBatteryStatus(BatteryChargeStatus chargeStatus)
|
||||
{
|
||||
if (chargeStatus == BatteryChargeStatus.Low && !infoShown)
|
||||
{
|
||||
ShowPopup(text.Get(TextKey.SystemControl_BatteryChargeLowInfo));
|
||||
infoShown = true;
|
||||
}
|
||||
|
||||
if (chargeStatus == BatteryChargeStatus.Critical && !warningShown)
|
||||
{
|
||||
ShowPopup(text.Get(TextKey.SystemControl_BatteryChargeCriticalWarning));
|
||||
warningShown = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowPopup(string text)
|
||||
{
|
||||
Popup.IsOpen = true;
|
||||
PopupText.Text = text;
|
||||
Background = Brushes.LightGray;
|
||||
}
|
||||
|
||||
private void ClosePopup()
|
||||
{
|
||||
Popup.IsOpen = false;
|
||||
Background = Brushes.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.QuitButton" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls"
|
||||
x:Name="root"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Button x:Name="Button" Click="Button_Click" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch"
|
||||
Template="{StaticResource TaskbarButton}" VerticalAlignment="Stretch"
|
||||
AutomationProperties.Name="{Binding Path=(AutomationProperties.Name), ElementName=root}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell.Events;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
internal partial class QuitButton : UserControl
|
||||
{
|
||||
internal event QuitButtonClickedEventHandler Clicked;
|
||||
|
||||
public QuitButton()
|
||||
{
|
||||
InitializeComponent();
|
||||
LoadIcon();
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Clicked?.Invoke(new CancelEventArgs());
|
||||
}
|
||||
|
||||
private void LoadIcon()
|
||||
{
|
||||
var uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ShutDown.xaml");
|
||||
var resource = new XamlIconResource { Uri = uri };
|
||||
|
||||
Button.Content = IconResourceLoader.Load(resource);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar.RaiseHandControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar"
|
||||
mc:Ignorable="d" d:DesignHeight="40" d:DesignWidth="40">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Buttons.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
<ResourceDictionary Source="../../Templates/ScrollViewers.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Popup x:Name="Popup" IsOpen="False" Placement="Custom" PlacementTarget="{Binding ElementName=Button}">
|
||||
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="1,1,1,0" >
|
||||
<StackPanel>
|
||||
<TextBox Name="Message" AcceptsReturn="True" Height="150" IsReadOnly="False" Margin="5,5,5,0" Width="350" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" />
|
||||
<Grid>
|
||||
<Button Name="HandButton" Background="Transparent" Height="30" Margin="5" Padding="5" Template="{StaticResource TaskbarButton}" Width="150">
|
||||
<Viewbox Stretch="Uniform">
|
||||
<TextBlock x:Name="HandButtonText" FontWeight="Bold" TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
<Button x:Name="NotificationButton" Background="Transparent" Template="{StaticResource TaskbarButton}" Padding="5" Width="40">
|
||||
<ContentControl Name="Icon" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Proctoring.Contracts;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
|
||||
{
|
||||
public partial class RaiseHandControl : UserControl, INotificationControl
|
||||
{
|
||||
private readonly IProctoringController controller;
|
||||
private readonly ProctoringSettings settings;
|
||||
private readonly IText text;
|
||||
|
||||
private IconResource LoweredIcon;
|
||||
private IconResource RaisedIcon;
|
||||
|
||||
public RaiseHandControl(IProctoringController controller, ProctoringSettings settings, IText text)
|
||||
{
|
||||
this.controller = controller;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeRaiseHandControl();
|
||||
}
|
||||
|
||||
private void InitializeRaiseHandControl()
|
||||
{
|
||||
var originalBrush = NotificationButton.Background;
|
||||
|
||||
controller.HandLowered += () => Dispatcher.Invoke(ShowLowered);
|
||||
controller.HandRaised += () => Dispatcher.Invoke(ShowRaised);
|
||||
|
||||
HandButton.Click += RaiseHandButton_Click;
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
|
||||
LoweredIcon = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Hand_Lowered.xaml") };
|
||||
RaisedIcon = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Hand_Raised.xaml") };
|
||||
Icon.Content = IconResourceLoader.Load(LoweredIcon);
|
||||
|
||||
var lastOpenedBySpacePress = false;
|
||||
NotificationButton.PreviewKeyDown += (o, args) =>
|
||||
{
|
||||
if (args.Key == System.Windows.Input.Key.Space) // for some reason, the popup immediately closes again if opened by a Space Bar key event - as a mitigation, we record the space bar event and leave the popup open for at least 3 seconds
|
||||
{
|
||||
lastOpenedBySpacePress = true;
|
||||
}
|
||||
};
|
||||
NotificationButton.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = Popup.IsMouseOver;
|
||||
}));
|
||||
NotificationButton.PreviewMouseLeftButtonUp += NotificationButton_PreviewMouseLeftButtonUp;
|
||||
NotificationButton.PreviewMouseRightButtonUp += NotificationButton_PreviewMouseRightButtonUp;
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
|
||||
Popup.CustomPopupPlacementCallback = new CustomPopupPlacementCallback(Popup_PlacementCallback);
|
||||
Popup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Popup.IsOpen && lastOpenedBySpacePress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Popup.IsOpen = IsMouseOver;
|
||||
}));
|
||||
Popup.Opened += (o, args) =>
|
||||
{
|
||||
Background = Brushes.LightGray;
|
||||
NotificationButton.Background = Brushes.LightGray;
|
||||
};
|
||||
Popup.Closed += (o, args) =>
|
||||
{
|
||||
Background = originalBrush;
|
||||
NotificationButton.Background = originalBrush;
|
||||
lastOpenedBySpacePress = false;
|
||||
};
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (settings.ForceRaiseHandMessage || Popup.IsOpen)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
}
|
||||
|
||||
private void NotificationButton_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Popup.IsOpen = !Popup.IsOpen;
|
||||
}
|
||||
|
||||
private CustomPopupPlacement[] Popup_PlacementCallback(Size popupSize, Size targetSize, Point offset)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new CustomPopupPlacement(new Point(targetSize.Width / 2 - popupSize.Width / 2, -popupSize.Height), PopupPrimaryAxis.None)
|
||||
};
|
||||
}
|
||||
|
||||
private void RaiseHandButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ToggleHand();
|
||||
}
|
||||
|
||||
private void ToggleHand()
|
||||
{
|
||||
if (controller.IsHandRaised)
|
||||
{
|
||||
controller.LowerHand();
|
||||
}
|
||||
else
|
||||
{
|
||||
controller.RaiseHand(Message.Text);
|
||||
Message.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowLowered()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringRaiseHand);
|
||||
Icon.Content = IconResourceLoader.Load(LoweredIcon);
|
||||
Message.IsEnabled = true;
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandLowered);
|
||||
Popup.IsOpen = false;
|
||||
}
|
||||
|
||||
private void ShowRaised()
|
||||
{
|
||||
HandButtonText.Text = text.Get(TextKey.Notification_ProctoringLowerHand);
|
||||
Icon.Content = IconResourceLoader.Load(RaisedIcon);
|
||||
Message.IsEnabled = false;
|
||||
NotificationButton.ToolTip = text.Get(TextKey.Notification_ProctoringHandRaised);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.Taskview.WindowControl" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Controls">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="5" Height="175" Width="250">
|
||||
<Border Name="Indicator" Background="{StaticResource BackgroundTransparentEmphasisBrush}" BorderThickness="0" Visibility="Hidden" />
|
||||
<Grid Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl Grid.Row="0" Grid.Column="0" Name="Icon" Margin="0,0,5,0" Height="16" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Name="Title" Foreground="{StaticResource PrimaryTextBrush}" VerticalAlignment="Center" TextTrimming="CharacterEllipsis" />
|
||||
<ContentControl Grid.Row="1" Grid.ColumnSpan="2" Name="Placeholder" Margin="0,5,0,0" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskview
|
||||
{
|
||||
internal partial class WindowControl : UserControl
|
||||
{
|
||||
private Windows.Taskview taskview;
|
||||
private IntPtr thumbnail;
|
||||
private IApplicationWindow window;
|
||||
|
||||
internal WindowControl(IApplicationWindow window, Windows.Taskview taskview)
|
||||
{
|
||||
this.window = window;
|
||||
this.taskview = taskview;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeControl();
|
||||
}
|
||||
|
||||
internal void Activate()
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
internal void Deselect()
|
||||
{
|
||||
Indicator.Visibility = Visibility.Hidden;
|
||||
Title.FontWeight = FontWeights.Normal;
|
||||
}
|
||||
|
||||
internal void Destroy()
|
||||
{
|
||||
if (thumbnail != IntPtr.Zero)
|
||||
{
|
||||
Thumbnail.DwmUnregisterThumbnail(thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Select()
|
||||
{
|
||||
Indicator.Visibility = Visibility.Visible;
|
||||
Title.FontWeight = FontWeights.SemiBold;
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
if (!IsLoaded || !IsVisible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (thumbnail == IntPtr.Zero && taskview.Handle != IntPtr.Zero && window.Handle != IntPtr.Zero)
|
||||
{
|
||||
Thumbnail.DwmRegisterThumbnail(taskview.Handle, window.Handle, out thumbnail);
|
||||
}
|
||||
|
||||
if (thumbnail != IntPtr.Zero)
|
||||
{
|
||||
Thumbnail.DwmQueryThumbnailSourceSize(thumbnail, out var size);
|
||||
|
||||
var destination = CalculatePhysicalDestination(size);
|
||||
var properties = new Thumbnail.Properties
|
||||
{
|
||||
Destination = destination,
|
||||
Flags = Thumbnail.DWM_TNP_RECTDESTINATION | Thumbnail.DWM_TNP_VISIBLE,
|
||||
Visible = true
|
||||
};
|
||||
|
||||
Thumbnail.DwmUpdateThumbnailProperties(thumbnail, ref properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void TaskViewWindowControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.NewValue as bool? == true)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_TitleChanged(string title)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Title.Text = title);
|
||||
}
|
||||
|
||||
private void Window_IconChanged(IconResource icon)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(window.Icon));
|
||||
}
|
||||
|
||||
private Thumbnail.Rectangle CalculatePhysicalDestination(Thumbnail.Size size)
|
||||
{
|
||||
var controlToTaskview = TransformToVisual(taskview);
|
||||
var placeholderToControl = Placeholder.TransformToVisual(this);
|
||||
var placeholderLeft = placeholderToControl.Transform(new Point(0, 0)).X;
|
||||
var placeholderTop = placeholderToControl.Transform(new Point(0, 0)).Y;
|
||||
var placeholderRight = placeholderToControl.Transform(new Point(Placeholder.ActualWidth, 0)).X;
|
||||
var placeholderBottom = placeholderToControl.Transform(new Point(0, Placeholder.ActualHeight)).Y;
|
||||
|
||||
var physicalBounds = new Thumbnail.Rectangle
|
||||
{
|
||||
Left = (int) Math.Round(this.TransformToPhysical(controlToTaskview.Transform(new Point(placeholderLeft, 0)).X, 0).X),
|
||||
Top = (int) Math.Round(this.TransformToPhysical(0, controlToTaskview.Transform(new Point(0, placeholderTop)).Y).Y),
|
||||
Right = (int) Math.Round(this.TransformToPhysical(controlToTaskview.Transform(new Point(placeholderRight, 0)).X, 0).X),
|
||||
Bottom = (int) Math.Round(this.TransformToPhysical(0, controlToTaskview.Transform(new Point(0, placeholderBottom)).Y).Y)
|
||||
};
|
||||
|
||||
var scaleFactor = default(double);
|
||||
var thumbnailHeight = default(double);
|
||||
var thumbnailWidth = default(double);
|
||||
var maxWidth = (double) physicalBounds.Right - physicalBounds.Left;
|
||||
var maxHeight = (double) physicalBounds.Bottom - physicalBounds.Top;
|
||||
var placeholderRatio = maxWidth / maxHeight;
|
||||
var windowRatio = (double) size.X / size.Y;
|
||||
|
||||
if (windowRatio < placeholderRatio)
|
||||
{
|
||||
thumbnailHeight = maxHeight;
|
||||
scaleFactor = thumbnailHeight / size.Y;
|
||||
thumbnailWidth = size.X * scaleFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
thumbnailWidth = maxWidth;
|
||||
scaleFactor = thumbnailWidth / size.X;
|
||||
thumbnailHeight = size.Y * scaleFactor;
|
||||
}
|
||||
|
||||
var widthDifference = maxWidth - thumbnailWidth;
|
||||
var heightDifference = maxHeight - thumbnailHeight;
|
||||
|
||||
return new Thumbnail.Rectangle
|
||||
{
|
||||
Left = (int) Math.Round(physicalBounds.Left + (widthDifference / 2)),
|
||||
Top = (int) Math.Round(physicalBounds.Top + (heightDifference / 2)),
|
||||
Right = (int) Math.Round(physicalBounds.Right - (widthDifference / 2)),
|
||||
Bottom = (int) Math.Round(physicalBounds.Bottom - (heightDifference / 2))
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeControl()
|
||||
{
|
||||
Icon.Content = IconResourceLoader.Load(window.Icon);
|
||||
IsVisibleChanged += TaskViewWindowControl_IsVisibleChanged;
|
||||
Loaded += (o, args) => Update();
|
||||
Title.Text = window.Title;
|
||||
window.IconChanged += Window_IconChanged;
|
||||
window.TitleChanged += Window_TitleChanged;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user