// --------------------------------------------------------------------------------------------------------------------
//
// 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 NumberBox.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 NumberBox.xaml
///
public partial class NumberBox
{
#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(NumberBox), new PropertyMetadata(true, OnAllowEmptyChanged));
///
/// The maximum property.
///
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(
MaximumPropertyName, typeof(double), typeof(NumberBox), new UIPropertyMetadata(double.MaxValue));
///
/// The minimum property.
///
public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register(
MinimumPropertyName, typeof(double), typeof(NumberBox), new UIPropertyMetadata(double.MinValue));
///
/// The modulus property.
///
public static readonly DependencyProperty ModulusProperty = DependencyProperty.Register(
ModulusPropertyName, typeof(double), typeof(NumberBox), new UIPropertyMetadata(0.0));
///
/// The number property.
///
public static readonly DependencyProperty NumberProperty = DependencyProperty.Register(
"Number", typeof(double), typeof(NumberBox), new PropertyMetadata(OnNumberChanged));
///
/// 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 NumberBox()
{
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 allow empty.
///
public bool AllowEmpty
{
get
{
return (bool)this.GetValue(AllowEmptyProperty);
}
set
{
this.SetValue(AllowEmptyProperty, value);
}
}
///
/// Gets or sets the maximum.
///
public double Maximum
{
get
{
return (double)this.GetValue(MaximumProperty);
}
set
{
this.SetValue(MaximumProperty, value);
}
}
///
/// Gets or sets the minimum.
///
public double Minimum
{
get
{
return (double)this.GetValue(MinimumProperty);
}
set
{
this.SetValue(MinimumProperty, value);
}
}
///
/// Gets or sets the modulus.
///
public double Modulus
{
get
{
return (double)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 double Number
{
get
{
return (double)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 double 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 NumberBox;
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 NumberBox;
if (!numBox.suppressRefresh)
{
numBox.RefreshNumberBox();
}
}
}
///
/// The decrement number.
///
private void DecrementNumber()
{
double 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 double GetNearestValue(double number, double modulus)
{
double remainder = number % modulus;
if (remainder == 0)
{
return number;
}
return remainder >= (modulus / 2) ? number + (modulus - remainder) : number - remainder;
}
///
/// The increment number.
///
private void IncrementNumber()
{
double 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.ClearValue(TextBox.ForegroundProperty);
}
}
///
/// 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;
}
if (e.Key == Key.Up)
{
this.IncrementNumber();
}
else if (e.Key == Key.Down)
{
this.DecrementNumber();
}
}
///
/// 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 != '.' && (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;
}
else
{
this.numberBox.Text = this.Number.ToString(CultureInfo.InvariantCulture);
}
this.RefreshNumberBoxColor();
}
///
/// The refresh number box color.
///
private void RefreshNumberBoxColor()
{
if (this.numberBox.Text == this.NoneCaption)
{
this.numberBox.Foreground = new SolidColorBrush(Colors.Gray);
}
else
{
this.numberBox.ClearValue(TextBox.ForegroundProperty);
}
}
///
/// 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()
{
double newNumber;
if (double.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;
}
}
}
}
///
/// 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
}
}