2019-08-16 10:08:10 +02:00
/ *
2023-03-08 00:30:20 +01:00
* Copyright ( c ) 2023 ETH Zürich , Educational Development and Technology ( LET )
2019-08-16 10:08:10 +02:00
*
* 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 System.Windows.Threading ;
2021-03-18 23:12:07 +01:00
using SafeExamBrowser.Core.Contracts.Resources.Icons ;
2019-08-30 09:55:26 +02:00
using SafeExamBrowser.I18n.Contracts ;
2019-08-30 17:33:28 +02:00
using SafeExamBrowser.SystemComponents.Contracts.Audio ;
2019-08-30 09:55:26 +02:00
using SafeExamBrowser.UserInterface.Contracts.Shell ;
2019-08-16 10:08:10 +02:00
using SafeExamBrowser.UserInterface.Shared.Utilities ;
2020-03-17 10:37:08 +01:00
namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
2019-08-16 10:08:10 +02:00
{
2020-03-17 10:37:08 +01:00
internal partial class AudioControl : UserControl , ISystemControl
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
private readonly IAudio audio ;
2019-08-16 10:08:10 +02:00
private readonly IText text ;
private bool muted ;
2019-11-06 08:45:37 +01:00
private IconResource MutedIcon ;
private IconResource NoDeviceIcon ;
2019-08-16 10:08:10 +02:00
2020-03-17 10:37:08 +01:00
internal AudioControl ( IAudio audio , IText text )
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
this . audio = audio ;
2019-08-16 10:08:10 +02:00
this . text = text ;
InitializeComponent ( ) ;
InitializeAudioControl ( ) ;
}
2019-08-30 17:33:28 +02:00
public void Close ( )
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
Popup . IsOpen = false ;
2019-08-16 10:08:10 +02:00
}
2019-08-30 17:33:28 +02:00
private void InitializeAudioControl ( )
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
var originalBrush = Grid . Background ;
2019-08-16 10:08:10 +02:00
2019-08-30 17:33:28 +02:00
audio . VolumeChanged + = Audio_VolumeChanged ;
Button . Click + = ( o , args ) = > Popup . IsOpen = ! Popup . IsOpen ;
2023-02-27 13:25:23 +01:00
var lastOpenedBySpacePress = false ;
2023-02-13 13:39:53 +01:00
Button . 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
{
2023-02-27 13:25:23 +01:00
lastOpenedBySpacePress = true ;
2023-02-13 13:39:53 +01:00
}
} ;
Button . MouseLeave + = ( o , args ) = > Task . Delay ( 250 ) . ContinueWith ( _ = > Dispatcher . Invoke ( ( ) = >
{
2023-02-27 13:25:23 +01:00
if ( Popup . IsOpen & & lastOpenedBySpacePress )
2023-02-13 13:39:53 +01:00
{
return ;
}
Popup . IsOpen = Popup . IsMouseOver ;
} ) ) ;
2019-08-30 17:33:28 +02:00
MuteButton . Click + = MuteButton_Click ;
2019-12-03 15:43:48 +01:00
MutedIcon = new XamlIconResource { Uri = new Uri ( "pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Muted.xaml" ) } ;
NoDeviceIcon = new XamlIconResource { Uri = new Uri ( "pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Light_NoDevice.xaml" ) } ;
2023-02-27 13:25:23 +01:00
Popup . MouseLeave + = ( o , args ) = > Task . Delay ( 250 ) . ContinueWith ( _ = > Dispatcher . Invoke ( ( ) = >
{
if ( Popup . IsOpen & & lastOpenedBySpacePress )
{
return ;
}
Popup . IsOpen = IsMouseOver ;
} ) ) ;
2019-08-30 17:33:28 +02:00
Popup . Opened + = ( o , args ) = > Grid . Background = Brushes . Gray ;
2023-02-27 13:25:23 +01:00
Popup . Closed + = ( o , args ) = >
{
Grid . Background = originalBrush ;
lastOpenedBySpacePress = false ;
} ;
2019-08-30 17:33:28 +02:00
Volume . ValueChanged + = Volume_ValueChanged ;
if ( audio . HasOutputDevice )
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
AudioDeviceName . Text = audio . DeviceFullName ;
Button . IsEnabled = true ;
UpdateVolume ( audio . OutputVolume , audio . OutputMuted ) ;
2019-08-16 10:08:10 +02:00
}
2019-08-30 17:33:28 +02:00
else
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
AudioDeviceName . Text = text . Get ( TextKey . SystemControl_AudioDeviceNotFound ) ;
Button . IsEnabled = false ;
Button . ToolTip = text . Get ( TextKey . SystemControl_AudioDeviceNotFound ) ;
ButtonIcon . Content = IconResourceLoader . Load ( NoDeviceIcon ) ;
2019-08-16 10:08:10 +02:00
}
}
2019-08-30 17:33:28 +02:00
private void Audio_VolumeChanged ( double volume , bool muted )
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
Dispatcher . InvokeAsync ( ( ) = > UpdateVolume ( volume , muted ) ) ;
2019-08-16 10:08:10 +02:00
}
2019-08-30 17:33:28 +02:00
private void MuteButton_Click ( object sender , RoutedEventArgs e )
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
if ( muted )
{
audio . Unmute ( ) ;
}
else
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
audio . Mute ( ) ;
}
2019-08-16 10:08:10 +02:00
}
2019-08-30 17:33:28 +02:00
private void Volume_DragStarted ( object sender , DragStartedEventArgs e )
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
Volume . ValueChanged - = Volume_ValueChanged ;
}
2019-08-16 10:08:10 +02:00
2019-08-30 17:33:28 +02:00
private void Volume_DragCompleted ( object sender , DragCompletedEventArgs e )
{
audio . SetVolume ( Volume . Value / 100 ) ;
2019-08-16 10:08:10 +02:00
Volume . ValueChanged + = Volume_ValueChanged ;
}
2019-08-30 17:33:28 +02:00
private void Volume_ValueChanged ( object sender , RoutedPropertyChangedEventArgs < double > e )
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
audio . SetVolume ( Volume . Value / 100 ) ;
2019-08-16 10:08:10 +02:00
}
2019-08-30 17:33:28 +02:00
private void UpdateVolume ( double volume , bool muted )
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
var info = BuildInfoText ( volume , muted ) ;
this . muted = muted ;
Button . ToolTip = info ;
2022-11-08 11:02:56 +01:00
System . Windows . Automation . AutomationProperties . SetName ( Button , info ) ;
2019-08-30 17:33:28 +02:00
Text . Text = info ;
Volume . ValueChanged - = Volume_ValueChanged ;
Volume . Value = Math . Round ( volume * 100 ) ;
2019-08-16 10:08:10 +02:00
Volume . ValueChanged + = Volume_ValueChanged ;
2019-08-30 17:33:28 +02:00
if ( muted )
{
2022-11-08 11:02:56 +01:00
var tooltip = text . Get ( TextKey . SystemControl_AudioDeviceUnmuteTooltip ) ;
MuteButton . ToolTip = tooltip ;
System . Windows . Automation . AutomationProperties . SetName ( MuteButton , tooltip ) ;
2019-08-30 17:33:28 +02:00
ButtonIcon . Content = IconResourceLoader . Load ( MutedIcon ) ;
PopupIcon . Content = IconResourceLoader . Load ( MutedIcon ) ;
}
else
{
2022-11-08 11:02:56 +01:00
var tooltip = text . Get ( TextKey . SystemControl_AudioDeviceMuteTooltip ) ;
MuteButton . ToolTip = tooltip ;
System . Windows . Automation . AutomationProperties . SetName ( MuteButton , tooltip ) ;
2019-08-30 17:33:28 +02:00
ButtonIcon . Content = LoadIcon ( volume ) ;
PopupIcon . Content = LoadIcon ( volume ) ;
}
2019-08-16 10:08:10 +02:00
}
2019-08-30 17:33:28 +02:00
private string BuildInfoText ( double volume , bool muted )
2019-08-16 10:08:10 +02:00
{
2019-08-30 17:33:28 +02:00
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 ;
2019-08-16 10:08:10 +02:00
}
private UIElement LoadIcon ( double volume )
{
var icon = volume > 0.66 ? "100" : ( volume > 0.33 ? "66" : "33" ) ;
2019-08-30 17:33:28 +02:00
var uri = new Uri ( $"pack://application:,,,/SafeExamBrowser.UserInterface.Mobile;component/Images/Audio_Light_{icon}.xaml" ) ;
2019-12-03 15:43:48 +01:00
var resource = new XamlIconResource { Uri = uri } ;
2019-08-16 10:08:10 +02:00
return IconResourceLoader . Load ( resource ) ;
}
}
}