| /****************************************************************************  | 
| **  | 
| ** Copyright (C) 2016 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.ComponentModel.Design;  | 
| using System.Diagnostics;  | 
| using System.Diagnostics.CodeAnalysis;  | 
| using System.Globalization;  | 
| using System.IO;  | 
| using System.Linq;  | 
| using System.Net.Http;  | 
| using System.Runtime.InteropServices;  | 
| using System.Threading;  | 
| using System.Threading.Tasks;  | 
| using Task = System.Threading.Tasks.Task;  | 
| using System.Windows.Forms;  | 
| using Microsoft.VisualStudio;  | 
| using Microsoft.VisualStudio.OLE.Interop;  | 
| using Microsoft.VisualStudio.Settings;  | 
| using Microsoft.VisualStudio.Shell;  | 
| using Microsoft.VisualStudio.Shell.Interop;  | 
| using Microsoft.VisualStudio.Shell.Settings;  | 
| using Microsoft.VisualStudio.Threading;  | 
| using Microsoft.Win32;  | 
| using EnvDTE;  | 
|   | 
| namespace QtVsTools  | 
| {  | 
|     using Core;  | 
|     using QtMsBuild;  | 
|     using SyntaxAnalysis;  | 
|     using static SyntaxAnalysis.RegExpr;  | 
|     using VisualStudio;  | 
|   | 
|     [Guid(QtVsToolsPackage.PackageGuidString)]  | 
|     [InstalledProductRegistration("#110", "#112", Version.PRODUCT_VERSION, IconResourceID = 400)]  | 
|     [ProvideMenuResource("Menus.ctmenu", 1)]  | 
|     [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]  | 
|     [ProvideAutoLoad(UIContextGuids.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)]  | 
|     [ProvideAutoLoad(UIContextGuids.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]  | 
|   | 
|     // Custom editor: Qt Designer  | 
|     [ProvideEditorExtension(typeof(Editors.QtDesigner),  | 
|         extension: ".ui",  | 
|         priority: 999,  | 
|         DefaultName = Editors.QtDesigner.Title)]  | 
|     [ProvideEditorLogicalView(typeof(Editors.QtDesigner),  | 
|         logicalViewGuid: VSConstants.LOGVIEWID.TextView_string)]  | 
|   | 
|     // Custom editor: Qt Linguist  | 
|     [ProvideEditorExtension(typeof(Editors.QtLinguist),  | 
|         extension: ".ts",  | 
|         priority: 999,  | 
|         DefaultName = Editors.QtLinguist.Title)]  | 
|     [ProvideEditorLogicalView(typeof(Editors.QtLinguist),  | 
|         logicalViewGuid: VSConstants.LOGVIEWID.TextView_string)]  | 
|   | 
|     // Custom editor: Qt Resource Editor  | 
|     [ProvideEditorExtension(typeof(Editors.QtResourceEditor),  | 
|         extension: ".qrc",  | 
|         priority: 999,  | 
|         DefaultName = Editors.QtResourceEditor.Title)]  | 
|     [ProvideEditorLogicalView(typeof(Editors.QtResourceEditor),  | 
|         logicalViewGuid: VSConstants.LOGVIEWID.TextView_string)]  | 
|   | 
|     // Options page  | 
|     [ProvideOptionPage(typeof(Options.QtOptionsPage),  | 
|         "Qt", "General", 0, 0, true, Sort = 0)]  | 
|   | 
|     // Qt Versions page  | 
|     [ProvideOptionPage(typeof(Options.QtVersionsPage),  | 
|         "Qt", "Versions", 0, 0, true, Sort = 1)]  | 
|   | 
|     // Legacy options page  | 
|     [ProvideOptionPage(typeof(Options.QtLegacyOptionsPage),  | 
|         "Qt", "Legacy Project Format", 0, 0, true, Sort = 2)]  | 
|   | 
|     public sealed class QtVsToolsPackage : AsyncPackage, IVsServiceProvider, IProjectTracker  | 
|     {  | 
|         public const string PackageGuidString = "15021976-647e-4876-9040-2507afde45d2";  | 
|         const StringComparison IGNORE_CASE = StringComparison.InvariantCultureIgnoreCase;  | 
|   | 
|         public DTE Dte { get; private set; }  | 
|         public string PkgInstallPath { get; private set; }  | 
|         public Options.QtOptionsPage Options  | 
|             => GetDialogPage(typeof(Options.QtOptionsPage)) as Options.QtOptionsPage;  | 
|         public Options.QtLegacyOptionsPage LegacyOptions  | 
|             => GetDialogPage(typeof(Options.QtLegacyOptionsPage)) as Options.QtLegacyOptionsPage;  | 
|         public Editors.QtDesigner QtDesigner { get; private set; }  | 
|         public Editors.QtLinguist QtLinguist { get; private set; }  | 
|         public Editors.QtResourceEditor QtResourceEditor { get; private set; }  | 
|   | 
|         static EventWaitHandle initDone = new EventWaitHandle(false, EventResetMode.ManualReset);  | 
|   | 
|         static QtVsToolsPackage instance = null;  | 
|         public static QtVsToolsPackage Instance  | 
|         {  | 
|             get  | 
|             {  | 
|                 initDone.WaitOne();  | 
|                 return instance;  | 
|             }  | 
|         }  | 
|   | 
|         private string locateHelperExecutable(string exeName)  | 
|         {  | 
|             if (!string.IsNullOrEmpty(PkgInstallPath) && File.Exists(PkgInstallPath + exeName))  | 
|                 return PkgInstallPath + exeName;  | 
|             return null;  | 
|         }  | 
|   | 
|         private string _QMakeFileReaderPath;  | 
|         public string QMakeFileReaderPath  | 
|         {  | 
|             get  | 
|             {  | 
|                 if (_QMakeFileReaderPath == null)  | 
|                     _QMakeFileReaderPath = locateHelperExecutable("QMakeFileReader.exe");  | 
|                 return _QMakeFileReaderPath;  | 
|             }  | 
|         }  | 
|   | 
|         static readonly Stopwatch initTimer = Stopwatch.StartNew();  | 
|         static readonly HttpClient http = new HttpClient();  | 
|         const string urlDownloadQtIo = "https://download.qt.io/development_releases/vsaddin/";  | 
|   | 
|         private DteEventsHandler eventHandler;  | 
|         private bool useQtTmLanguage;  | 
|         private string qtTmLanguagePath;  | 
|         private string visualizersPath;  | 
|   | 
|   | 
|         public QtVsToolsPackage()  | 
|         {  | 
|         }  | 
|   | 
|         protected override async Task InitializeAsync(  | 
|             CancellationToken cancellationToken,  | 
|             IProgress<ServiceProgressData> progress)  | 
|         {  | 
|             try {  | 
|                 var timeInitBegin = initTimer.Elapsed;  | 
|                 VsServiceProvider.Instance = instance = this;  | 
|                 QtProject.ProjectTracker = this;  | 
|                 Messages.JoinableTaskFactory = JoinableTaskFactory;  | 
|   | 
|                 // determine the package installation directory  | 
|                 var uri = new Uri(System.Reflection.Assembly  | 
|                     .GetExecutingAssembly().EscapedCodeBase);  | 
|                 PkgInstallPath = Path.GetDirectoryName(  | 
|                     Uri.UnescapeDataString(uri.AbsolutePath)) + @"\";  | 
|   | 
|                 ///////////////////////////////////////////////////////////////////////////////////  | 
|                 // Switch to main (UI) thread  | 
|                 await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);  | 
|                 var timeUiThreadBegin = initTimer.Elapsed;  | 
|   | 
|                 if ((Dte = VsServiceProvider.GetService<DTE>()) == null)  | 
|                     throw new Exception("Unable to get service: DTE");  | 
|   | 
|                 QtVSIPSettings.Options = Options;  | 
|   | 
|                 eventHandler = new DteEventsHandler(Dte);  | 
|   | 
|                 Qml.Debug.Launcher.Initialize();  | 
|                 QtMainMenu.Initialize(this);  | 
|                 QtSolutionContextMenu.Initialize(this);  | 
|                 QtProjectContextMenu.Initialize(this);  | 
|                 QtItemContextMenu.Initialize(this);  | 
|                 RegisterEditorFactory(QtDesigner = new Editors.QtDesigner());  | 
|                 RegisterEditorFactory(QtLinguist = new Editors.QtLinguist());  | 
|                 RegisterEditorFactory(QtResourceEditor = new Editors.QtResourceEditor());  | 
|                 QtHelp.Initialize(this);  | 
|   | 
|                 if (!string.IsNullOrEmpty(VsShell.InstallRootDir))  | 
|                     HelperFunctions.VCPath = Path.Combine(VsShell.InstallRootDir, "VC");  | 
|   | 
|                 GetTextMateLanguagePath();  | 
|                 GetNatvisPath();  | 
|   | 
|                 ///////////////////////////////////////////////////////////////////////////////////  | 
|                 // Switch to background thread  | 
|                 await TaskScheduler.Default;  | 
|                 var timeUiThreadEnd = initTimer.Elapsed;  | 
|   | 
|                 var vm = QtVersionManager.The(initDone);  | 
|                 var error = string.Empty;  | 
|                 if (vm.HasInvalidVersions(out error))  | 
|                     Messages.Print(error);  | 
|   | 
|                 ///////////  | 
|                 // Install Qt/MSBuild files from package folder to standard location  | 
|                 //  -> %LOCALAPPDATA%\QtMsBuild  | 
|                 //  | 
|                 var QtMsBuildDefault = Path.Combine(  | 
|                     Environment.GetEnvironmentVariable("LocalAppData"), "QtMsBuild");  | 
|                 try {  | 
|                     var qtMsBuildDefaultUri = new Uri(QtMsBuildDefault + "\\");  | 
|                     var qtMsBuildVsixPath = Path.Combine(PkgInstallPath, "QtMsBuild");  | 
|                     var qtMsBuildVsixUri = new Uri(qtMsBuildVsixPath + "\\");  | 
|                     if (qtMsBuildVsixUri != qtMsBuildDefaultUri) {  | 
|                         var qtMsBuildVsixFiles = Directory  | 
|                             .GetFiles(qtMsBuildVsixPath, "*", SearchOption.AllDirectories)  | 
|                             .Select(x => qtMsBuildVsixUri.MakeRelativeUri(new Uri(x)));  | 
|                         foreach (var qtMsBuildFile in qtMsBuildVsixFiles) {  | 
|                             var sourcePath = new Uri(qtMsBuildVsixUri, qtMsBuildFile).LocalPath;  | 
|                             var targetPath = new Uri(qtMsBuildDefaultUri, qtMsBuildFile).LocalPath;  | 
|                             var targetPathTemp = targetPath + ".tmp";  | 
|                             Directory.CreateDirectory(Path.GetDirectoryName(targetPath));  | 
|                             File.Copy(sourcePath, targetPathTemp, overwrite: true);  | 
|                             ////////  | 
|                             // Copy Qt/MSBuild files to standard location, taking care not to  | 
|                             // overwrite the updated Qt props file, possibly containing user-defined  | 
|                             // build settings (written by the VS Property Manager). This file is  | 
|                             // recognized as being named "Qt.props" and containing the import  | 
|                             // statement for qt_private.props.  | 
|                             //  | 
|                             string qtPrivateImport =  | 
|                                 @"<Import Project=""$(MSBuildThisFileDirectory)\qt_private.props""";  | 
|                             Func<string, bool> isUpdateQtProps = (string filePath) =>  | 
|                             {  | 
|                                 return Path.GetFileName(targetPath).Equals("Qt.props", IGNORE_CASE)  | 
|                                     && File.ReadAllText(targetPath).Contains(qtPrivateImport);  | 
|                             };  | 
|                             if (!File.Exists(targetPath)) {  | 
|                                 // Target file does not exist  | 
|                                 //  -> Create new  | 
|                                 File.Move(targetPathTemp, targetPath);  | 
|                             } else if (!isUpdateQtProps(targetPath)) {  | 
|                                 // Target file is not the updated Qt.props  | 
|                                 //  -> Overwrite  | 
|                                 File.Replace(targetPathTemp, targetPath, null);  | 
|                             } else {  | 
|                                 // Target file *is* the updated Qt.props; skip!  | 
|                                 //  -> Remove temp file  | 
|                                 File.Delete(targetPathTemp);  | 
|                             }  | 
|                         }  | 
|                     }  | 
|                 } catch {  | 
|                     /////////  | 
|                     // Error copying files to standard location.  | 
|                     //  -> FAIL-SAFE: use source folder (within package) as the standard location  | 
|                     QtMsBuildDefault = Path.Combine(PkgInstallPath, "QtMsBuild");  | 
|                 }  | 
|   | 
|                 ///////  | 
|                 // Set %QTMSBUILD% by default to point to standard location of Qt/MSBuild  | 
|                 //  | 
|                 var QtMsBuildPath = Environment.GetEnvironmentVariable("QtMsBuild");  | 
|                 if (string.IsNullOrEmpty(QtMsBuildPath)) {  | 
|   | 
|                     Environment.SetEnvironmentVariable(  | 
|                         "QtMsBuild", QtMsBuildDefault,  | 
|                         EnvironmentVariableTarget.User);  | 
|   | 
|                     Environment.SetEnvironmentVariable(  | 
|                         "QtMsBuild", QtMsBuildDefault,  | 
|                         EnvironmentVariableTarget.Process);  | 
|                 }  | 
|   | 
|                 CopyTextMateLanguageFiles();  | 
|                 CopyNatvisFiles();  | 
|   | 
|                 Messages.Print(string.Format("\r\n"  | 
|                     + "== Qt Visual Studio Tools version {0}\r\n"  | 
|                     + "\r\n"  | 
|                     + "   Initialized in: {1:0.##} msecs\r\n"  | 
|                     + "   Main (UI) thread: {2:0.##} msecs\r\n"  | 
|                     , Version.USER_VERSION  | 
|                     , (initTimer.Elapsed - timeInitBegin).TotalMilliseconds  | 
|                     , (timeUiThreadEnd - timeUiThreadBegin).TotalMilliseconds  | 
|                     ));  | 
|   | 
|                 var devRelease = await GetLatestDevelopmentReleaseAsync();  | 
|                 if (devRelease != null) {  | 
|                     Messages.Print(string.Format(@"  | 
|     ================================================================  | 
|       Qt Visual Studio Tools version {1} PREVIEW available at:  | 
|       {0}{1}/  | 
|     ================================================================",  | 
|                         urlDownloadQtIo, devRelease));  | 
|                 }  | 
|             } catch (Exception e) {  | 
|                 Messages.Print(  | 
|                     e.Message + "\r\n\r\nStacktrace:\r\n" + e.StackTrace);  | 
|             } finally {  | 
|                 initDone.Set();  | 
|                 initTimer.Stop();  | 
|             }  | 
|   | 
|             ///////////////////////////////////////////////////////////////////////////////////  | 
|             // Switch to main (UI) thread  | 
|             await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);  | 
|   | 
|             /////////  | 
|             // Check if a solution was opened during initialization.  | 
|             // If so, fire solution open event.  | 
|             //  | 
|             if (Dte?.Solution?.IsOpen == true)  | 
|                 eventHandler.SolutionEvents_Opened();  | 
|         }  | 
|   | 
|         protected override int QueryClose(out bool canClose)  | 
|         {  | 
|             if (eventHandler != null) {  | 
|                 eventHandler.Disconnect();  | 
|             }  | 
|             return base.QueryClose(out canClose);  | 
|         }  | 
|   | 
|         private void GetTextMateLanguagePath()  | 
|         {  | 
|             var settingsManager = new ShellSettingsManager(this as System.IServiceProvider);  | 
|             var store = settingsManager.GetReadOnlySettingsStore(SettingsScope.UserSettings);  | 
|             useQtTmLanguage = store.GetBoolean(@"QtVsTools\Qml\TextMate", @"Enable", true);  | 
|             qtTmLanguagePath = Environment.  | 
|                 ExpandEnvironmentVariables("%USERPROFILE%\\.vs\\Extensions\\qttmlanguage");  | 
|         }  | 
|   | 
|         private void CopyTextMateLanguageFiles()  | 
|         {  | 
|             if (useQtTmLanguage) {  | 
|                 HelperFunctions.CopyDirectory(Path.Combine(PkgInstallPath, "qttmlanguage"),  | 
|                     qtTmLanguagePath);  | 
|             } else {  | 
|                 Directory.Delete(qtTmLanguagePath, true);  | 
|             }  | 
|   | 
|             //Remove textmate-based QML syntax highlighting  | 
|             var qmlTextmate = Path.Combine(qtTmLanguagePath, "qml");  | 
|             if (Directory.Exists(qmlTextmate)) {  | 
|                 try {  | 
|                     Directory.Delete(qmlTextmate, true);  | 
|                 } catch { }  | 
|             }  | 
|         }  | 
|   | 
|         private void CopyNatvisFile(string filename, string qtNamespace)  | 
|         {  | 
|             try {  | 
|                 string natvis = File.ReadAllText(Path.Combine(PkgInstallPath, filename));  | 
|   | 
|                 string natvisFile;  | 
|                 if (string.IsNullOrEmpty(qtNamespace)) {  | 
|                     natvis = natvis.Replace("##NAMESPACE##::", string.Empty);  | 
|                     natvisFile = Path.GetFileNameWithoutExtension(filename);  | 
|                 } else {  | 
|                     natvis = natvis.Replace("##NAMESPACE##", qtNamespace);  | 
|                     natvisFile = string.Format(filename.Substring(0, filename.IndexOf('.'))  | 
|                         + "_{0}.natvis", qtNamespace.Replace("::", "_"));  | 
|                 }  | 
|   | 
|                 if (!Directory.Exists(visualizersPath))  | 
|                     Directory.CreateDirectory(visualizersPath);  | 
|   | 
|                 File.WriteAllText(Path.Combine(visualizersPath, natvisFile),  | 
|                     natvis, System.Text.Encoding.UTF8);  | 
|             } catch (Exception e) {  | 
|                 Messages.Print(  | 
|                     e.Message + "\r\n\r\nStacktrace:\r\n" + e.StackTrace);  | 
|             }  | 
|         }  | 
|   | 
|         public string GetNatvisPath()  | 
|         {  | 
|             try {  | 
|                 using (var vsRootKey = Registry.CurrentUser.OpenSubKey(Dte.RegistryRoot)) {  | 
|                     if (vsRootKey.GetValue("VisualStudioLocation") is string vsLocation)  | 
|                         visualizersPath = Path.Combine(vsLocation, "Visualizers");  | 
|                 }  | 
|             } catch {  | 
|             }  | 
|             if (string.IsNullOrEmpty(visualizersPath)) {  | 
|                 visualizersPath = Path.Combine(  | 
|                     Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),  | 
| #if VS2022  | 
|                     @"Visual Studio 2022\Visualizers\");  | 
| #elif VS2019  | 
|                     @"Visual Studio 2019\Visualizers\");  | 
| #elif VS2017  | 
|                     @"Visual Studio 2017\Visualizers\");  | 
| #endif  | 
|             }  | 
|             return visualizersPath;  | 
|         }  | 
|   | 
|         public void CopyNatvisFiles(string qtNamespace = null)  | 
|         {  | 
|             string[] files = { "qt5.natvis.xml", "qt6.natvis.xml" };  | 
|             foreach (var file in files)  | 
|                 CopyNatvisFile(file, qtNamespace);  | 
|         }  | 
|   | 
|         public I GetService<T, I>()  | 
|             where T : class  | 
|             where I : class  | 
|         {  | 
|             return GetService(typeof(T)) as I;  | 
|         }  | 
|   | 
|         public async Task<I> GetServiceAsync<T, I>()  | 
|             where T : class  | 
|             where I : class  | 
|         {  | 
|             return await GetServiceAsync(typeof(T)) as I;  | 
|         }  | 
|   | 
|         void IProjectTracker.AddProject(Project project)  | 
|         {  | 
|             QtProjectTracker.Add(project);  | 
|         }  | 
|   | 
|         async Task<string> GetLatestDevelopmentReleaseAsync()  | 
|         {  | 
|             var currentVersion = new System.Version(Version.PRODUCT_VERSION);  | 
|             try {  | 
|                 var response = await http.GetAsync(urlDownloadQtIo);  | 
|                 if (!response.IsSuccessStatusCode)  | 
|                     return null;  | 
|   | 
|                 var tokenVersion = new Token("VERSION", Number & "." & Number & "." & Number)  | 
|                 {  | 
|                     new Rule<System.Version> { Capture(value => new System.Version(value)) }  | 
|                 };  | 
|                 var regexHrefVersion = "href=\"" & tokenVersion & Chars["/"].Optional() & "\"";  | 
|                 var regexResponse = (regexHrefVersion | AnyChar | VertSpace).Repeat();  | 
|                 var parserResponse = regexResponse.Render();  | 
|   | 
|                 var responseData = await response.Content.ReadAsStringAsync();  | 
|                 var devVersion = parserResponse.Parse(responseData)  | 
|                     .GetValues<System.Version>("VERSION")  | 
|                     .Where(v => currentVersion < v)  | 
|                     .Max();  | 
|                 if (devVersion == null)  | 
|                     return null;  | 
|   | 
|                 response = await http.GetAsync(  | 
|                     string.Format("{0}{1}/", urlDownloadQtIo, devVersion));  | 
|                 if (!response.IsSuccessStatusCode)  | 
|                     return null;  | 
|                 return devVersion.ToString();  | 
|             } catch {  | 
|                 return null;  | 
|             }  | 
|         }  | 
|     }  | 
| }  |