From ca47896204482bf4a6979e3838bf7f09f61cebeb Mon Sep 17 00:00:00 2001
From: giy <giy@omp-system.ru>
Date: Fri, 02 Sep 2022 14:16:56 +0300
Subject: [PATCH] Обновление до версии 2.9.0

---
 QtVsTools.Package/Options/QtVersionsTable.cs |  348 +++++++++++++++++++++++++++++++++------------------------
 1 files changed, 202 insertions(+), 146 deletions(-)

diff --git a/QtVsTools.Package/Options/QtVersionsTable.cs b/QtVsTools.Package/Options/QtVersionsTable.cs
index dc6f9c5..cb09fff 100644
--- a/QtVsTools.Package/Options/QtVersionsTable.cs
+++ b/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);

--
Gitblit v1.9.1