// -------------------------------------------------------------------------------------------------------------------- // // 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 } }