/**************************************************************************** ** ** 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.Drawing; using System.IO; using System.Linq; 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.Wizards.ProjectWizard { using QtVsTools.Core; public partial class ConfigPage : WizardPage { interface ICloneable where T : ICloneable { T Clone(); } class Module : ICloneable { public string Name { get; set; } public string Id { get; set; } public bool IsSelected { get; set; } public bool IsReadOnly { get; set; } public bool IsEnabled => !IsReadOnly; public Module Clone() { return new Module { Name = Name, Id = Id, IsSelected = IsSelected, IsReadOnly = IsReadOnly }; } } class Config : ICloneable, IWizardConfiguration { public string Name { get; set; } public VersionInformation QtVersion { get; set; } public string QtVersionName { get; set; } public string QtVersionPath { get; set; } public string Target { get; set; } public string Platform { get; set; } public bool IsDebug { get; set; } public Dictionary Modules { get; set; } public IEnumerable AllModules => Modules.Values; public IEnumerable SelectedModules => Modules.Values.Where((Module m) => m.IsSelected); IEnumerable IWizardConfiguration.Modules => SelectedModules.SelectMany((Module m) => m.Id.Split(' ')); public Config Clone() { return new Config { Name = Name, QtVersion = QtVersion, QtVersionName = QtVersionName, Target = Target, Platform = Platform, IsDebug = IsDebug, Modules = AllModules .Select((Module m) => m.Clone()) .ToDictionary((Module m) => m.Name) }; } } class CloneableList : List where T : ICloneable { public CloneableList() : base() { } public CloneableList(IEnumerable collection) : base(collection) { } public CloneableList Clone() { return new CloneableList(this.Select(x => x.Clone())); } } const string QT_VERSION_DEFAULT = ""; const string QT_VERSION_BROWSE = ""; IEnumerable qtVersionList = new[] { QT_VERSION_DEFAULT, QT_VERSION_BROWSE } .Union(QtVersionManager.The().GetVersions()); QtVersionManager qtVersionManager = QtVersionManager.The(); VersionInformation defaultQtVersionInfo; CloneableList defaultConfigs; List currentConfigs; bool initialNextButtonIsEnabled; bool initialFinishButtonIsEnabled; public ConfigPage() { InitializeComponent(); string defaultQtVersionName = qtVersionManager.GetDefaultVersion(); defaultQtVersionInfo = qtVersionManager.GetVersionInfo(defaultQtVersionName); ErrorIcon.Source = Imaging.CreateBitmapSourceFromHIcon( SystemIcons.Exclamation.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); DataContext = this; Loaded += OnLoaded; } void OnLoaded(object sender, RoutedEventArgs e) { Loaded -= OnLoaded; var qtModules = QtModules.Instance.GetAvailableModules() .Where((QtModule mi) => mi.Selectable) .Select((QtModule mi) => new Module() { Name = mi.Name, Id = mi.proVarQT, IsSelected = Data.DefaultModules.Contains(mi.LibraryPrefix), IsReadOnly = Data.DefaultModules.Contains(mi.LibraryPrefix), }); qtVersionList = new[] { QT_VERSION_DEFAULT, QT_VERSION_BROWSE } .Union(QtVersionManager.The().GetVersions()); if (defaultQtVersionInfo == null) { Validate(); return; } defaultConfigs = new CloneableList { new Config { Name = "Debug", IsDebug = true, QtVersion = defaultQtVersionInfo, QtVersionName = defaultQtVersionInfo.name, Target = defaultQtVersionInfo.isWinRT() ? ProjectTargets.WindowsStore.Cast() : ProjectTargets.Windows.Cast(), Platform = defaultQtVersionInfo.is64Bit() ? ProjectPlatforms.X64.Cast() : ProjectPlatforms.Win32.Cast(), Modules = qtModules.ToDictionary((Module m) => m.Name), }, new Config { Name = "Release", IsDebug = false, QtVersion = defaultQtVersionInfo, QtVersionName = defaultQtVersionInfo.name, Target = defaultQtVersionInfo.isWinRT() ? ProjectTargets.WindowsStore.Cast() : ProjectTargets.Windows.Cast(), Platform = defaultQtVersionInfo.is64Bit() ? ProjectPlatforms.X64.Cast() : ProjectPlatforms.Win32.Cast(), Modules = qtModules.ToDictionary((Module m) => m.Name), } }; currentConfigs = defaultConfigs.Clone(); ConfigTable.ItemsSource = currentConfigs; initialNextButtonIsEnabled = NextButton.IsEnabled; initialFinishButtonIsEnabled = FinishButton.IsEnabled; Validate(); } /// /// Callback to validate selected configurations. /// Must return an error message in case of failed validation. /// Otherwise, return empty string or null. /// public Func, string> ValidateConfigs { get; set; } void Validate() { if (currentConfigs == null) { ErrorMsg.Content = "Register at least one Qt version using \"Qt VS Tools\"" + " -> \"Qt Options\"."; ErrorPanel.Visibility = Visibility.Visible; NextButton.IsEnabled = false; FinishButton.IsEnabled = false; } else if (currentConfigs // "$(Configuration)|$(Platform)" must be unique .GroupBy((Config c) => string.Format("{0}|{1}", c.Name, c.Platform)) .Where((IGrouping g) => g.Count() > 1) .Any()) { ErrorMsg.Content = "(Configuration, Platform) must be unique"; ErrorPanel.Visibility = Visibility.Visible; NextButton.IsEnabled = false; FinishButton.IsEnabled = false; } else if (ValidateConfigs != null && ValidateConfigs(currentConfigs) is string errorMsg && !string.IsNullOrEmpty(errorMsg)) { ErrorMsg.Content = errorMsg; ErrorPanel.Visibility = Visibility.Visible; NextButton.IsEnabled = false; FinishButton.IsEnabled = false; } else { ErrorMsg.Content = string.Empty; ErrorPanel.Visibility = Visibility.Hidden; NextButton.IsEnabled = initialNextButtonIsEnabled; FinishButton.IsEnabled = initialFinishButtonIsEnabled; } } void RemoveConfig_Click(object sender, RoutedEventArgs e) { if (sender is Button buttonRemove && GetBinding(buttonRemove) is Config config) { currentConfigs.Remove(config); if (!currentConfigs.Any()) { currentConfigs = defaultConfigs.Clone(); ConfigTable.ItemsSource = currentConfigs; } ConfigTable.Items.Refresh(); Validate(); } } void DuplicateConfig_Click(object sender, RoutedEventArgs e) { if (sender is Button buttonDuplicate && GetBinding(buttonDuplicate) is Config config) { currentConfigs.Add(config.Clone()); ConfigTable.Items.Refresh(); Validate(); } } void Name_TextChanged(object sender, TextChangedEventArgs e) { if (sender is TextBox txt && GetBinding(txt) is Config cfg) cfg.Name = txt.Text; Validate(); } void QtVersion_ComboBox_Loaded(object sender, RoutedEventArgs e) { if (sender is ComboBox comboBoxQtVersion && GetBinding(comboBoxQtVersion) is Config config) { comboBoxQtVersion.IsEnabled = false; comboBoxQtVersion.ItemsSource = qtVersionList; comboBoxQtVersion.Text = config.QtVersionName; comboBoxQtVersion.IsEnabled = true; } } void QtVersion_TextChanged(object sender, TextChangedEventArgs e) { if (sender is ComboBox comboBoxQtVersion && comboBoxQtVersion.IsEnabled && GetBinding(comboBoxQtVersion) is Config config && config.QtVersionName != comboBoxQtVersion.Text) { var oldQtVersion = config.QtVersion; if (comboBoxQtVersion.Text == QT_VERSION_DEFAULT) { config.QtVersion = defaultQtVersionInfo; config.QtVersionName = defaultQtVersionInfo.name; config.QtVersionPath = defaultQtVersionInfo.qtDir; comboBoxQtVersion.Text = defaultQtVersionInfo.name; } else if (comboBoxQtVersion.Text == QT_VERSION_BROWSE) { var openFileDialog = new OpenFileDialog { Filter = "qmake (qmake.exe)|qmake.exe" }; if (openFileDialog.ShowDialog() == true) { IEnumerable binPath = Path.GetDirectoryName(openFileDialog.FileName) .Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string lastDirName = binPath.LastOrDefault(); if ("bin".Equals(lastDirName, StringComparison.InvariantCultureIgnoreCase)) binPath = binPath.Take(binPath.Count() - 1); var qtVersion = string.Join( Path.DirectorySeparatorChar.ToString(), binPath); var versionInfo = VersionInformation.Get(qtVersion); if (versionInfo != null) { versionInfo.name = qtVersion; config.QtVersion = versionInfo; config.QtVersionName = versionInfo.name; config.QtVersionPath = config.QtVersion.qtDir; } } comboBoxQtVersion.Text = config.QtVersionName; } else if (qtVersionManager.GetVersions().Contains(comboBoxQtVersion.Text)) { config.QtVersion = qtVersionManager.GetVersionInfo(comboBoxQtVersion.Text); config.QtVersionName = comboBoxQtVersion.Text; config.QtVersionPath = qtVersionManager.GetInstallPath(comboBoxQtVersion.Text); } else { config.QtVersion = null; config.QtVersionName = config.QtVersionPath = comboBoxQtVersion.Text; } if (oldQtVersion != config.QtVersion) { if (config.QtVersion != null) { config.Target = config.QtVersion.isWinRT() ? ProjectTargets.WindowsStore.Cast() : ProjectTargets.Windows.Cast(); config.Platform = config.QtVersion.is64Bit() ? ProjectPlatforms.X64.Cast() : ProjectPlatforms.Win32.Cast(); } else if (config.QtVersionPath.StartsWith("SSH:")) { config.Target = ProjectTargets.LinuxSSH.Cast(); } else if (config.QtVersionPath.StartsWith("WSL:")) { config.Target = ProjectTargets.LinuxWSL.Cast(); } ConfigTable.Items.Refresh(); } Validate(); } } void Target_ComboBox_Loaded(object sender, RoutedEventArgs e) { if (sender is ComboBox comboBoxTarget && GetBinding(comboBoxTarget) is Config config) { comboBoxTarget.IsEnabled = false; comboBoxTarget.ItemsSource = EnumExt.GetValues(typeof(ProjectTargets)); comboBoxTarget.Text = config.Target; comboBoxTarget.IsEnabled = true; } } void Target_TextChanged(object sender, TextChangedEventArgs e) { if (sender is ComboBox comboBoxTarget && comboBoxTarget.IsEnabled && GetBinding(comboBoxTarget) is Config config && config.Target != comboBoxTarget.Text) { config.Target = comboBoxTarget.Text; ConfigTable.Items.Refresh(); Validate(); } } void Platform_ComboBox_Loaded(object sender, RoutedEventArgs e) { if (sender is ComboBox comboBoxPlatform && GetBinding(comboBoxPlatform) is Config config) { comboBoxPlatform.IsEnabled = false; comboBoxPlatform.ItemsSource = EnumExt.GetValues(typeof(ProjectPlatforms)); comboBoxPlatform.Text = config.Platform; comboBoxPlatform.IsEnabled = true; } } void Platform_TextChanged(object sender, TextChangedEventArgs e) { if (sender is ComboBox comboBoxPlatform && comboBoxPlatform.IsEnabled && GetBinding(comboBoxPlatform) is Config config && config.Platform != comboBoxPlatform.Text) { config.Platform = comboBoxPlatform.Text; ConfigTable.Items.Refresh(); Validate(); } } void Debug_Click(object sender, RoutedEventArgs e) { if (sender is CheckBox checkBox && GetBinding(checkBox) is Config config) { config.IsDebug = checkBox.IsChecked ?? false; if (config.IsDebug && config.Name.EndsWith("Release")) { config.Name = string.Format("{0}Debug", config.Name.Substring(0, config.Name.Length - "Release".Length)); ConfigTable.Items.Refresh(); } else if (!config.IsDebug && config.Name.EndsWith("Debug")) { config.Name = string.Format("{0}Release", config.Name.Substring(0, config.Name.Length - "Debug".Length)); ConfigTable.Items.Refresh(); } Validate(); } } void Module_Click(object sender, RoutedEventArgs e) { if (sender is CheckBox checkBoxModule && (checkBoxModule.TemplatedParent as ContentPresenter)?.Content is Module module && GetBinding(checkBoxModule) is Config config && FindAncestor(checkBoxModule, "Modules") is ComboBox comboBoxModules && FindDescendant(comboBoxModules, "SelectedModules") is ListView selectedModules) { selectedModules.ItemsSource = config.SelectedModules; Validate(); } } protected override void OnNextButtonClick(object sender, RoutedEventArgs e) { Data.Configs = currentConfigs.Cast(); base.OnNextButtonClick(sender, e); } protected override void OnFinishButtonClick(object sender, RoutedEventArgs e) { Data.Configs = currentConfigs.Cast(); base.OnFinishButtonClick(sender, e); } static object GetBinding(FrameworkElement control) { if (control.BindingGroup == null || control.BindingGroup.Items == null || control.BindingGroup.Items.Count == 0) { return null; } return control.BindingGroup.Items[0]; } static FrameworkElement FindAncestor(FrameworkElement control, string name) { while (control != null && control.Name != name) { object parent = control.Parent ?? control.TemplatedParent ?? VisualTreeHelper.GetParent(control); control = parent as FrameworkElement; } return control; } static FrameworkElement FindDescendant(FrameworkElement control, string name) { var stack = new Stack(new[] { control }); while (stack.Any()) { control = stack.Pop(); if (control?.Name == name && control is FrameworkElement result) return result; for (int i = 0; i < VisualTreeHelper.GetChildrenCount(control); ++i) { if (VisualTreeHelper.GetChild(control, i) is FrameworkElement child) stack.Push(child); } } return null; } } }