File: ChangeSignature\ChangeSignatureDialog.xaml.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ChangeSignature;
using Microsoft.VisualStudio.PlatformUI;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature;
 
/// <summary>
/// Interaction logic for ChangeSignatureDialog.xaml
/// </summary>
internal partial class ChangeSignatureDialog : DialogWindow
{
    private readonly ChangeSignatureDialogViewModel _viewModel;
 
    // Expose localized strings for binding
    public static string ChangeSignatureDialogTitle { get { return EditorFeaturesResources.Change_Signature; } }
    public static string CurrentParameter { get { return ServicesVSResources.Current_parameter; } }
    public static string Parameters { get { return ServicesVSResources.Parameters_colon2; } }
    public static string PreviewMethodSignature { get { return ServicesVSResources.Preview_method_signature_colon; } }
    public static string PreviewReferenceChanges { get { return ServicesVSResources.Preview_reference_changes; } }
    public static string Remove { get { return ServicesVSResources.Re_move; } }
    public static string Restore { get { return ServicesVSResources.Restore; } }
    public static string Add { get { return ServicesVSResources.Add; } }
    public static string OK { get { return ServicesVSResources.OK; } }
    public static string Cancel { get { return EditorFeaturesResources.Cancel; } }
    public static string WarningTypeDoesNotBind { get { return ServicesVSResources.Warning_colon_type_does_not_bind; } }
    public static string WarningDuplicateParameterName { get { return ServicesVSResources.Warning_colon_duplicate_parameter_name; } }
 
    public Brush ParameterText { get; }
    public Brush RemovedParameterText { get; }
    public Brush DisabledParameterForeground { get; }
    public Brush DisabledParameterBackground { get; }
    public Brush StrikethroughBrush { get; }
 
    // Use C# Reorder Parameters helpTopic for C# and VB.
    internal ChangeSignatureDialog(ChangeSignatureDialogViewModel viewModel)
        : base(helpTopic: "vs.csharp.refactoring.reorder")
    {
        _viewModel = viewModel;
 
        InitializeComponent();
 
        // Set these headers explicitly because binding to DataGridTextColumn.Header is not
        // supported.
        modifierHeader.Header = ServicesVSResources.Modifier;
        defaultHeader.Header = ServicesVSResources.Default_;
        typeHeader.Header = ServicesVSResources.Type;
        parameterHeader.Header = ServicesVSResources.Parameter;
        callsiteHeader.Header = ServicesVSResources.Callsite;
        indexHeader.Header = ServicesVSResources.Index;
 
        ParameterText = SystemParameters.HighContrast ? SystemColors.WindowTextBrush : new SolidColorBrush(Color.FromArgb(0xFF, 0x1E, 0x1E, 0x1E));
        RemovedParameterText = SystemParameters.HighContrast ? SystemColors.WindowTextBrush : new SolidColorBrush(Colors.Gray);
        DisabledParameterBackground = SystemParameters.HighContrast ? SystemColors.WindowBrush : new SolidColorBrush(Color.FromArgb(0xFF, 0xDF, 0xE7, 0xF3));
        DisabledParameterForeground = SystemParameters.HighContrast ? SystemColors.GrayTextBrush : new SolidColorBrush(Color.FromArgb(0xFF, 0xA2, 0xA4, 0xA5));
        Members.Background = SystemParameters.HighContrast ? SystemColors.WindowBrush : new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
        StrikethroughBrush = SystemParameters.HighContrast ? SystemColors.WindowTextBrush : new SolidColorBrush(Colors.Red);
 
        DataContext = viewModel;
 
        Loaded += ChangeSignatureDialog_Loaded;
    }
 
    private void ChangeSignatureDialog_Loaded(object sender, RoutedEventArgs e)
        => Members.Focus();
 
    private void OK_Click(object sender, RoutedEventArgs e)
    {
        if (_viewModel.TrySubmit())
        {
            DialogResult = true;
        }
    }
 
    private void Cancel_Click(object sender, RoutedEventArgs e)
        => DialogResult = false;
 
    private void MoveUp_Click(object sender, EventArgs e)
    {
        MoveUp_UpdateSelectedIndex();
        SetFocusToSelectedRow(false);
    }
 
    private void MoveUp_Click_FocusRow(object sender, EventArgs e)
    {
        MoveUp_UpdateSelectedIndex();
        SetFocusToSelectedRow(true);
    }
 
    private void MoveUp_UpdateSelectedIndex()
    {
        var oldSelectedIndex = Members.SelectedIndex;
        if (_viewModel.CanMoveUp && oldSelectedIndex >= 0)
        {
            _viewModel.MoveUp();
            Members.Items.Refresh();
            Members.SelectedIndex = oldSelectedIndex - 1;
        }
    }
 
    private void MoveDown_Click(object sender, EventArgs e)
    {
        MoveDown_UpdateSelectedIndex();
        SetFocusToSelectedRow(false);
    }
 
    private void MoveDown_Click_FocusRow(object sender, EventArgs e)
    {
        MoveDown_UpdateSelectedIndex();
        SetFocusToSelectedRow(true);
    }
 
    private void MoveDown_UpdateSelectedIndex()
    {
        var oldSelectedIndex = Members.SelectedIndex;
        if (_viewModel.CanMoveDown && oldSelectedIndex >= 0)
        {
            _viewModel.MoveDown();
            Members.Items.Refresh();
            Members.SelectedIndex = oldSelectedIndex + 1;
        }
    }
 
    private void Remove_Click(object sender, RoutedEventArgs e)
    {
        if (_viewModel.CanRemove)
        {
            _viewModel.Remove();
            Members.Items.Refresh();
        }
 
        SetFocusToSelectedRow(true);
    }
 
    private void Restore_Click(object sender, RoutedEventArgs e)
    {
        if (_viewModel.CanRestore)
        {
            _viewModel.Restore();
            Members.Items.Refresh();
        }
 
        SetFocusToSelectedRow(true);
    }
 
    private void Add_Click(object sender, RoutedEventArgs e)
    {
        var addParameterViewModel = _viewModel.CreateAddParameterDialogViewModel();
        var dialog = new AddParameterDialog(addParameterViewModel);
        var result = dialog.ShowModal();
 
        ChangeSignatureLogger.LogAddParameterDialogLaunched();
 
        if (result.HasValue && result.Value)
        {
            ChangeSignatureLogger.LogAddParameterDialogCommitted();
 
            var addedParameter = new AddedParameter(
                addParameterViewModel.TypeSymbol,
                addParameterViewModel.TypeName,
                addParameterViewModel.ParameterName,
                GetCallSiteKind(addParameterViewModel),
                addParameterViewModel.IsCallsiteRegularValue ? addParameterViewModel.CallSiteValue : string.Empty,
                addParameterViewModel.IsRequired,
                addParameterViewModel.IsRequired ? string.Empty : addParameterViewModel.DefaultValue,
                addParameterViewModel.TypeBinds);
 
            _viewModel.AddParameter(addedParameter);
        }
 
        SetFocusToSelectedRow(false);
    }
 
    private static CallSiteKind GetCallSiteKind(AddParameterDialogViewModel addParameterViewModel)
    {
        if (addParameterViewModel.IsCallsiteInferred)
            return CallSiteKind.Inferred;
 
        if (addParameterViewModel.IsCallsiteOmitted)
            return CallSiteKind.Omitted;
 
        if (addParameterViewModel.IsCallsiteTodo)
            return CallSiteKind.Todo;
 
        Debug.Assert(addParameterViewModel.IsCallsiteRegularValue);
 
        return addParameterViewModel.UseNamedArguments
            ? CallSiteKind.ValueWithName
            : CallSiteKind.Value;
    }
 
    private void SetFocusToSelectedRow(bool focusRow)
    {
        if (Members.SelectedIndex >= 0)
        {
            if (Members.ItemContainerGenerator.ContainerFromIndex(Members.SelectedIndex) is not DataGridRow row)
            {
                Members.ScrollIntoView(Members.SelectedItem);
                row = Members.ItemContainerGenerator.ContainerFromIndex(Members.SelectedIndex) as DataGridRow;
            }
 
            if (row != null && focusRow)
            {
                // This line is required primarily for accessibility purposes to ensure the screenreader always
                // focuses on individual rows rather than the parent DataGrid.
                Members.UpdateLayout();
 
                FocusRow(row);
            }
        }
    }
 
    private static void FocusRow(DataGridRow row)
    {
        var cell = row.FindDescendant<DataGridCell>();
        cell?.Focus();
    }
 
    private void MoveSelectionUp_Click(object sender, EventArgs e)
    {
        var oldSelectedIndex = Members.SelectedIndex;
        if (oldSelectedIndex > 0)
        {
            var potentialNewSelectedParameter = Members.Items[oldSelectedIndex - 1] as ChangeSignatureDialogViewModel.ParameterViewModel;
            if (!potentialNewSelectedParameter.IsDisabled)
            {
                Members.SelectedIndex = oldSelectedIndex - 1;
            }
        }
 
        SetFocusToSelectedRow(true);
    }
 
    private void MoveSelectionDown_Click(object sender, EventArgs e)
    {
        var oldSelectedIndex = Members.SelectedIndex;
        if (oldSelectedIndex >= 0 && oldSelectedIndex < Members.Items.Count - 1)
        {
            Members.SelectedIndex = oldSelectedIndex + 1;
        }
 
        SetFocusToSelectedRow(true);
    }
 
    private void Members_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        if (Members.CurrentItem != null)
        {
            // When it has a valid value, CurrentItem is generally more up-to-date than SelectedIndex.
            // For example, if the user clicks on an out of view item in the parameter list (i.e. the
            // parameter list is long and the user scrolls to click another parameter farther down/up
            // in the list), CurrentItem will update immediately while SelectedIndex will not.
            Members.SelectedIndex = Members.Items.IndexOf(Members.CurrentItem);
        }
 
        if (Members.SelectedIndex == -1)
        {
            Members.SelectedIndex = _viewModel.GetStartingSelectionIndex();
        }
 
        SetFocusToSelectedRow(true);
    }
 
    private void ToggleRemovedState(object sender, ExecutedRoutedEventArgs e)
    {
        if (_viewModel.CanRemove)
        {
            _viewModel.Remove();
        }
        else if (_viewModel.CanRestore)
        {
            _viewModel.Restore();
        }
 
        Members.Items.Refresh();
        SetFocusToSelectedRow(true);
    }
 
    internal TestAccessor GetTestAccessor()
        => new(this);
 
    internal readonly struct TestAccessor
    {
        private readonly ChangeSignatureDialog _dialog;
 
        public TestAccessor(ChangeSignatureDialog dialog)
            => _dialog = dialog;
 
        public ChangeSignatureDialogViewModel ViewModel => _dialog._viewModel;
 
        public DataGrid Members => _dialog.Members;
 
        public DialogButton OKButton => _dialog.OKButton;
 
        public DialogButton CancelButton => _dialog.CancelButton;
 
        public DialogButton DownButton => _dialog.DownButton;
 
        public DialogButton UpButton => _dialog.UpButton;
 
        public DialogButton AddButton => _dialog.AddButton;
 
        public DialogButton RemoveButton => _dialog.RemoveButton;
 
        public DialogButton RestoreButton => _dialog.RestoreButton;
    }
}