/****************************************************************************
|
**
|
** Copyright (C) 2020 The Qt Company Ltd.
|
** Contact: https://www.qt.io/licensing/
|
**
|
** This file is part of the Qt VS Tools.
|
**
|
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
** Commercial License Usage
|
** Licensees holding valid commercial Qt licenses may use this file in
|
** accordance with the commercial license agreement provided with the
|
** Software or, alternatively, in accordance with the terms contained in
|
** a written agreement between you and The Qt Company. For licensing terms
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
** information use the contact form at https://www.qt.io/contact-us.
|
**
|
** GNU General Public License Usage
|
** Alternatively, this file may be used under the terms of the GNU
|
** General Public License version 3 as published by the Free Software
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
** included in the packaging of this file. Please review the following
|
** information to ensure the GNU General Public License requirements will
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
**
|
** $QT_END_LICENSE$
|
**
|
****************************************************************************/
|
|
using System;
|
using System.Collections.Generic;
|
using System.IO;
|
using System.Linq;
|
using System.Runtime.InteropServices;
|
using System.Windows;
|
using System.Windows.Controls;
|
using System.Windows.Interop;
|
using System.Windows.Media;
|
using System.Windows.Media.Imaging;
|
using Microsoft.Win32;
|
using QtVsTools.Common;
|
|
namespace QtVsTools.Options
|
{
|
using static EnumExt;
|
|
public enum BuildHost
|
{
|
[String("Windows")] Windows,
|
[String("Linux SSH")] LinuxSSH,
|
[String("Linux WSL")] LinuxWSL,
|
}
|
|
public partial class QtVersionsTable : UserControl
|
{
|
public QtVersionsTable()
|
{
|
InitializeComponent();
|
}
|
|
public class Field
|
{
|
public string Value { get; set; }
|
public Control Control { get; set; }
|
public DataGridCell Cell { get; set; }
|
public string ValidationError { get; set; }
|
public bool IsValid => string.IsNullOrEmpty(ValidationError);
|
public ToolTip ToolTip
|
=> IsValid ? null : new ToolTip() { Content = ValidationError };
|
public int SelectionStart { get; set; }
|
}
|
|
public class Row
|
{
|
public enum FieldNames { IsDefault, VersionName, Host, Path, Compiler }
|
|
public Dictionary<FieldNames, Field> _Fields;
|
public Dictionary<FieldNames, Field> Fields => _Fields
|
?? (_Fields = GetValues<FieldNames>()
|
.Select(field => new KeyValuePair<FieldNames, Field>(field, null))
|
.ToDictionary(keyValue => keyValue.Key, keyValue => keyValue.Value));
|
|
public Field FieldDefault => Fields[FieldNames.IsDefault]
|
?? (Fields[FieldNames.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 string VersionName
|
{
|
get => FieldVersionName.Value;
|
set => FieldVersionName.Value = value;
|
}
|
|
public Field FieldHost => Fields[FieldNames.Host]
|
?? (Fields[FieldNames.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 string Path
|
{
|
get => FieldPath.Value;
|
set => FieldPath.Value = value;
|
}
|
|
public Field FieldCompiler => Fields[FieldNames.Compiler]
|
?? (Fields[FieldNames.Compiler] = new Field());
|
public string Compiler
|
{
|
get => FieldCompiler.Value;
|
set => FieldCompiler.Value = value;
|
}
|
|
public bool LastRow { get; set; }
|
|
public bool DefaultEnabled => !IsDefault && !LastRow;
|
public bool NameEnabled => !LastRow;
|
public bool CompilerEnabled => (Host != BuildHost.Windows);
|
public Visibility RowVisibility
|
=> LastRow ? Visibility.Hidden : Visibility.Visible;
|
public Visibility ButtonAddVisibility
|
=> LastRow ? Visibility.Visible : Visibility.Hidden;
|
public Visibility ButtonBrowseVisibility
|
=> (!LastRow && Host == BuildHost.Windows) ? Visibility.Visible : Visibility.Hidden;
|
public Thickness PathMargin
|
=> new Thickness(((Host == BuildHost.Windows) ? 22 : 2), 0, 2, 0);
|
public FontWeight FontWeight
|
=> IsDefault ? FontWeights.Bold : FontWeights.Normal;
|
|
public static ImageSource _ExplorerIcon;
|
public static ImageSource ExplorerIcon => _ExplorerIcon
|
?? (_ExplorerIcon = GetExplorerIcon());
|
}
|
|
public bool IsValid { get; private set; }
|
|
Field FocusedField { get; set; }
|
|
List<Row> _Rows;
|
List<Row> Rows => _Rows ?? (_Rows = new List<Row>());
|
public IEnumerable<Row> Versions => Rows.TakeWhile(item => !item.LastRow);
|
|
public void UpdateVersions(IEnumerable<Row> versions)
|
{
|
Rows.Clear();
|
Rows.AddRange(versions);
|
Rows.Add(new Row { LastRow = true });
|
DataGrid.ItemsSource = Rows;
|
IsValid = true;
|
FocusedField = null;
|
Validate(true);
|
}
|
|
public IEnumerable<string> GetErrorMessages()
|
{
|
Validate(true);
|
return Versions
|
.SelectMany(v => v.Fields.Values.Select(f => f.ValidationError))
|
.Where(s => !string.IsNullOrEmpty(s))
|
.Distinct();
|
}
|
|
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) {
|
|
//////////////////////
|
// 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 (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 (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 (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 (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 (previousValidation != version.FieldCompiler.ValidationError)
|
mustRefresh = true;
|
}
|
|
//////////////////////////////////////
|
// Refresh versions table if required
|
mustRefresh |= (wasValid != IsValid);
|
if (mustRefresh) {
|
// Reset bindings
|
foreach (var version in Versions) {
|
foreach (var field in version.Fields.Values) {
|
field.Control = null;
|
field.Cell = null;
|
}
|
}
|
// Refresh UI
|
DataGrid.Items.Refresh();
|
}
|
}
|
|
static readonly Brush InvalidCellBackground = new DrawingBrush
|
{
|
TileMode = TileMode.Tile,
|
Viewport = new Rect(0.0, 0.0, 10.0, 10.0),
|
ViewportUnits = BrushMappingMode.Absolute,
|
Viewbox = new Rect(0.0, 0.0, 10.0, 10.0),
|
ViewboxUnits = BrushMappingMode.Absolute,
|
Drawing = new DrawingGroup
|
{
|
Children = new DrawingCollection
|
{
|
new GeometryDrawing
|
{
|
Brush = Brushes.Red,
|
Geometry = new RectangleGeometry(new Rect(5, 0, 5, 10))
|
}
|
}
|
},
|
Transform = new RotateTransform
|
{
|
Angle = -135.0,
|
CenterX = 0.5,
|
CenterY = 0.5
|
},
|
Opacity = 0.25
|
};
|
|
void Control_Loaded(object sender, RoutedEventArgs e)
|
{
|
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))
|
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;
|
}
|
if (fieldBinding == FocusedField)
|
control.Focus();
|
if (control is TextBox textBox && fieldBinding.SelectionStart >= 0)
|
textBox.Select(fieldBinding.SelectionStart, 0);
|
}
|
}
|
|
void ComboBox_Loaded(object sender, RoutedEventArgs e)
|
{
|
if (sender is ComboBox comboBox && GetBinding(comboBox) is Row version) {
|
comboBox.IsEnabled = false;
|
var hosts = GetValues<string>(typeof(BuildHost));
|
comboBox.ItemsSource = hosts;
|
comboBox.Text = version.Host.Cast<string>();
|
comboBox.SelectedIndex = (int)version.Host;
|
comboBox.IsEnabled = true;
|
}
|
Control_Loaded(sender, e);
|
}
|
|
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))
|
return;
|
var fieldBinding = version.Fields[field];
|
if (fieldBinding.Control != control)
|
return;
|
FocusedField = fieldBinding;
|
}
|
}
|
|
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))
|
return;
|
var fieldBinding = version.Fields[field];
|
if (fieldBinding != FocusedField || fieldBinding.Control != control)
|
return;
|
FocusedField = null;
|
}
|
}
|
|
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))
|
return;
|
var fieldBinding = version.Fields[field];
|
if (fieldBinding.Control != textBox)
|
return;
|
fieldBinding.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))
|
return;
|
var fieldBinding = version.Fields[field];
|
if (fieldBinding == null
|
|| fieldBinding.Control != textBox
|
|| fieldBinding.Value == textBox.Text)
|
return;
|
fieldBinding.SelectionStart = textBox.SelectionStart;
|
fieldBinding.Value = textBox.Text;
|
Validate(false);
|
}
|
}
|
|
void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
{
|
if (sender is ComboBox comboBox && GetBinding(comboBox) is Row version) {
|
if (!comboBox.IsEnabled || comboBox.SelectedIndex < 0)
|
return;
|
string comboBoxValue = comboBox.Items[comboBox.SelectedIndex] as string;
|
string controlName = comboBox.Name;
|
Row.FieldNames field;
|
if (string.IsNullOrEmpty(controlName) || !controlName.TryCast(out field))
|
return;
|
var fieldBinding = version.Fields[field];
|
if (fieldBinding == null
|
|| fieldBinding.Control != comboBox
|
|| fieldBinding.Value == comboBoxValue)
|
return;
|
fieldBinding.Value = comboBoxValue;
|
Validate(false);
|
}
|
}
|
|
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();
|
if (defaultVersion != null)
|
defaultVersion.IsDefault = false;
|
version.IsDefault = true;
|
Validate(true);
|
}
|
}
|
|
void Add_Click(object sender, RoutedEventArgs e)
|
{
|
var version = new Row()
|
{
|
IsDefault = !Versions.Any(),
|
Host = BuildHost.Windows,
|
Path = "",
|
Compiler = "msvc",
|
LastRow = false
|
};
|
Rows.Insert(Rows.Count - 1, version);
|
FocusedField = version.FieldVersionName;
|
Validate(true);
|
}
|
|
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;
|
Validate(true);
|
}
|
}
|
|
void Explorer_Click(object sender, RoutedEventArgs e)
|
{
|
if (sender is Button button && GetBinding(button) is Row version) {
|
var openFileDialog = new OpenFileDialog()
|
{
|
AddExtension = false,
|
CheckFileExists = true,
|
CheckPathExists = true,
|
Filter = "qmake Executable|qmake.exe",
|
Title = "Qt VS Tools - Select qmake.exe"
|
};
|
if (openFileDialog.ShowDialog() == true) {
|
var qmakePath = openFileDialog.FileName;
|
var qmakeDir = Path.GetDirectoryName(qmakePath);
|
if (Path.GetFileName(qmakeDir)
|
.Equals("bin", StringComparison.InvariantCultureIgnoreCase)) {
|
qmakeDir = Path.GetDirectoryName(qmakeDir);
|
version.Path = qmakeDir;
|
} else {
|
version.Path = qmakePath;
|
}
|
if (string.IsNullOrEmpty(version.VersionName)) {
|
version.VersionName = string.Format("{0}_{1}",
|
Path.GetFileName(Path.GetDirectoryName(qmakeDir)),
|
Path.GetFileName(qmakeDir))
|
.Replace(" ", "_");
|
}
|
Validate(true);
|
}
|
}
|
}
|
|
static ImageSource GetExplorerIcon()
|
{
|
var pathWindowsExplorer = string.Format(@"{0}\explorer.exe",
|
Environment.GetFolderPath(Environment.SpecialFolder.Windows));
|
|
NativeAPI.SHFILEINFO shellFileInfo = new NativeAPI.SHFILEINFO();
|
NativeAPI.SHGetFileInfo(pathWindowsExplorer,
|
0, ref shellFileInfo, Marshal.SizeOf(shellFileInfo),
|
NativeAPI.SHGFI.Icon | NativeAPI.SHGFI.SmallIcon);
|
if (shellFileInfo.hIcon == IntPtr.Zero)
|
return null;
|
|
var iconImageSource = Imaging.CreateBitmapSourceFromHIcon(
|
shellFileInfo.hIcon, Int32Rect.Empty,
|
BitmapSizeOptions.FromEmptyOptions());
|
|
NativeAPI.DestroyIcon(shellFileInfo.hIcon);
|
return iconImageSource;
|
}
|
|
static object GetBinding(FrameworkElement control)
|
{
|
if (control == null
|
|| control.BindingGroup == null
|
|| control.BindingGroup.Items == null
|
|| control.BindingGroup.Items.Count == 0) {
|
return null;
|
}
|
return control.BindingGroup.Items[0];
|
}
|
|
static DataGridCell FindContainingCell(DependencyObject control)
|
{
|
while (control != null) {
|
if (control is ContentPresenter contentPresenter
|
&& contentPresenter.Parent is DataGridCell cell) {
|
return cell;
|
}
|
control = VisualTreeHelper.GetParent(control);
|
}
|
return null;
|
}
|
}
|
}
|