/****************************************************************************
|
**
|
** 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.Diagnostics;
|
using System.Drawing;
|
using System.IO;
|
using System.Linq;
|
using System.Runtime.InteropServices;
|
using System.Security.Permissions;
|
using System.Threading;
|
using System.Windows.Forms;
|
using Microsoft.VisualStudio;
|
using Microsoft.VisualStudio.Shell;
|
using Microsoft.VisualStudio.Shell.Interop;
|
using Microsoft.VisualStudio.VCProjectEngine;
|
using QtVsTools.Core;
|
using QtVsTools.VisualStudio;
|
|
namespace QtVsTools.Editors
|
{
|
using static Core.HelperFunctions;
|
|
public abstract class Editor : IVsEditorFactory
|
{
|
public abstract Guid Guid { get; }
|
public abstract string ExecutableName { get; }
|
|
public virtual Func<string, bool> WindowFilter => (caption => true);
|
|
protected virtual string GetTitle(Process editorProcess)
|
{
|
return editorProcess.StartInfo.FileName;
|
}
|
|
protected virtual string GetToolsPath()
|
{
|
return GetQtToolsPath() ?? GetDefaultQtToolsPath();
|
}
|
|
protected IVsHierarchy Context { get; private set; }
|
protected uint ItemId { get; private set; }
|
|
string GetQtToolsPath()
|
{
|
var project = VsShell.GetProject(Context);
|
if (project == null)
|
return null;
|
|
var vcProject = project.Object as VCProject;
|
if (vcProject == null)
|
return null;
|
|
var vcConfigs = vcProject.Configurations as IVCCollection;
|
if (vcConfigs == null)
|
return null;
|
|
var activeConfig = project.ConfigurationManager?.ActiveConfiguration;
|
if (activeConfig == null)
|
return null;
|
|
var activeConfigId = string.Format("{0}|{1}",
|
activeConfig.ConfigurationName, activeConfig.PlatformName);
|
var vcConfig = vcConfigs.Item(activeConfigId) as VCConfiguration;
|
if (vcConfig == null)
|
return null;
|
|
var qtToolsPath = vcConfig.GetEvaluatedPropertyValue("QtToolsPath");
|
if (string.IsNullOrEmpty(qtToolsPath))
|
return null;
|
|
return qtToolsPath;
|
}
|
|
string GetDefaultQtToolsPath()
|
{
|
var versionMgr = QtVersionManager.The();
|
if (versionMgr == null)
|
return null;
|
|
var defaultVersion = versionMgr.GetDefaultVersion();
|
var defaultVersionInfo = versionMgr.GetVersionInfo(defaultVersion);
|
if (defaultVersionInfo == null || string.IsNullOrEmpty(defaultVersionInfo.qtDir))
|
return null;
|
|
return Path.Combine(defaultVersionInfo.qtDir, "bin");
|
}
|
|
[EnvironmentPermission(SecurityAction.Demand, Unrestricted = true)]
|
public virtual int CreateEditorInstance(
|
uint grfCreateDoc,
|
string pszMkDocument,
|
string pszPhysicalView,
|
IVsHierarchy pvHier,
|
uint itemid,
|
IntPtr punkDocDataExisting,
|
out IntPtr ppunkDocView,
|
out IntPtr ppunkDocData,
|
out string pbstrEditorCaption,
|
out Guid pguidCmdUI,
|
out int pgrfCDW)
|
{
|
// Initialize to null
|
ppunkDocView = IntPtr.Zero;
|
ppunkDocData = IntPtr.Zero;
|
pguidCmdUI = Guid;
|
pgrfCDW = 0;
|
pbstrEditorCaption = null;
|
|
// Validate inputs
|
if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) == 0) {
|
return VSConstants.E_INVALIDARG;
|
}
|
if (punkDocDataExisting != IntPtr.Zero) {
|
return VSConstants.VS_E_INCOMPATIBLEDOCDATA;
|
}
|
|
Context = pvHier;
|
ItemId = itemid;
|
|
var toolsPath = GetToolsPath();
|
if (string.IsNullOrEmpty(toolsPath))
|
return VSConstants.VS_E_INCOMPATIBLEDOCDATA;
|
|
// Create the Document (editor)
|
EditorPane newEditor = new EditorPane(this, toolsPath);
|
ppunkDocView = Marshal.GetIUnknownForObject(newEditor);
|
ppunkDocData = Marshal.GetIUnknownForObject(newEditor);
|
pbstrEditorCaption = "";
|
pgrfCDW = (int)(_VSRDTFLAGS.RDT_CantSave | _VSRDTFLAGS.RDT_DontAutoOpen);
|
|
return VSConstants.S_OK;
|
}
|
|
public virtual int SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp)
|
{
|
return VSConstants.S_OK;
|
}
|
|
public virtual int Close()
|
{
|
return VSConstants.S_OK;
|
}
|
|
public virtual int MapLogicalView(ref Guid rguidLogicalView, out string pbstrPhysicalView)
|
{
|
pbstrPhysicalView = null; // initialize out parameter
|
|
// we support only a single physical view
|
if (VSConstants.LOGVIEWID_Primary == rguidLogicalView) {
|
// primary view uses NULL as pbstrPhysicalView
|
return VSConstants.S_OK;
|
} else {
|
// you must return E_NOTIMPL for any unrecognized rguidLogicalView values
|
return VSConstants.E_NOTIMPL;
|
}
|
}
|
|
protected virtual ProcessStartInfo GetStartInfo(
|
string filePath,
|
string qtToolsPath,
|
bool hideWindow)
|
{
|
return new ProcessStartInfo
|
{
|
FileName = Path.GetFullPath(Path.Combine(qtToolsPath, ExecutableName)),
|
Arguments = SafePath(filePath),
|
WindowStyle = hideWindow ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal
|
};
|
}
|
|
public virtual Process Start(
|
string filePath = "",
|
string qtToolsPath = null,
|
bool hideWindow = true)
|
{
|
if (string.IsNullOrEmpty(qtToolsPath))
|
qtToolsPath = GetDefaultQtToolsPath();
|
try {
|
return Process.Start(GetStartInfo(filePath, qtToolsPath, hideWindow));
|
} catch (Exception e) {
|
Messages.Print(
|
e.Message + "\r\n\r\nStacktrace:\r\n" + e.StackTrace);
|
return null;
|
}
|
}
|
|
protected virtual void OnStart(Process process)
|
{
|
}
|
|
protected virtual void OnExit(Process process)
|
{
|
}
|
|
protected virtual bool Detached => false;
|
|
private class EditorPane : WindowPane, IVsPersistDocData
|
{
|
public Editor Editor { get; private set; }
|
public string QtToolsPath { get; private set; }
|
|
public TableLayoutPanel EditorContainer { get; private set; }
|
public Label EditorTitle { get; private set; }
|
public LinkLabel EditorDetachButton { get; private set; }
|
public Panel EditorControl { get; private set; }
|
public override IWin32Window Window => EditorContainer;
|
|
public Process EditorProcess { get; private set; }
|
public IntPtr EditorWindow { get; private set; }
|
public int EditorWindowStyle { get; private set; }
|
public int EditorWindowStyleExt { get; private set; }
|
public IntPtr EditorIcon { get; private set; }
|
|
public EditorPane(Editor editor, string qtToolsPath)
|
{
|
Editor = editor;
|
QtToolsPath = qtToolsPath;
|
|
var titleBar = new FlowLayoutPanel
|
{
|
AutoSize = true,
|
Dock = DockStyle.Fill,
|
BackColor = Color.FromArgb(201, 221, 201),
|
};
|
titleBar.Controls.Add(EditorTitle = new Label
|
{
|
Text = Editor.ExecutableName,
|
ForeColor = Color.FromArgb(9, 16, 43),
|
Font = new Font("Segoe UI", 8F, FontStyle.Bold, GraphicsUnit.Point),
|
AutoSize = true,
|
Margin = new Padding(8),
|
});
|
titleBar.Controls.Add(EditorDetachButton = new LinkLabel
|
{
|
Text = "Detach",
|
Font = new Font("Segoe UI", 8F, FontStyle.Regular, GraphicsUnit.Point),
|
AutoSize = true,
|
Margin = new Padding(0, 8, 8, 8),
|
});
|
EditorDetachButton.Click += EditorDetachButton_Click;
|
|
EditorControl = new Panel
|
{
|
BackColor = SystemColors.Window,
|
Dock = DockStyle.Fill
|
};
|
EditorControl.Resize += EditorControl_Resize;
|
|
EditorContainer = new TableLayoutPanel
|
{
|
ColumnCount = 1,
|
RowCount = 2,
|
};
|
EditorContainer.ColumnStyles.Add(new ColumnStyle());
|
EditorContainer.RowStyles.Add(new RowStyle());
|
EditorContainer.RowStyles.Add(new RowStyle());
|
EditorContainer.Controls.Add(titleBar, 0, 0);
|
EditorContainer.Controls.Add(EditorControl, 0, 1);
|
}
|
|
protected override void Dispose(bool disposing)
|
{
|
try {
|
if (disposing) {
|
EditorContainer?.Dispose();
|
EditorContainer = null;
|
GC.SuppressFinalize(this);
|
}
|
} finally {
|
base.Dispose(disposing);
|
}
|
}
|
|
int IVsPersistDocData.GetGuidEditorType(out Guid pClassID)
|
{
|
pClassID = Editor.Guid;
|
return VSConstants.S_OK;
|
}
|
|
int IVsPersistDocData.LoadDocData(string pszMkDocument)
|
{
|
var solution = GetService(typeof(SVsSolution)) as IVsSolution;
|
EditorProcess = Editor.Start(pszMkDocument, QtToolsPath,
|
hideWindow: !Editor.Detached);
|
if (EditorProcess == null)
|
return VSConstants.E_FAIL;
|
if (Editor.Detached) {
|
Editor.OnStart(EditorProcess);
|
CloseParentFrame();
|
return VSConstants.S_OK;
|
}
|
|
EditorTitle.Text = Editor.GetTitle(EditorProcess);
|
|
EditorProcess.WaitForInputIdle();
|
EditorProcess.EnableRaisingEvents = true;
|
EditorProcess.Exited += EditorProcess_Exited;
|
|
var t = Stopwatch.StartNew();
|
while (EditorWindow == IntPtr.Zero && t.ElapsedMilliseconds < 5000) {
|
var windows = new Dictionary<IntPtr, string>();
|
foreach (ProcessThread thread in EditorProcess.Threads) {
|
NativeAPI.EnumThreadWindows(
|
dwThreadId: (uint)thread.Id,
|
lParam: IntPtr.Zero,
|
lpfn: (hWnd, lParam) =>
|
{
|
windows.Add(hWnd, NativeAPI.GetWindowCaption(hWnd));
|
return true;
|
});
|
}
|
|
EditorWindow = windows
|
.Where(w => Editor.WindowFilter(w.Value))
|
.Select(w => w.Key)
|
.FirstOrDefault();
|
|
if (EditorWindow == IntPtr.Zero)
|
Thread.Sleep(100);
|
}
|
|
if (EditorWindow == IntPtr.Zero) {
|
EditorProcess.Kill();
|
EditorProcess = null;
|
return VSConstants.E_FAIL;
|
}
|
|
// Save editor window styles and icon
|
EditorWindowStyle = NativeAPI.GetWindowLong(
|
EditorWindow, NativeAPI.GWL_STYLE);
|
EditorWindowStyleExt = NativeAPI.GetWindowLong(
|
EditorWindow, NativeAPI.GWL_EXSTYLE);
|
EditorIcon = NativeAPI.SendMessage(
|
EditorWindow, NativeAPI.WM_GETICON, NativeAPI.ICON_SMALL, 0);
|
if (EditorIcon == IntPtr.Zero)
|
EditorIcon = (IntPtr)NativeAPI.GetClassLong(EditorWindow, NativeAPI.GCL_HICON);
|
if (EditorIcon == IntPtr.Zero)
|
EditorIcon = (IntPtr)NativeAPI.GetClassLong(EditorWindow, NativeAPI.GCL_HICONSM);
|
|
// Move editor window inside VS
|
if (NativeAPI.SetParent(
|
EditorWindow, EditorControl.Handle) == IntPtr.Zero) {
|
EditorWindow = IntPtr.Zero;
|
EditorProcess.Kill();
|
EditorProcess = null;
|
return VSConstants.E_FAIL;
|
}
|
if (NativeAPI.SetWindowLong(
|
EditorWindow, NativeAPI.GWL_STYLE, NativeAPI.WS_VISIBLE) == 0) {
|
EditorWindow = IntPtr.Zero;
|
EditorProcess.Kill();
|
EditorProcess = null;
|
return VSConstants.E_FAIL;
|
}
|
if (!NativeAPI.MoveWindow(
|
EditorWindow, 0, 0, EditorControl.Width, EditorControl.Height, true)) {
|
EditorWindow = IntPtr.Zero;
|
EditorProcess.Kill();
|
EditorProcess = null;
|
return VSConstants.E_FAIL;
|
}
|
|
Editor.OnStart(EditorProcess);
|
return VSConstants.S_OK;
|
}
|
|
void CloseParentFrame()
|
{
|
EditorProcess = null;
|
EditorWindow = IntPtr.Zero;
|
var parentFrame = GetService(typeof(SVsWindowFrame)) as IVsWindowFrame;
|
parentFrame?.CloseFrame((uint)__FRAMECLOSE.FRAMECLOSE_NoSave);
|
}
|
|
private void EditorProcess_Exited(object sender, EventArgs e)
|
{
|
CloseParentFrame();
|
Editor.OnExit(EditorProcess);
|
}
|
|
void EditorControl_Resize(object sender, EventArgs e)
|
{
|
if (EditorControl != null && EditorWindow != IntPtr.Zero) {
|
NativeAPI.MoveWindow(
|
EditorWindow, 0, 0, EditorControl.Width, EditorControl.Height, true);
|
}
|
}
|
|
private void EditorDetachButton_Click(object sender, EventArgs e)
|
{
|
if (EditorProcess != null) {
|
var editorWindow = EditorWindow;
|
DetachEditorWindow();
|
CloseParentFrame();
|
NativeAPI.ShowWindow(editorWindow, NativeAPI.SW_RESTORE);
|
NativeAPI.SetForegroundWindow(editorWindow);
|
}
|
}
|
|
public void DetachEditorWindow()
|
{
|
NativeAPI.ShowWindow(EditorWindow,
|
NativeAPI.SW_HIDE);
|
NativeAPI.SetParent(
|
EditorWindow, IntPtr.Zero);
|
NativeAPI.SetWindowLong(
|
EditorWindow, NativeAPI.GWL_STYLE, EditorWindowStyle);
|
NativeAPI.SetWindowLong(
|
EditorWindow, NativeAPI.GWL_EXSTYLE, EditorWindowStyleExt);
|
NativeAPI.SendMessage(
|
EditorWindow, NativeAPI.WM_SETICON, NativeAPI.ICON_SMALL, EditorIcon);
|
NativeAPI.MoveWindow(
|
EditorWindow, 0, 0, EditorControl.Width, EditorControl.Height, true);
|
NativeAPI.ShowWindow(EditorWindow,
|
NativeAPI.SW_SHOWMINNOACTIVE);
|
}
|
|
int IVsPersistDocData.Close()
|
{
|
if (EditorProcess != null) {
|
DetachEditorWindow();
|
var editorProcess = EditorProcess;
|
EditorProcess = null;
|
var editorWindow = EditorWindow;
|
EditorWindow = IntPtr.Zero;
|
|
// Close editor window
|
System.Threading.Tasks.Task.Run(() =>
|
{
|
NativeAPI.SendMessage(editorWindow, NativeAPI.WM_CLOSE, 0, 0);
|
if (!editorProcess.WaitForExit(500)) {
|
NativeAPI.ShowWindow(editorWindow,
|
NativeAPI.SW_RESTORE);
|
NativeAPI.SetForegroundWindow(editorWindow);
|
}
|
});
|
}
|
|
if (EditorContainer == null) {
|
if (EditorContainer != null) {
|
EditorContainer.Dispose();
|
EditorContainer = null;
|
}
|
}
|
return VSConstants.S_OK;
|
}
|
|
int IVsPersistDocData.RenameDocData(
|
uint grfAttribs,
|
IVsHierarchy pHierNew,
|
uint itemidNew,
|
string pszMkDocumentNew)
|
{
|
return VSConstants.E_NOTIMPL;
|
}
|
|
int IVsPersistDocData.IsDocDataDirty(out int pfDirty)
|
{
|
pfDirty = 0;
|
return VSConstants.S_OK;
|
}
|
|
int IVsPersistDocData.SetUntitledDocPath(string pszDocDataPath)
|
{
|
return VSConstants.S_OK;
|
}
|
|
int IVsPersistDocData.SaveDocData(
|
VSSAVEFLAGS dwSave,
|
out string pbstrMkDocumentNew,
|
out int pfSaveCanceled)
|
{
|
pbstrMkDocumentNew = string.Empty;
|
pfSaveCanceled = 0;
|
return VSConstants.E_NOTIMPL;
|
}
|
|
int IVsPersistDocData.OnRegisterDocData(
|
uint docCookie,
|
IVsHierarchy pHierNew,
|
uint itemidNew)
|
{
|
return VSConstants.S_OK;
|
}
|
|
int IVsPersistDocData.IsDocDataReloadable(out int pfReloadable)
|
{
|
pfReloadable = 0;
|
return VSConstants.S_OK;
|
}
|
|
int IVsPersistDocData.ReloadDocData(uint grfFlags)
|
{
|
return VSConstants.E_NOTIMPL;
|
}
|
}
|
}
|
}
|