// -------------------------------------------------------------------------------------------------------------------- // // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. // // // Interaction logic for TimeSpanBox.xaml // // -------------------------------------------------------------------------------------------------------------------- namespace HandBrakeWPF.Controls { using System; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; /// /// Interaction logic for TimeSpanBox.xaml /// public partial class TimeSpanBox { #region Constants and Fields /// /// The maximum property name. /// public const string MaximumPropertyName = "Maximum"; /// /// The minimum property name. /// public const string MinimumPropertyName = "Minimum"; /// /// The modulus property name. /// public const string ModulusPropertyName = "Modulus"; /// /// The allow empty property. /// public static readonly DependencyProperty AllowEmptyProperty = DependencyProperty.Register( "AllowEmpty", typeof(bool), typeof(TimeSpanBox), new PropertyMetadata(true, OnAllowEmptyChanged)); /// /// The maximum property. /// public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register( MaximumPropertyName, typeof(int), typeof(TimeSpanBox), new UIPropertyMetadata(int.MaxValue)); /// /// The minimum property. /// public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register( MinimumPropertyName, typeof(int), typeof(TimeSpanBox), new UIPropertyMetadata(int.MinValue)); /// /// The modulus property. /// public static readonly DependencyProperty ModulusProperty = DependencyProperty.Register( ModulusPropertyName, typeof(int), typeof(TimeSpanBox), new UIPropertyMetadata(0)); /// /// The number property. /// public static readonly DependencyProperty NumberProperty = DependencyProperty.Register( "Number", typeof(long), typeof(TimeSpanBox), new PropertyMetadata(OnNumberChanged)); /// /// The show time span property. /// public static readonly DependencyProperty ShowTimeSpanProperty = DependencyProperty.Register( "ShowTimeSpan", typeof(bool), typeof(TimeSpanBox), new PropertyMetadata(OnShowTimeSpanChanged)); /// /// The select all threshold. /// private static readonly TimeSpan SelectAllThreshold = TimeSpan.FromMilliseconds(500); /// /// The has focus. /// private bool hasFocus; /// /// The last focus mouse down. /// private DateTime lastFocusMouseDown; /// /// The none caption. /// private string noneCaption; /// /// The refire control. /// private RefireControl refireControl; /// /// The suppress refresh. /// private bool suppressRefresh; #endregion #region Constructors and Destructors /// /// Initializes a new instance of the class. /// public TimeSpanBox() { this.noneCaption = "(none)"; this.UpdateBindingOnTextChange = true; this.ShowIncrementButtons = true; this.SelectAllOnClick = true; this.InitializeComponent(); this.RefreshNumberBox(); } #endregion #region Properties /// /// Gets or sets a value indicating whether show time span. /// public bool ShowTimeSpan { get { return (bool)this.GetValue(ShowTimeSpanProperty); } set { this.SetValue(ShowTimeSpanProperty, value); } } /// /// Gets or sets a value indicating whether allow empty. /// public bool AllowEmpty { get { return (bool)this.GetValue(AllowEmptyProperty); } set { this.SetValue(AllowEmptyProperty, value); } } /// /// Gets or sets the maximum. /// public int Maximum { get { return (int)this.GetValue(MaximumProperty); } set { this.SetValue(MaximumProperty, value); } } /// /// Gets or sets the minimum. /// public int Minimum { get { return (int)this.GetValue(MinimumProperty); } set { this.SetValue(MinimumProperty, value); } } /// /// Gets or sets the modulus. /// public int Modulus { get { return (int)this.GetValue(ModulusProperty); } set { this.SetValue(ModulusProperty, value); } } /// /// Gets or sets the none caption. /// public string NoneCaption { get { return this.noneCaption; } set { this.noneCaption = value; this.RefreshNumberBox(); } } /// /// Gets or sets the number. /// public long Number { get { return (long)this.GetValue(NumberProperty); } set { this.SetValue(NumberProperty, value); } } /// /// Gets or sets a value indicating whether select all on click. /// public bool SelectAllOnClick { get; set; } /// /// Gets or sets a value indicating whether show increment buttons. /// public bool ShowIncrementButtons { get; set; } /// /// Gets or sets a value indicating whether update binding on text change. /// public bool UpdateBindingOnTextChange { get; set; } /// /// Gets the increment. /// private int Increment { get { return this.Modulus > 0 ? this.Modulus : 1; } } #endregion #region Methods /// /// The on allow empty changed. /// /// /// The dependency object. /// /// /// The event args. /// private static void OnAllowEmptyChanged( DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { var numBox = dependencyObject as TimeSpanBox; if (numBox != null) { numBox.RefreshNumberBox(); } } /// /// The on number changed. /// /// /// The dependency object. /// /// /// The event args. /// private static void OnNumberChanged( DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { if (eventArgs.NewValue != eventArgs.OldValue) { var numBox = dependencyObject as TimeSpanBox; if (!numBox.suppressRefresh) { numBox.RefreshNumberBox(); } } } /// /// The on show timespan number changed. /// /// /// The dependency object. /// /// /// The event args. /// private static void OnShowTimeSpanChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { if (eventArgs.NewValue != eventArgs.OldValue) { var numBox = dependencyObject as TimeSpanBox; if (!numBox.suppressRefresh) { numBox.RefreshNumberBox(); } } } /// /// The decrement number. /// private void DecrementNumber() { long newNumber; if (this.AllowEmpty && this.Number == 0) { newNumber = Math.Min(this.Maximum, -this.Increment); } else { newNumber = this.Number - this.Increment; } if (newNumber < this.Minimum) { newNumber = this.Minimum; } if (newNumber != this.Number) { this.Number = newNumber; } } /// /// The down button mouse left button down. /// /// /// The sender. /// /// /// The e. /// private void DownButtonMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { this.refireControl = new RefireControl(this.DecrementNumber); this.refireControl.Begin(); } /// /// The down button mouse left button up. /// /// /// The sender. /// /// /// The e. /// private void DownButtonMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { this.refireControl.Stop(); } /// /// The get nearest value. /// /// /// The number. /// /// /// The modulus. /// /// /// The . /// private int GetNearestValue(int number, int modulus) { int remainder = number % modulus; if (remainder == 0) { return number; } return remainder >= (modulus / 2) ? number + (modulus - remainder) : number - remainder; } /// /// The increment number. /// private void IncrementNumber() { long newNumber; if (this.AllowEmpty && this.Number == 0) { newNumber = Math.Max(this.Minimum, this.Increment); } else { newNumber = this.Number + this.Increment; } if (newNumber > this.Maximum) { newNumber = this.Maximum; } if (newNumber != this.Number) { this.Number = newNumber; } } /// /// The number box got focus. /// /// /// The sender. /// /// /// The e. /// private void NumberBoxGotFocus(object sender, RoutedEventArgs e) { this.hasFocus = true; if (this.AllowEmpty) { if (this.Number == 0) { this.numberBox.Text = string.Empty; } this.numberBox.Foreground = new SolidColorBrush(Colors.Black); } } /// /// The number box lost focus. /// /// /// The sender. /// /// /// The e. /// private void NumberBoxLostFocus(object sender, RoutedEventArgs e) { this.hasFocus = false; if (this.AllowEmpty && this.numberBox.Text == string.Empty) { this.Number = 0; this.RefreshNumberBox(); return; } this.UpdateNumberBindingFromBox(); this.RefreshNumberBox(); } /// /// The number box preview key down. /// /// /// The sender. /// /// /// The e. /// private void NumberBoxPreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Space) { e.Handled = true; } } /// /// The number box preview mouse down. /// /// /// The sender. /// /// /// The e. /// private void NumberBoxPreviewMouseDown(object sender, MouseButtonEventArgs e) { if (!this.hasFocus) { this.lastFocusMouseDown = DateTime.Now; } } /// /// The number box preview mouse up. /// /// /// The sender. /// /// /// The e. /// private void NumberBoxPreviewMouseUp(object sender, MouseButtonEventArgs e) { // If this mouse up is soon enough after an initial click on the box, select all. if (this.SelectAllOnClick && DateTime.Now - this.lastFocusMouseDown < SelectAllThreshold) { this.Dispatcher.BeginInvoke(new Action(() => this.numberBox.SelectAll())); } } /// /// The number box preview text input. /// /// /// The sender. /// /// /// The e. /// private void NumberBoxPreviewTextInput(object sender, TextCompositionEventArgs e) { if (e.Text.Any(c => !char.IsNumber(c) && c != '.' && c != ':' && (this.Minimum >= 0 || c != '-'))) { e.Handled = true; } } /// /// The number box text changed. /// /// /// The sender. /// /// /// The e. /// private void NumberBoxTextChanged(object sender, TextChangedEventArgs e) { if (this.UpdateBindingOnTextChange) { if (this.AllowEmpty && this.numberBox.Text == string.Empty) { this.Number = 0; return; } this.UpdateNumberBindingFromBox(); } this.RefreshNumberBoxColor(); } /// /// The number is valid. /// /// /// The number. /// /// /// The . /// private bool NumberIsValid(double number) { return number >= this.Minimum && number <= this.Maximum; } /// /// The refresh number box. /// private void RefreshNumberBox() { if (this.AllowEmpty && this.Number == 0) { this.numberBox.Text = this.hasFocus ? string.Empty : this.NoneCaption; // this.numberBox.Foreground = new SolidColorBrush(Colors.Gray); } else { if (this.ShowTimeSpan) { this.numberBox.Text = TimeSpan.FromSeconds(this.Number).ToString(); } else { this.numberBox.Text = this.Number.ToString(CultureInfo.InvariantCulture); } // this.numberBox.Foreground = new SolidColorBrush(Colors.Black); } this.RefreshNumberBoxColor(); } /// /// The refresh number box color. /// private void RefreshNumberBoxColor() { this.numberBox.Foreground = this.numberBox.Text == this.NoneCaption ? new SolidColorBrush(Colors.Gray) : new SolidColorBrush(Colors.Black); } /// /// The up button mouse left button down. /// /// /// The sender. /// /// /// The e. /// private void UpButtonMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { this.refireControl = new RefireControl(this.IncrementNumber); this.refireControl.Begin(); } /// /// The up button mouse left button up. /// /// /// The sender. /// /// /// The e. /// private void UpButtonMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { this.refireControl.Stop(); } /// /// The update number binding from box. /// private void UpdateNumberBindingFromBox() { int newNumber; TimeSpan newTimespanNumber; if (int.TryParse(this.numberBox.Text, out newNumber)) { if (this.NumberIsValid(newNumber)) { if (this.Modulus != 0) { newNumber = this.GetNearestValue(newNumber, this.Modulus); } if (newNumber != this.Number) { // While updating the binding we don't need to react to the change. this.suppressRefresh = true; this.Number = newNumber; this.suppressRefresh = false; } } } else if (TimeSpan.TryParse(this.numberBox.Text, out newTimespanNumber)) { if (newTimespanNumber != TimeSpan.Zero) { int seconds = (int)Math.Round(newTimespanNumber.TotalSeconds, 0); if (seconds != this.Number) { // While updating the binding we don't need to react to the change. this.suppressRefresh = true; this.Number = seconds; this.suppressRefresh = false; } } } } /// /// The user control_ loaded. /// /// /// The sender. /// /// /// The e. /// private void UserControl_Loaded(object sender, RoutedEventArgs e) { if (!this.ShowIncrementButtons) { this.incrementButtonsGrid.Visibility = Visibility.Collapsed; } } #endregion } }