Наша сборка Qt VS Tools
giy
2022-09-02 ca47896204482bf4a6979e3838bf7f09f61cebeb
QtVsTools.Package/Options/QtVersionsTable.cs
@@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt VS Tools.
@@ -37,11 +37,12 @@
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
using QtVsTools.Common;
namespace QtVsTools.Options
{
    using static EnumExt;
    using Common;
    using QtVsTools.Core;
    using static Common.EnumExt;
    public enum BuildHost
    {
@@ -52,9 +53,28 @@
    public partial class QtVersionsTable : UserControl
    {
        LazyFactory Lazy { get; } = new LazyFactory();
        public QtVersionsTable()
        {
            InitializeComponent();
        }
        [Flags] public enum Column
        {
            IsDefault = 0x10,
            VersionName = 0x20,
            Host = 0x40,
            Path = 0x80,
            Compiler = 0x100
        }
        [Flags] public enum State
        {
            Unknown = 0x00,
            Existing = 0x01,
            Removed = 0x02,
            Modified = 0x04
        }
        public class Field
@@ -62,57 +82,66 @@
            public string Value { get; set; }
            public Control Control { get; set; }
            public DataGridCell Cell { get; set; }
            public string ValidationError { get; set; }
            private string error;
            public string ValidationError {
                set {
                    UpdateUi = value != error;
                    error = value;
                }
                get { return error; }
            }
            public bool IsValid => string.IsNullOrEmpty(ValidationError);
            public ToolTip ToolTip
                => IsValid ? null : new ToolTip() { Content = ValidationError };
            public int SelectionStart { get; set; }
            public bool UpdateUi { get; private set; } = false;
        }
        public class Row
        {
            public enum FieldNames { IsDefault, VersionName, Host, Path, Compiler }
            static LazyFactory StaticLazy { get; } = new LazyFactory();
            LazyFactory Lazy { get; } = new LazyFactory();
            public Dictionary<FieldNames, Field> _Fields;
            public Dictionary<FieldNames, Field> Fields => _Fields
                ?? (_Fields = GetValues<FieldNames>()
                    .Select(field => new KeyValuePair<FieldNames, Field>(field, null))
            public Dictionary<Column, Field> Fields => Lazy.Get(() =>
                Fields, () => GetValues<Column>()
                    .Select(field => new KeyValuePair<Column, Field>(field, null))
                    .ToDictionary(keyValue => keyValue.Key, keyValue => keyValue.Value));
            public Field FieldDefault => Fields[FieldNames.IsDefault]
                ?? (Fields[FieldNames.IsDefault] = new Field());
            public Field FieldDefault => Fields[Column.IsDefault]
                ?? (Fields[Column.IsDefault] = new Field());
            public bool IsDefault
            {
                get => (FieldDefault.Value == true.ToString());
                set => FieldDefault.Value = value.ToString();
            }
            public Field FieldVersionName => Fields[FieldNames.VersionName]
                ?? (Fields[FieldNames.VersionName] = new Field());
            public Field FieldVersionName => Fields[Column.VersionName]
                ?? (Fields[Column.VersionName] = new Field());
            public string VersionName
            {
                get => FieldVersionName.Value;
                set => FieldVersionName.Value = value;
            }
            public string InitialVersionName { get; set; }
            public Field FieldHost => Fields[FieldNames.Host]
                ?? (Fields[FieldNames.Host] = new Field());
            public Field FieldHost => Fields[Column.Host]
                ?? (Fields[Column.Host] = new Field());
            public BuildHost Host
            {
                get => FieldHost.Value.Cast(defaultValue: BuildHost.Windows);
                set => FieldHost.Value = value.Cast<string>();
            }
            public Field FieldPath => Fields[FieldNames.Path]
                ?? (Fields[FieldNames.Path] = new Field());
            public Field FieldPath => Fields[Column.Path]
                ?? (Fields[Column.Path] = new Field());
            public string Path
            {
                get => FieldPath.Value;
                set => FieldPath.Value = value;
            }
            public Field FieldCompiler => Fields[FieldNames.Compiler]
                ?? (Fields[FieldNames.Compiler] = new Field());
            public Field FieldCompiler => Fields[Column.Compiler]
                ?? (Fields[Column.Compiler] = new Field());
            public string Compiler
            {
                get => FieldCompiler.Value;
@@ -124,7 +153,7 @@
            public bool DefaultEnabled => !IsDefault && !LastRow;
            public bool NameEnabled => !LastRow;
            public bool CompilerEnabled => (Host != BuildHost.Windows);
            public Visibility RowVisibility
            public Visibility RowContentVisibility
                => LastRow ? Visibility.Hidden : Visibility.Visible;
            public Visibility ButtonAddVisibility
                => LastRow ? Visibility.Visible : Visibility.Hidden;
@@ -135,17 +164,16 @@
            public FontWeight FontWeight
                => IsDefault ? FontWeights.Bold : FontWeights.Normal;
            public static ImageSource _ExplorerIcon;
            public static ImageSource ExplorerIcon => _ExplorerIcon
                ?? (_ExplorerIcon = GetExplorerIcon());
        }
            public static ImageSource ExplorerIcon => StaticLazy.Get(() =>
                ExplorerIcon, () => GetExplorerIcon());
        public bool IsValid { get; private set; }
            public State State { get; set; } = State.Unknown;
            public bool RowVisible => State != State.Removed;
        }
        Field FocusedField { get; set; }
        List<Row> _Rows;
        List<Row> Rows => _Rows ?? (_Rows = new List<Row>());
        List<Row> Rows => Lazy.Get(() => Rows, () => new List<Row>());
        public IEnumerable<Row> Versions => Rows.TakeWhile(item => !item.LastRow);
        public void UpdateVersions(IEnumerable<Row> versions)
@@ -154,15 +182,16 @@
            Rows.AddRange(versions);
            Rows.Add(new Row { LastRow = true });
            DataGrid.ItemsSource = Rows;
            IsValid = true;
            FocusedField = null;
            Validate(true);
            Rows.ForEach(item => item.State = State.Existing);
        }
        public IEnumerable<string> GetErrorMessages()
        {
            Validate(true);
            return Versions
                .Where(v => v.State != State.Removed)
                .SelectMany(v => v.Fields.Values.Select(f => f.ValidationError))
                .Where(s => !string.IsNullOrEmpty(s))
                .Distinct();
@@ -170,95 +199,73 @@
        void Validate(bool mustRefresh)
        {
            /////////////////////////
            // Automatic cell values
            foreach (var version in Versions) {
                if (version.Host != BuildHost.Windows && version.Compiler == "msvc") {
                    version.Compiler = "g++";
                    version.FieldCompiler.SelectionStart = version.Compiler.Length;
                    mustRefresh = true;
                } else if (version.Host == BuildHost.Windows && version.Compiler != "msvc") {
                    version.Compiler = "msvc";
                    version.FieldCompiler.SelectionStart = version.Compiler.Length;
                    mustRefresh = true;
                }
            }
            ////////////////////////
            // Validate cell values
            string previousValidation;
            bool wasValid = IsValid;
            IsValid = true;
            foreach (var version in Versions) {
                if (!version.State.HasFlag(State.Modified))
                    continue;
                //////////////////////
                // Default validation
                previousValidation = version.FieldDefault.ValidationError;
                version.FieldDefault.ValidationError = null;
                if (version.IsDefault && version.Host != BuildHost.Windows) {
                    version.FieldDefault.ValidationError = "Default version: host must be Windows";
                    IsValid = false;
                if (version.State.HasFlag((State)Column.IsDefault)) {
                    version.FieldDefault.ValidationError = null;
                    if (version.IsDefault && version.Host != BuildHost.Windows)
                        version.FieldDefault.ValidationError = "Default version: Host must be Windows";
                    mustRefresh |= version.FieldDefault.UpdateUi;
                }
                if (previousValidation != version.FieldDefault.ValidationError)
                    mustRefresh = true;
                ///////////////////
                // Name validation
                previousValidation = version.FieldVersionName.ValidationError;
                version.FieldVersionName.ValidationError = null;
                if (string.IsNullOrEmpty(version.VersionName)) {
                    version.FieldVersionName.ValidationError = "Name cannot be empty";
                    IsValid = false;
                } else if (Versions
                    .Where(otherVersion => otherVersion != version
                        && otherVersion.VersionName == version.VersionName)
                    .Any()) {
                    version.FieldVersionName.ValidationError = "Duplicate version names";
                    IsValid = false;
                if (version.State.HasFlag((State)Column.VersionName)) {
                    version.FieldVersionName.ValidationError = null;
                    if (string.IsNullOrEmpty(version.VersionName)) {
                        version.FieldVersionName.ValidationError = "Name cannot be empty";
                    } else if (Versions.Where(otherVersion => otherVersion != version
                        && otherVersion.VersionName == version.VersionName).Any()) {
                        version.FieldVersionName.ValidationError = "Duplicate version names";
                    }
                    mustRefresh |= version.FieldVersionName.UpdateUi;
                }
                if (previousValidation != version.FieldVersionName.ValidationError)
                    mustRefresh = true;
                ///////////////////
                // Host validation
                previousValidation = version.FieldHost.ValidationError;
                version.FieldHost.ValidationError = null;
                if (version.IsDefault && version.Host != BuildHost.Windows) {
                    version.FieldHost.ValidationError = "Default version: host must be Windows";
                    IsValid = false;
                if (version.State.HasFlag((State)Column.Host)) {
                    version.FieldHost.ValidationError = null;
                    if (version.IsDefault && version.Host != BuildHost.Windows)
                        version.FieldHost.ValidationError = "Default version: Host must be Windows";
                    mustRefresh |= version.FieldHost.UpdateUi;
                }
                if (previousValidation != version.FieldHost.ValidationError)
                    mustRefresh = true;
                ///////////////////
                // Path validation
                previousValidation = version.FieldPath.ValidationError;
                version.FieldPath.ValidationError = null;
                if (string.IsNullOrEmpty(version.Path)) {
                    version.FieldPath.ValidationError = "Path cannot be empty";
                    IsValid = false;
                } else if (version.Host == BuildHost.Windows && !Directory.Exists(version.Path)) {
                    version.FieldPath.ValidationError = "Path does not exist";
                    IsValid = false;
                if (version.State.HasFlag((State)Column.Path)) {
                    version.FieldPath.ValidationError = null;
                    if (string.IsNullOrEmpty(version.Path)) {
                        version.FieldPath.ValidationError = "Path cannot be empty";
                    } else if (version.Host == BuildHost.Windows) {
                        string path = NormalizePath(version.Path);
                        if (path == null) {
                            version.FieldPath.ValidationError = "Invalid path format";
                        } else {
                            if (!QMake.Exists(path))
                                version.FieldPath.ValidationError = "Cannot find qmake.exe";
                        }
                    }
                    mustRefresh |= version.FieldPath.UpdateUi;
                }
                if (previousValidation != version.FieldPath.ValidationError)
                    mustRefresh = true;
                ///////////////////////
                // Compiler validation
                previousValidation = version.FieldCompiler.ValidationError;
                version.FieldCompiler.ValidationError = null;
                if (string.IsNullOrEmpty(version.Compiler)) {
                    version.FieldCompiler.ValidationError = "Compiler cannot be empty";
                    IsValid = false;
                if (version.State.HasFlag((State)Column.Compiler)) {
                    version.FieldCompiler.ValidationError = null;
                    if (string.IsNullOrEmpty(version.Compiler))
                        version.FieldCompiler.ValidationError = "Compiler cannot be empty";
                    mustRefresh |= version.FieldCompiler.UpdateUi;
                }
                if (previousValidation != version.FieldCompiler.ValidationError)
                    mustRefresh = true;
            }
            //////////////////////////////////////
            // Refresh versions table if required
            mustRefresh |= (wasValid != IsValid);
            if (mustRefresh) {
                // Reset bindings
                foreach (var version in Versions) {
@@ -304,22 +311,20 @@
            if (sender is Control control && GetBinding(control) is Row version) {
                if (version.LastRow)
                    return;
                Row.FieldNames field;
                if (string.IsNullOrEmpty(control.Name) || !control.Name.TryCast(out field))
                if (string.IsNullOrEmpty(control.Name) || !control.Name.TryCast(out Column column))
                    return;
                var fieldBinding = version.Fields[field];
                fieldBinding.Control = control;
                fieldBinding.Cell = FindContainingCell(control);
                if (fieldBinding.Cell != null) {
                    fieldBinding.Cell.Background =
                        fieldBinding.IsValid ? Brushes.Transparent : InvalidCellBackground;
                var field = version.Fields[column];
                field.Control = control;
                field.Cell = FindContainingCell(control);
                if (field.Cell != null) {
                    field.Cell.Background =
                        field.IsValid ? Brushes.Transparent : InvalidCellBackground;
                }
                if (fieldBinding == FocusedField)
                if (field == FocusedField)
                    control.Focus();
                if (control is TextBox textBox && fieldBinding.SelectionStart >= 0)
                    textBox.Select(fieldBinding.SelectionStart, 0);
                if (control is TextBox textBox && field.SelectionStart >= 0)
                    textBox.Select(field.SelectionStart, 0);
            }
        }
@@ -339,24 +344,24 @@
        void Control_GotFocus(object sender, RoutedEventArgs e)
        {
            if (sender is Control control && GetBinding(control) is Row version) {
                Row.FieldNames field;
                if (string.IsNullOrEmpty(control.Name) || !control.Name.TryCast(out field))
                if (string.IsNullOrEmpty(control.Name) || !control.Name.TryCast(out Column column))
                    return;
                var fieldBinding = version.Fields[field];
                if (fieldBinding.Control != control)
                var field = version.Fields[column];
                if (field.Control != control)
                    return;
                FocusedField = fieldBinding;
                FocusedField = field;
            }
        }
        void Control_LostFocus(object sender, RoutedEventArgs e)
        {
            if (sender is Control control && GetBinding(control) is Row version) {
                Row.FieldNames field;
                if (string.IsNullOrEmpty(control.Name) || !control.Name.TryCast(out field))
                if (string.IsNullOrEmpty(control.Name) || !control.Name.TryCast(out Column column))
                    return;
                var fieldBinding = version.Fields[field];
                if (fieldBinding != FocusedField || fieldBinding.Control != control)
                var field = version.Fields[column];
                if (field != FocusedField || field.Control != control)
                    return;
                FocusedField = null;
            }
@@ -365,29 +370,32 @@
        void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
        {
            if (sender is TextBox textBox && GetBinding(textBox) is Row version) {
                Row.FieldNames field;
                if (string.IsNullOrEmpty(textBox.Name) || !textBox.Name.TryCast(out field))
                if (string.IsNullOrEmpty(textBox.Name) || !textBox.Name.TryCast(out Column column))
                    return;
                var fieldBinding = version.Fields[field];
                if (fieldBinding.Control != textBox)
                var field = version.Fields[column];
                if (field.Control != textBox)
                    return;
                fieldBinding.SelectionStart = textBox.SelectionStart;
                field.SelectionStart = textBox.SelectionStart;
            }
        }
        void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (sender is TextBox textBox && GetBinding(textBox) is Row version) {
                Row.FieldNames field;
                if (string.IsNullOrEmpty(textBox.Name) || !textBox.Name.TryCast(out field))
                if (string.IsNullOrEmpty(textBox.Name) || !textBox.Name.TryCast(out Column column))
                    return;
                var fieldBinding = version.Fields[field];
                if (fieldBinding == null
                    || fieldBinding.Control != textBox
                    || fieldBinding.Value == textBox.Text)
                var field = version.Fields[column];
                if (field == null
                    || field.Control != textBox
                    || field.Value == textBox.Text)
                    return;
                fieldBinding.SelectionStart = textBox.SelectionStart;
                fieldBinding.Value = textBox.Text;
                field.SelectionStart = textBox.SelectionStart;
                field.Value = textBox.Text;
                version.State |= State.Modified | (State)column;
                Validate(false);
            }
        }
@@ -397,28 +405,49 @@
            if (sender is ComboBox comboBox && GetBinding(comboBox) is Row version) {
                if (!comboBox.IsEnabled || comboBox.SelectedIndex < 0)
                    return;
                if (string.IsNullOrEmpty(comboBox.Name) || !comboBox.Name.TryCast(out Column column))
                    return;
                string comboBoxValue = comboBox.Items[comboBox.SelectedIndex] as string;
                string controlName = comboBox.Name;
                Row.FieldNames field;
                if (string.IsNullOrEmpty(controlName) || !controlName.TryCast(out field))
                var field = version.Fields[column];
                if (field == null
                    || field.Control != comboBox
                    || field.Value == comboBoxValue)
                    return;
                var fieldBinding = version.Fields[field];
                if (fieldBinding == null
                    || fieldBinding.Control != comboBox
                    || fieldBinding.Value == comboBoxValue)
                    return;
                fieldBinding.Value = comboBoxValue;
                Validate(false);
                field.Value = comboBoxValue;
                version.State |= State.Modified | (State)Column.Host;
                bool mustRefresh = false;
                if (version.Host != BuildHost.Windows && version.Compiler == "msvc") {
                    version.Compiler = "g++";
                    version.FieldCompiler.SelectionStart = version.Compiler.Length;
                    version.State |= (State)Column.Compiler;
                    mustRefresh = true;
                } else if (version.Host == BuildHost.Windows && version.Compiler != "msvc") {
                    version.Compiler = "msvc";
                    version.FieldCompiler.SelectionStart = version.Compiler.Length;
                    version.State |= (State)Column.Compiler;
                    mustRefresh = true;
                }
                Validate(mustRefresh);
            }
        }
        static void SetDefaultState(ref Row version, bool value)
        {
            version.IsDefault = value;
            version.State |= State.Modified | (State)Column.IsDefault;
        }
        void Default_Click(object sender, RoutedEventArgs e)
        {
            if (sender is CheckBox checkBox && GetBinding(checkBox) is Row version) {
                var defaultVersion = Rows.Where(row => row.IsDefault).FirstOrDefault();
                var defaultVersion = Rows.FirstOrDefault(row => row.IsDefault);
                if (defaultVersion != null)
                    defaultVersion.IsDefault = false;
                version.IsDefault = true;
                    SetDefaultState(ref defaultVersion, false);
                SetDefaultState(ref version, true);
                Validate(true);
            }
        }
@@ -427,12 +456,16 @@
        {
            var version = new Row()
            {
                IsDefault = !Versions.Any(),
                IsDefault = !Versions.Any(x => x.State != State.Removed),
                Host = BuildHost.Windows,
                Path = "",
                Compiler = "msvc",
                LastRow = false
                LastRow = false,
                State = State.Modified | (State)Column.VersionName | (State)Column.Host
                                       | (State)Column.Path | (State)Column.Compiler
            };
            if (version.IsDefault)
                version.State |= (State)Column.IsDefault;
            Rows.Insert(Rows.Count - 1, version);
            FocusedField = version.FieldVersionName;
            Validate(true);
@@ -441,10 +474,26 @@
        void Remove_Click(object sender, RoutedEventArgs e)
        {
            if (sender is Button button && GetBinding(button) is Row version) {
                Rows.Remove(version);
                if (version.IsDefault && Versions.Any())
                    Versions.First().IsDefault = true;
                version.State = State.Removed;
                if (version.IsDefault) {
                    var first = Versions.FirstOrDefault(x => x.State != State.Removed);
                    if (first != null)
                        SetDefaultState(ref first, true);
                }
                Validate(true);
            }
        }
        static string NormalizePath(string path)
        {
            if (string.IsNullOrEmpty(path))
                return path;
            try {
                return Path.GetFullPath(new Uri(path).LocalPath)
                    .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
                    .ToUpperInvariant();
            } catch (UriFormatException) {
                return null;
            }
        }
@@ -462,6 +511,7 @@
                if (openFileDialog.ShowDialog() == true) {
                    var qmakePath = openFileDialog.FileName;
                    var qmakeDir = Path.GetDirectoryName(qmakePath);
                    var previousPath = NormalizePath(version.Path);
                    if (Path.GetFileName(qmakeDir)
                        .Equals("bin", StringComparison.InvariantCultureIgnoreCase)) {
                        qmakeDir = Path.GetDirectoryName(qmakeDir);
@@ -469,12 +519,18 @@
                    } else {
                        version.Path = qmakePath;
                    }
                    if (previousPath != NormalizePath(version.Path))
                        version.State |= State.Modified | (State)Column.Path;
                    if (string.IsNullOrEmpty(version.VersionName)) {
                        version.VersionName = string.Format("{0}_{1}",
                            Path.GetFileName(Path.GetDirectoryName(qmakeDir)),
                            Path.GetFileName(qmakeDir))
                            .Replace(" ", "_");
                        version.State |= State.Modified | (State)Column.VersionName;
                    }
                    Validate(true);
                }
            }
@@ -503,9 +559,9 @@
        static object GetBinding(FrameworkElement control)
        {
            if (control == null
            || control.BindingGroup == null
            || control.BindingGroup.Items == null
            || control.BindingGroup.Items.Count == 0) {
                || control.BindingGroup == null
                || control.BindingGroup.Items == null
                || control.BindingGroup.Items.Count == 0) {
                return null;
            }
            return control.BindingGroup.Items[0];
@@ -515,7 +571,7 @@
        {
            while (control != null) {
                if (control is ContentPresenter contentPresenter
                && contentPresenter.Parent is DataGridCell cell) {
                    && contentPresenter.Parent is DataGridCell cell) {
                    return cell;
                }
                control = VisualTreeHelper.GetParent(control);