diff options
author | Lluis Sanchez <llsan@microsoft.com> | 2019-03-20 01:31:47 +0300 |
---|---|---|
committer | Lluis Sanchez <llsan@microsoft.com> | 2019-03-20 01:31:47 +0300 |
commit | 935f47982ef658566f1b1af36acdd5ca46cdb323 (patch) | |
tree | feafb21d99ed8eda14ca390c2691cb87184daec9 /main/src | |
parent | 683355b7bbd702f60f27946ea97581302650461f (diff) | |
parent | 40d52a9e69683bf9da9aecaeb6e0a75464dd83cc (diff) |
Merge remote-tracking branch 'origin/master' into new-service-model
Diffstat (limited to 'main/src')
74 files changed, 2495 insertions, 963 deletions
diff --git a/main/src/addins/AspNet/Projects/AspNetAppProjectFlavor.cs b/main/src/addins/AspNet/Projects/AspNetAppProjectFlavor.cs index 08133edf1d..17a81a259b 100644 --- a/main/src/addins/AspNet/Projects/AspNetAppProjectFlavor.cs +++ b/main/src/addins/AspNet/Projects/AspNetAppProjectFlavor.cs @@ -60,6 +60,7 @@ namespace MonoDevelop.AspNet.Projects WebFormsRegistrationCache registrationCache; WebFormsCodeBehindTypeNameCache codebehindTypeNameCache; + public const string TypeScriptCompile = "TypeScriptCompile"; #region properties @@ -303,6 +304,8 @@ namespace MonoDevelop.AspNet.Projects return WebSubtype.Stylus; case "CSHTML": return WebSubtype.Razor; + case "TS": + return WebSubtype.TypeScript; default: return WebSubtype.None; } @@ -546,11 +549,12 @@ namespace MonoDevelop.AspNet.Projects protected override string OnGetDefaultBuildAction (string fileName) { - - WebSubtype type = DetermineWebSubtype (fileName); + var type = DetermineWebSubtype (fileName); switch (type) { case WebSubtype.Code: return BuildAction.Compile; + case WebSubtype.TypeScript: + return TypeScriptCompile; case WebSubtype.None: return base.OnGetDefaultBuildAction (fileName); default: diff --git a/main/src/addins/AspNet/Projects/WebSubtype.cs b/main/src/addins/AspNet/Projects/WebSubtype.cs index 24bd85dbb4..766578ab20 100644 --- a/main/src/addins/AspNet/Projects/WebSubtype.cs +++ b/main/src/addins/AspNet/Projects/WebSubtype.cs @@ -53,7 +53,8 @@ namespace MonoDevelop.AspNet.Projects Svg, Sass, Stylus,
- Razor + Razor,
+ TypeScript } } diff --git a/main/src/addins/AspNet/Templates/EmptyTypeScriptFile.xft.xml b/main/src/addins/AspNet/Templates/EmptyTypeScriptFile.xft.xml index ba60eb2595..30a6a4a012 100644 --- a/main/src/addins/AspNet/Templates/EmptyTypeScriptFile.xft.xml +++ b/main/src/addins/AspNet/Templates/EmptyTypeScriptFile.xft.xml @@ -14,6 +14,6 @@ </TemplateConfiguration> <TemplateFiles> - <File DefaultExtension=".ts" src="TypeScriptFileTemplate.ts" /> + <File DefaultExtension=".ts" src="TypeScriptFileTemplate.ts" BuildAction="TypeScriptCompile" /> </TemplateFiles> </Template> diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Project/CSharpCompilerParameters.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Project/CSharpCompilerParameters.cs index 3ae26b0d8c..9feb21b991 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Project/CSharpCompilerParameters.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Project/CSharpCompilerParameters.cs @@ -145,6 +145,8 @@ namespace MonoDevelop.CSharp.Project cryptoKeyFile: ParentConfiguration.SignAssembly ? ParentConfiguration.AssemblyKeyFile : null, cryptoPublicKey: ImmutableArray<byte>.Empty, platform: GetPlatform (), + publicSign: ParentConfiguration.PublicSign, + delaySign: ParentConfiguration.DelaySign, generalDiagnosticOption: TreatWarningsAsErrors ? ReportDiagnostic.Error : ReportDiagnostic.Default, warningLevel: WarningLevel, specificDiagnosticOptions: GetSpecificDiagnosticOptions (), diff --git a/main/src/addins/MacPlatform/MacInterop/Keychain.cs b/main/src/addins/MacPlatform/MacInterop/Keychain.cs index 2196fe09df..64b76c6ff1 100644 --- a/main/src/addins/MacPlatform/MacInterop/Keychain.cs +++ b/main/src/addins/MacPlatform/MacInterop/Keychain.cs @@ -3,9 +3,11 @@ // // Authors: Michael Hutchinson <mhutchinson@novell.com> // Jeffrey Stedfast <jeff@xamarin.com> +// Javier Suárez <jsuarez@microsoft.com> // // Copyright (c) 2009 Novell, Inc. (http://www.novell.com) // Copyright (c) 2013 Xamarin Inc. (http://www.xamarin.com) +// Copyright (c) 2019 Microsoft Corporation (http://www.microsoft.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -26,624 +28,232 @@ // THE SOFTWARE. using System; -using System.Text; -using System.Runtime.InteropServices; using Security; +using Foundation; namespace MonoDevelop.MacInterop { public static class Keychain { - const string CoreFoundationLib = ObjCRuntime.Constants.CoreFoundationLibrary; - const string SecurityLib = ObjCRuntime.Constants.SecurityLibrary; - - internal static IntPtr CurrentKeychain = IntPtr.Zero; - - [DllImport (CoreFoundationLib, EntryPoint="CFRelease")] - static extern void CFReleaseInternal (IntPtr cfRef); - - static void CFRelease (IntPtr cfRef) + public static void AddInternetPassword (Uri uri, string password) { - if (cfRef != IntPtr.Zero) - CFReleaseInternal (cfRef); - } - - #region Managing Keychains - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainCreate (byte[] pathName, uint passwordLength, byte[] password, - bool promptUser, IntPtr initialAccess, out IntPtr keychain); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainDelete (IntPtr keychain); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainOpen (byte[] pathName, out IntPtr keychain); + // See if there is already a password there for this uri + var record = SecKeychainFindInternetPassword (uri, out SecRecord searchRecord); - internal static IntPtr CreateKeychain (string path, string password) - { - var passwd = Encoding.UTF8.GetBytes (password); + if (record == null) { + record = uri.ToSecRecord (password: password); - var status = SecKeychainCreate (path.ToNullTerminatedUtf8 (), (uint) passwd.Length, passwd, false, IntPtr.Zero, out IntPtr result); - if (status != SecStatusCode.Success) - throw new Exception (status.GetStatusDescription ()); + SecStatusCode result = SecKeyChain.Add (record); - return result; - } + if (result != SecStatusCode.Success && result != SecStatusCode.DuplicateItem) + throw new Exception ("Could not add internet password to keychain: " + result.GetStatusDescription ()); - internal static bool TryDeleteKeychain (string path) - { - var result = SecKeychainOpen (path.ToNullTerminatedUtf8 (), out IntPtr ptr); - try { - if (result == SecStatusCode.Success) { - DeleteKeychain (ptr); - return true; - } - } catch { - // If we call 'SecKeychainOpen' on a keychain path that does not exist, - // we get a return value of 'success' and we also get a non-null handle. - // As such there's no way for the test suite to safely check if a keychain - // exists so it can delete a pre-existing one before exceuting. Work around - // this by wrapping in a try/catch. The 'DeleteKeyChain (IntPtr)' method - // will throw a 'key chain does not exist' error if there is no pre-existing - // keychain - } finally { - CFRelease (ptr); + return; } - return false; - } - internal static void DeleteKeychain (IntPtr keychain) - { - var status = SecKeychainDelete (keychain); - if (status != SecStatusCode.Success) - throw new Exception (status.GetStatusDescription ()); - } - - #endregion - - #region Storing and Retrieving Passwords - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainAddInternetPassword (IntPtr keychain, uint serverNameLength, byte[] serverName, uint securityDomainLength, - byte[] securityDomain, uint accountNameLength, byte[] accountName, uint pathLength, - byte[] path, ushort port, SecProtocolType protocol, SecAuthenticationType authType, - uint passwordLength, byte[] passwordData, ref IntPtr itemRef); - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainFindInternetPassword (IntPtr keychain, uint serverNameLength, byte[] serverName, uint securityDomainLength, - byte[] securityDomain, uint accountNameLength, byte[] accountName, uint pathLength, - byte[] path, ushort port, SecProtocolType protocol, SecAuthenticationType authType, - out uint passwordLength, out IntPtr passwordData, ref IntPtr itemRef); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainFindInternetPassword (IntPtr keychain, uint serverNameLength, byte[] serverName, uint securityDomainLength, - byte[] securityDomain, uint accountNameLength, byte[] accountName, uint pathLength, - byte[] path, ushort port, SecProtocolType protocol, SecAuthenticationType authType, - IntPtr passwordLengthRef, IntPtr passwordDataRef, ref IntPtr itemRef); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainAddGenericPassword (IntPtr keychain, uint serviceNameLength, byte[] serviceName, - uint accountNameLength, byte[] accountName, uint passwordLength, - byte[] passwordData, ref IntPtr itemRef); - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainFindGenericPassword (IntPtr keychain, uint serviceNameLength, byte[] serviceName, - uint accountNameLength, byte[] accountName, out uint passwordLength, - out IntPtr passwordData, ref IntPtr itemRef); - - #endregion - - #region Creating and Deleting Keychain Items - - [StructLayout (LayoutKind.Sequential)] - struct SecKeychainAttributeList - { - public int Count; - public IntPtr Attrs; + record.ValueData = NSData.FromString (password); - public SecKeychainAttributeList (int count, IntPtr attrs) - { - Count = count; - Attrs = attrs; - } + // If there is, replace it with the new one + SecKeyChain.Update (searchRecord, record); } - [StructLayout (LayoutKind.Sequential)] - struct SecKeychainAttribute + public static void AddInternetPassword (Uri uri, string username, string password) { - public SecItemAttr Tag; - public uint Length; - public IntPtr Data; - - public SecKeychainAttribute (SecItemAttr tag, uint length, IntPtr data) - { - Tag = tag; - Length = length; - Data = data; - } - } + // See if there is already a password there for this uri + var record = SecKeychainFindInternetPassword (uri, out SecRecord searchRecord); - [DllImport (SecurityLib)] - static extern unsafe SecStatusCode SecKeychainItemCreateFromContent (SecItemClass itemClass, SecKeychainAttributeList *attrList, - uint passwordLength, byte[] password, IntPtr keychain, - IntPtr initialAccess, IntPtr itemRef); + if (record == null) { + record = uri.ToSecRecord (username, password); - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainItemDelete (IntPtr itemRef); + SecStatusCode result = SecKeyChain.Add (record); - #endregion + if (result != SecStatusCode.Success && result != SecStatusCode.DuplicateItem) + throw new Exception ("Could not add internet password to keychain: " + result.GetStatusDescription ()); - #region Managing Keychain Items + return; + } - [StructLayout (LayoutKind.Sequential)] - unsafe struct SecKeychainAttributeInfo - { - public uint Count; - public int* Tag; - public int* Format; + // If there is, replace it with the new one + var update = uri.ToSecRecord (username, password); + SecKeyChain.Update (record, update); } - [DllImport (SecurityLib)] - static extern unsafe SecStatusCode SecKeychainItemFreeAttributesAndData (SecKeychainAttributeList* list, IntPtr data); - - [DllImport (SecurityLib)] - static extern unsafe SecStatusCode SecKeychainItemCopyAttributesAndData (IntPtr itemRef, SecKeychainAttributeInfo* info, ref SecItemClass itemClass, - SecKeychainAttributeList** attrList, IntPtr lengthRef, IntPtr outDataRef); - - [DllImport (SecurityLib)] - static extern unsafe SecStatusCode SecKeychainItemModifyAttributesAndData (IntPtr itemRef, SecKeychainAttributeList *attrList, uint length, byte [] data); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainItemCopyContent (IntPtr itemRef, ref SecItemClass itemClass, IntPtr attrList, ref uint length, ref IntPtr data); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainItemFreeContent (IntPtr attrList, IntPtr data); - - #endregion - - static SecAuthenticationType GetSecAuthenticationType (string query) + public static string FindInternetPassword (Uri url) { - if (string.IsNullOrEmpty (query)) - return SecAuthenticationType.Any; + // See if there is already a password there for this uri + var record = SecKeychainFindInternetPassword (url, out _); - string auth = "default"; - foreach (var pair in query.Substring (1).Split (new char[] { '&' })) { - var kvp = pair.Split (new char[] { '=' }); - if (string.Equals (kvp[0], "auth", StringComparison.InvariantCultureIgnoreCase) && kvp.Length == 2) { - auth = kvp[1].ToLowerInvariant (); - break; - } - } + if (record != null) + return NSString.FromData (record.ValueData, NSStringEncoding.UTF8); - switch (auth) { - case "ntlm": return SecAuthenticationType.NTLM; - case "msn": return SecAuthenticationType.MSN; - case "dpa": return SecAuthenticationType.DPA; - case "rpa": return SecAuthenticationType.RPA; - case "httpbasic": case "basic": return SecAuthenticationType.HTTPBasic; - case "httpdigest": case "digest": return SecAuthenticationType.HTTPDigest; - case "htmlform": case "form": return SecAuthenticationType.HTMLForm; - case "default": return SecAuthenticationType.Default; - default: return SecAuthenticationType.Any; - } + return null; } - static SecProtocolType GetSecProtocolType (string protocol) + public static unsafe Tuple<string, string> FindInternetUserNameAndPassword (Uri uri) { - switch (protocol.ToLowerInvariant ()) { - case "ftp": return SecProtocolType.FTP; - case "ftpaccount": return SecProtocolType.FTPAccount; - case "http": return SecProtocolType.HTTP; - case "irc": return SecProtocolType.IRC; - case "nntp": return SecProtocolType.NNTP; - case "pop3": return SecProtocolType.POP3; - case "smtp": return SecProtocolType.SMTP; - case "socks": return SecProtocolType.SOCKS; - case "imap": return SecProtocolType.IMAP; - case "ldap": return SecProtocolType.LDAP; - case "appletalk": return SecProtocolType.AppleTalk; - case "afp": return SecProtocolType.AFP; - case "telnet": return SecProtocolType.Telnet; - case "ssh": return SecProtocolType.SSH; - case "ftps": return SecProtocolType.FTPS; - case "httpproxy": return SecProtocolType.HTTPProxy; - case "httpsproxy": return SecProtocolType.HTTPSProxy; - case "ftpproxy": return SecProtocolType.FTPProxy; - case "cifs": return SecProtocolType.CIFS; - case "smb": return SecProtocolType.SMB; - case "rtsp": return SecProtocolType.RTSP; - case "rtspproxy": return SecProtocolType.RTSPProxy; - case "daap": return SecProtocolType.DAAP; - case "eppc": return SecProtocolType.EPPC; - case "ipp": return SecProtocolType.IPP; - case "nntps": return SecProtocolType.NNTPS; - case "ldaps": return SecProtocolType.LDAPS; - case "telnets": return SecProtocolType.TelnetS; - case "imaps": return SecProtocolType.IMAPS; - case "ircs": return SecProtocolType.IRCS; - case "pop3s": return SecProtocolType.POP3S; - case "cvspserver": return SecProtocolType.CVSpserver; - case "svn": return SecProtocolType.SVN; - default: return SecProtocolType.Any; - } + return FindInternetUserNameAndPassword (uri, GetSecProtocolType (uri.Scheme)); } - static unsafe SecStatusCode ReplaceInternetPassword (IntPtr item, byte[] desc, byte[] passwd) + public static Tuple<string, string> FindInternetUserNameAndPassword (Uri url, SecProtocol protocol) { - fixed (byte* descPtr = desc) { - SecKeychainAttribute* attrs = stackalloc SecKeychainAttribute [1]; - int n = 0; + var record = SecKeychainFindInternetPassword (url, protocol, out _); - if (desc != null) - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Description, (uint) desc.Length, (IntPtr) descPtr); + if (record != null) { - SecKeychainAttributeList attrList = new SecKeychainAttributeList (n, (IntPtr) attrs); + string username = record.Account != null ? NSString.FromData (record.Account, NSStringEncoding.UTF8) : null; + string password = record.ValueData != null ? NSString.FromData (record.ValueData, NSStringEncoding.UTF8) : null; - return SecKeychainItemModifyAttributesAndData (item, &attrList, (uint) passwd.Length, passwd); + return Tuple.Create (username, password); } + return null; } - static unsafe SecStatusCode AddInternetPassword (byte[] label, byte[] desc, SecAuthenticationType auth, byte[] user, byte[] passwd, SecProtocolType protocol, byte[] host, int port, byte[] path) + public static void RemoveInternetPassword (Uri url) { - // Note: the following code does more-or-less the same as: - //SecKeychainAddInternetPassword (CurrentKeychain, (uint) host.Length, host, 0, null, - // (uint) user.Length, user, (uint) path.Length, path, (ushort) port, - // protocol, auth, (uint) passwd.Length, passwd, ref item); - - fixed (byte* labelPtr = label, descPtr = desc, userPtr = user, hostPtr = host, pathPtr = path) { - SecKeychainAttribute* attrs = stackalloc SecKeychainAttribute [8]; - int* protoPtr = (int*) &protocol; - int* authPtr = (int*) &auth; - int* portPtr = &port; - int n = 0; - - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Label, (uint) label.Length, (IntPtr) labelPtr); - if (desc != null) - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Description, (uint) desc.Length, (IntPtr) descPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Account, (uint) user.Length, (IntPtr) userPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Protocol, (uint) 4, (IntPtr) protoPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.AuthType, (uint) 4, (IntPtr) authPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Server, (uint) host.Length, (IntPtr) hostPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Port, (uint) 4, (IntPtr) portPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Path, (uint) Math.Max (path.Length - 1, 0), (IntPtr) pathPtr); - - SecKeychainAttributeList attrList = new SecKeychainAttributeList (n, (IntPtr) attrs); - - var result = SecKeychainItemCreateFromContent (SecItemClass.InternetPassword, &attrList, (uint) passwd.Length, passwd, CurrentKeychain, IntPtr.Zero, IntPtr.Zero); - - return result; - } - } - - public static unsafe void AddInternetPassword (Uri uri, string username, string password) - { - byte[] path = uri.ToPathBytes (); - byte[] passwd = Encoding.UTF8.GetBytes (password); - byte[] host = Encoding.UTF8.GetBytes (uri.Host); - byte[] user = Encoding.UTF8.GetBytes (username); - var auth = GetSecAuthenticationType (uri.Query); - var protocol = GetSecProtocolType (uri.Scheme); - IntPtr item = IntPtr.Zero; - int port = uri.Port; - byte[] desc = null; + var record = SecKeychainFindInternetPassword (url, out _); - if (auth == SecAuthenticationType.HTMLForm) - desc = WebFormPassword; + if (record != null) { + var result = SecKeyChain.Remove (record); - // See if there is already a password there for this uri - var result = SecKeychainFindInternetPassword (CurrentKeychain, (uint)host.Length, host, 0, null, - (uint)user.Length, user, (uint)path.Length, path, (ushort)port, - protocol, auth, IntPtr.Zero, IntPtr.Zero, ref item); - - try { - if (result == SecStatusCode.Success) { - // If there is, replace it with the new one - result = ReplaceInternetPassword (item, desc, passwd); - } else { - var label = Encoding.UTF8.GetBytes (string.Format ("{0} ({1})", uri.Host, username)); - - result = AddInternetPassword (label, desc, auth, user, passwd, protocol, host, port, path); - } - } finally { - CFRelease (item); + if (result != SecStatusCode.Success) + throw new Exception ("Could not delete internet password from keychain: " + result.GetStatusDescription ()); } - - if (result != SecStatusCode.Success && result != SecStatusCode.DuplicateItem) - throw new Exception ("Could not add internet password to keychain: " + result.GetStatusDescription ()); } - static readonly byte[] WebFormPassword = Encoding.UTF8.GetBytes ("Web form password"); - - public static unsafe void AddInternetPassword (Uri uri, string password) => - AddInternetPassword (uri, Uri.UnescapeDataString (uri.UserInfo), password); - - static unsafe string GetUsernameFromKeychainItemRef (IntPtr itemRef) + public static void RemoveInternetUserNameAndPassword (Uri url) { - int[] formatConstants = { (int) CssmDbAttributeFormat.String }; - int[] attributeTags = { (int) SecItemAttr.Account }; + var record = SecKeychainFindInternetPassword (url, out _); - fixed (int* tags = attributeTags, formats = formatConstants) { - var attributeInfo = new SecKeychainAttributeInfo { - Count = 1, - Tag = tags, - Format = formats - }; - SecKeychainAttributeList* attributeList = null; - SecItemClass itemClass = 0; + if (record != null) { + SecStatusCode result = SecKeyChain.Remove (record); - try { - SecStatusCode status = SecKeychainItemCopyAttributesAndData (itemRef, &attributeInfo, ref itemClass, &attributeList, IntPtr.Zero, IntPtr.Zero); - - if (status == SecStatusCode.ItemNotFound) - throw new Exception ("Could not add internet password to keychain: " + status.GetStatusDescription ()); - - if (status != SecStatusCode.Success) - throw new Exception ("Could not find internet username and password: " + status.GetStatusDescription ()); - - var userNameAttr = (SecKeychainAttribute*)attributeList->Attrs; - - if (userNameAttr->Length == 0) - return null; - - return Marshal.PtrToStringAuto (userNameAttr->Data, (int)userNameAttr->Length); - } finally { - SecKeychainItemFreeAttributesAndData (attributeList, IntPtr.Zero); - } + if (result != SecStatusCode.Success) + throw new Exception ("Could not delete internet password from keychain: " + result.GetStatusDescription ()); } } - public static unsafe Tuple<string, string> FindInternetUserNameAndPassword (Uri uri) + static SecRecord SecKeychainFindInternetPassword (Uri uri, out SecRecord searchRecord) { - var protocol = GetSecProtocolType (uri.Scheme); - return FindInternetUserNameAndPassword (uri, protocol); + return SecKeychainFindInternetPassword (uri, GetSecProtocolType (uri.Scheme), out searchRecord); } - public static unsafe Tuple<string, string> FindInternetUserNameAndPassword (Uri uri, SecProtocolType protocol) + static SecRecord SecKeychainFindInternetPassword (Uri uri, SecProtocol protocol, out SecRecord searchRecord) { - byte[] path = uri.ToPathBytes (); - byte[] host = Encoding.UTF8.GetBytes (uri.Host); - var auth = GetSecAuthenticationType (uri.Query); - IntPtr passwordData; - IntPtr item = IntPtr.Zero; - uint passwordLength = 0; - - var result = SecKeychainFindInternetPassword ( - CurrentKeychain, (uint) host.Length, host, 0, null, - 0, null, (uint) path.Length, path, (ushort) uri.Port, - protocol, auth, out passwordLength, out passwordData, ref item); - - try { - if (result != SecStatusCode.Success) - return null; + // Look for an internet password for the given protocol and auth mechanism + searchRecord = uri.ToSecRecord (); + if (protocol != SecProtocol.Invalid) + searchRecord.Protocol = protocol; - var username = GetUsernameFromKeychainItemRef (item); - return Tuple.Create (username, Marshal.PtrToStringAuto (passwordData, (int) passwordLength)); - } finally { - SecKeychainItemFreeContent (IntPtr.Zero, passwordData); - CFRelease (item); - } - } + var data = SecKeyChain.QueryAsRecord (searchRecord, out SecStatusCode code); - public static string FindInternetPassword (Uri uri) - { - byte[] path = uri.ToPathBytes (); - byte[] user = Encoding.UTF8.GetBytes (Uri.UnescapeDataString (uri.UserInfo)); - byte[] host = Encoding.UTF8.GetBytes (uri.Host); - var auth = GetSecAuthenticationType (uri.Query); - var protocol = GetSecProtocolType (uri.Scheme); - IntPtr passwordData = IntPtr.Zero; - IntPtr item = IntPtr.Zero; - uint passwordLength = 0; - - try { - // Look for an internet password for the given protocol and auth mechanism - var result = SecKeychainFindInternetPassword (CurrentKeychain, (uint)host.Length, host, 0, null, - (uint)user.Length, user, (uint)path.Length, path, (ushort)uri.Port, - protocol, auth, out passwordLength, out passwordData, ref item); - - // Fall back to looking for a password for SecProtocolType.Any && SecAuthenticationType.Any - if (result == SecStatusCode.ItemNotFound && protocol != SecProtocolType.Any) - result = SecKeychainFindInternetPassword (CurrentKeychain, (uint)host.Length, host, 0, null, - (uint)user.Length, user, (uint)path.Length, path, (ushort)uri.Port, - 0, auth, out passwordLength, out passwordData, ref item); + if (code == SecStatusCode.ItemNotFound) { + // Fall back to looking for a password without use SecProtocol && SecAuthenticationType + searchRecord.Protocol = SecProtocol.Http; // Http is the default used by SecKeyChain internally + searchRecord.AuthenticationType = SecAuthenticationType.Default; - if (result != SecStatusCode.Success) - return null; + data = SecKeyChain.QueryAsRecord (searchRecord, out code); + } - return Marshal.PtrToStringAuto (passwordData, (int)passwordLength); - } finally { - CFRelease (item); + if (code != SecStatusCode.Success) + return null; - SecKeychainItemFreeContent (IntPtr.Zero, passwordData); - } + return data; } - public static void RemoveInternetPassword (Uri uri) + static readonly string WebFormPassword = "Web form password"; + + static SecRecord ToSecRecord (this Uri uri, string username = null, string password = null) { - byte[] path = uri.ToPathBytes (); - byte[] user = Encoding.UTF8.GetBytes (Uri.UnescapeDataString (uri.UserInfo)); - byte[] host = Encoding.UTF8.GetBytes (uri.Host); - var auth = GetSecAuthenticationType (uri.Query); + var record = new SecRecord (SecKind.InternetPassword) { + Server = uri.Host, + Path = string.Join (string.Empty, uri.Segments), + Port = uri.Port, + }; var protocol = GetSecProtocolType (uri.Scheme); - IntPtr item = IntPtr.Zero; + if (protocol != SecProtocol.Invalid) + record.Protocol = protocol; + var authType = GetSecAuthenticationType (uri.Query); + if (authType != SecAuthenticationType.Default) + record.AuthenticationType = authType; - // Look for an internet password for the given protocol and auth mechanism - var result = SecKeychainFindInternetPassword (CurrentKeychain, (uint) host.Length, host, 0, null, - (uint) user.Length, user, (uint) path.Length, path, (ushort) uri.Port, - protocol, auth, IntPtr.Zero, IntPtr.Zero, ref item); + if (record.AuthenticationType == SecAuthenticationType.HtmlForm) + record.Description = WebFormPassword; - // Fall back to looking for a password for SecProtocolType.Any && SecAuthenticationType.Any - if (result == SecStatusCode.ItemNotFound && protocol != SecProtocolType.Any) - result = SecKeychainFindInternetPassword (CurrentKeychain, (uint) host.Length, host, 0, null, - (uint) user.Length, user, (uint) path.Length, path, (ushort) uri.Port, - 0, auth, IntPtr.Zero, IntPtr.Zero, ref item); + var account = Uri.UnescapeDataString (uri.UserInfo); + if (string.IsNullOrEmpty (account)) // account from Uri has always priority + account = username; - try { - if (result != SecStatusCode.Success) - return; + if (!string.IsNullOrEmpty (account)) + record.Account = account; - SecKeychainItemDelete (item); - } finally { - CFRelease (item); - } + if (password != null) + record.ValueData = NSData.FromString (password); + + return record; } - public static void RemoveInternetUserNameAndPassword (Uri uri) + static SecProtocol GetSecProtocolType (string protocol) { - byte[] path = uri.ToPathBytes (); - byte[] host = Encoding.UTF8.GetBytes (uri.Host); - var auth = GetSecAuthenticationType (uri.Query); - IntPtr item = IntPtr.Zero; - - var result = SecKeychainFindInternetPassword ( - CurrentKeychain, (uint) host.Length, host, 0, null, - 0, null, (uint) path.Length, path, (ushort) uri.Port, - GetSecProtocolType (uri.Scheme), auth, IntPtr.Zero, IntPtr.Zero, ref item); - - try { - if (result != SecStatusCode.Success) - return; - - result = SecKeychainItemDelete (item); - } finally { - CFRelease (item); + switch (protocol.ToLowerInvariant ()) { + case "ftp": return SecProtocol.Ftp; + case "ftpaccount": return SecProtocol.FtpAccount; + case "http": return SecProtocol.Http; + case "https": return SecProtocol.Https; + case "irc": return SecProtocol.Irc; + case "nntp": return SecProtocol.Nntp; + case "pop3": return SecProtocol.Pop3; + case "pop3s": return SecProtocol.Pop3s; + case "smtp": return SecProtocol.Smtp; + case "socks": return SecProtocol.Socks; + case "imap": return SecProtocol.Imap; + case "imaps": return SecProtocol.Imaps; + case "ldap": return SecProtocol.Ldap; + case "ldaps": return SecProtocol.Ldaps; + case "appletalk": return SecProtocol.AppleTalk; + case "afp": return SecProtocol.Afp; + case "telnet": return SecProtocol.Telnet; + case "ssh": return SecProtocol.Ssh; + case "ftps": return SecProtocol.Ftps; + case "httpproxy": return SecProtocol.HttpProxy; + case "httpsproxy": return SecProtocol.HttpProxy; + case "ftpproxy": return SecProtocol.FtpProxy; + case "smb": return SecProtocol.Smb; + case "rtsp": return SecProtocol.Rtsp; + case "rtspproxy": return SecProtocol.RtspProxy; + case "daap": return SecProtocol.Daap; + case "eppc": return SecProtocol.Eppc; + case "ipp": return SecProtocol.Ipp; + case "nntps": return SecProtocol.Nntps; + case "telnets": return SecProtocol.Telnets; + case "ircs": return SecProtocol.Ircs; + default: return SecProtocol.Invalid; } } - static byte [] ToPathBytes (this Uri uri) + static SecAuthenticationType GetSecAuthenticationType (string query) { - var pathStr = string.Join (string.Empty, uri.Segments); - byte[] path = pathStr.Length > 0 ? pathStr.ToNullTerminatedUtf8 (1) : Array.Empty<byte> (); // don't include the leading '/' - return path; - } + if (string.IsNullOrEmpty (query)) + return SecAuthenticationType.Default; - // It seems that keychain APIs require null terminated native strings. - internal static byte [] ToNullTerminatedUtf8 (this string str, int offset = 0) - { - unsafe { - fixed (char* p = str) { - return ToNullTerminatedUtf8 (p + offset, str.Length - offset); + string auth = "default"; + foreach (var pair in query.Substring (1).Split (new char [] { '&' })) { + var kvp = pair.Split (new char [] { '=' }); + if (string.Equals (kvp [0], "auth", StringComparison.InvariantCultureIgnoreCase) && kvp.Length == 2) { + auth = kvp [1].ToLowerInvariant (); + break; } } - } - - static unsafe byte [] ToNullTerminatedUtf8 (char* p, int len) - { - if (len == 0) - return Array.Empty<byte> (); - var byteCount = Encoding.UTF8.GetByteCount (p, len); - var bytes = new byte [byteCount + 1]; - fixed (byte* b = bytes) { - Encoding.UTF8.GetBytes (p, len, b, byteCount); + switch (auth) { + case "ntlm": return SecAuthenticationType.Ntlm; + case "msn": return SecAuthenticationType.Msn; + case "dpa": return SecAuthenticationType.Dpa; + case "rpa": return SecAuthenticationType.Rpa; + case "httpbasic": case "basic": return SecAuthenticationType.HttpBasic; + case "httpdigest": case "digest": return SecAuthenticationType.HttpDigest; + case "htmlform": case "form": return SecAuthenticationType.HtmlForm; + default: return SecAuthenticationType.Default; } - return bytes; } } - - enum SecItemClass : uint - { - InternetPassword = 1768842612, // 'inet' - GenericPassword = 1734700656, // 'genp' - AppleSharePassword = 1634953328, // 'ashp' - Certificate = 0x80000000 + 0x1000, - PublicKey = 0x0000000A + 5, - PrivateKey = 0x0000000A + 6, - SymmetricKey = 0x0000000A + 7 - } - - enum SecItemAttr : int - { - CreationDate = 1667522932, - ModDate = 1835295092, - Description = 1684370275, - Comment = 1768123764, - Creator = 1668445298, - Type = 1954115685, - ScriptCode = 1935897200, - Label = 1818321516, - Invisible = 1768846953, - Negative = 1852139361, - CustomIcon = 1668641641, - Account = 1633903476, - Service = 1937138533, - Generic = 1734700641, - SecurityDomain = 1935961454, - Server = 1936881266, - AuthType = 1635023216, - Port = 1886351988, - Path = 1885434984, - Volume = 1986817381, - Address = 1633969266, - Signature = 1936943463, - Protocol = 1886675820, - CertificateType = 1668577648, - CertificateEncoding = 1667591779, - CrlType = 1668445296, - CrlEncoding = 1668443747, - Alias = 1634494835, - } - - enum SecAuthenticationType : int - { - NTLM = 1835824238, - MSN = 1634628461, - DPA = 1633775716, - RPA = 1633775730, - HTTPBasic = 1886680168, - HTTPDigest = 1685353576, - HTMLForm = 1836216166, - Default = 1953261156, - Any = 0 - } - - public enum SecProtocolType : int - { - FTP = 1718906912, - FTPAccount = 1718906977, - HTTP = 1752462448, - IRC = 1769104160, - NNTP = 1852732528, - POP3 = 1886351411, - SMTP = 1936553072, - SOCKS = 1936685088, - IMAP = 1768776048, - LDAP = 1818517872, - AppleTalk = 1635019883, - AFP = 1634103328, - Telnet = 1952803950, - SSH = 1936943136, - FTPS = 1718906995, - HTTPProxy = 1752461432, - HTTPSProxy = 1752462200, - FTPProxy = 1718907000, - CIFS = 1667851891, - SMB = 1936548384, - RTSP = 1920234352, - RTSPProxy = 1920234360, - DAAP = 1684103536, - EPPC = 1701867619, - IPP = 1768976416, - NNTPS = 1853124723, - LDAPS = 1818521715, - TelnetS = 1952803955, - IMAPS = 1768779891, - IRCS = 1769104243, - POP3S = 1886351475, - CVSpserver = 1668707184, - SVN = 1937141280, - Any = 0 - } - - enum CssmDbAttributeFormat : int - { - String = 0, - Int32 = 1, - UInt32 = 2, - BigNum = 3, - Real = 4, - DateTime = 5, - Blob = 6, - MultiUInt32 = 7, - Complex = 8 - } -} +}
\ No newline at end of file diff --git a/main/src/addins/MacPlatform/MacPlatform.cs b/main/src/addins/MacPlatform/MacPlatform.cs index a5a54ba328..6dfb61f82f 100644 --- a/main/src/addins/MacPlatform/MacPlatform.cs +++ b/main/src/addins/MacPlatform/MacPlatform.cs @@ -57,6 +57,7 @@ using Xwt.Mac; using MonoDevelop.Components.Mac; using System.Reflection; using MacPlatform; +using MonoDevelop.Projects; namespace MonoDevelop.MacIntegration { @@ -582,6 +583,14 @@ namespace MonoDevelop.MacIntegration // Gtk.Rc.ParseString (gtkrc); } + static TimeToCodeMetadata.DocumentType GetDocumentTypeFromFilename (string filename) + { + if (Projects.Services.ProjectService.IsWorkspaceItemFile (filename) || Projects.Services.ProjectService.IsSolutionItemFile (filename)) { + return TimeToCodeMetadata.DocumentType.Solution; + } + return TimeToCodeMetadata.DocumentType.File; + } + void GlobalSetup () { //FIXME: should we remove these when finalizing? @@ -628,21 +637,30 @@ namespace MonoDevelop.MacIntegration }; ApplicationEvents.OpenDocuments += delegate (object sender, ApplicationDocumentEventArgs e) { - //OpenFiles may pump the mainloop, but can't do that from an AppleEvent, so use a brief timeout - GLib.Timeout.Add (0, delegate { + //OpenFiles may pump the mainloop, but can't do that from an AppleEvent + GLib.Idle.Add (delegate { Ide.WelcomePage.WelcomePageService.HideWelcomePageOrWindow (); - IdeApp.ReportTimeToCode = true; + var trackTTC = IdeApp.StartTimeToCodeLoadTimer (); IdeApp.OpenFiles (e.Documents.Select ( - doc => new FileOpenInformation (doc.Key, null, doc.Value, 1, OpenDocumentOptions.DefaultInternal)) - ); + doc => new FileOpenInformation (doc.Key, null, doc.Value, 1, OpenDocumentOptions.DefaultInternal)), + null + ).ContinueWith ((result) => { + if (!trackTTC) { + return; + } + + var firstFile = e.Documents.First ().Key; + + IdeApp.TrackTimeToCode (GetDocumentTypeFromFilename (firstFile)); + }); return false; }); e.Handled = true; }; ApplicationEvents.OpenUrls += delegate (object sender, ApplicationUrlEventArgs e) { - GLib.Timeout.Add (0, delegate { - IdeApp.ReportTimeToCode = true; + GLib.Idle.Add (delegate { + var trackTTC = IdeApp.StartTimeToCodeLoadTimer (); // Open files via the monodevelop:// URI scheme, compatible with the // common TextMate scheme: http://blog.macromates.com/2007/the-textmate-url-scheme/ IdeApp.OpenFiles (e.Urls.Select (url => { @@ -666,7 +684,14 @@ namespace MonoDevelop.MacIntegration LoggingService.LogError ("Invalid TextMate URI: " + url, ex); return null; } - }).Where (foi => foi != null)); + }).Where (foi => foi != null), null).ContinueWith ((result) => { + if (!trackTTC) { + return; + } + var firstFile = e.Urls.First (); + + IdeApp.TrackTimeToCode (GetDocumentTypeFromFilename (firstFile)); + }); return false; }); }; @@ -970,7 +995,7 @@ namespace MonoDevelop.MacIntegration public override Window GetParentForModalWindow () { - return NSApplication.SharedApplication.KeyWindow ?? NSApplication.SharedApplication.MainWindow; + return NSApplication.SharedApplication.ModalWindow ?? NSApplication.SharedApplication.KeyWindow ?? NSApplication.SharedApplication.MainWindow; } public override Window GetFocusedTopLevelWindow () diff --git a/main/src/addins/MacPlatform/MacProxyCredentialProvider.cs b/main/src/addins/MacPlatform/MacProxyCredentialProvider.cs index c8a49b7cba..ad74568afa 100644 --- a/main/src/addins/MacPlatform/MacProxyCredentialProvider.cs +++ b/main/src/addins/MacPlatform/MacProxyCredentialProvider.cs @@ -34,6 +34,7 @@ using AppKit; using CoreGraphics; using Foundation; using MonoDevelop.MacInterop; +using Security; namespace MonoDevelop.MacIntegration { @@ -65,11 +66,11 @@ namespace MonoDevelop.MacIntegration static ICredentials GetSystemProxyCredentials (Uri uri) { - var kind = SecProtocolType.Any; + var kind = SecProtocol.Http; if (uri.Scheme == "http") - kind = SecProtocolType.HTTPProxy; + kind = SecProtocol.HttpProxy; else if (uri.Scheme == "https") - kind = SecProtocolType.HTTPSProxy; + kind = SecProtocol.HttpsProxy; //TODO: get username from SystemConfiguration APIs so we don't trigger a double auth prompt var existing = Keychain.FindInternetUserNameAndPassword (uri, kind); diff --git a/main/src/addins/MonoDevelop.AspNetCore/MonoDevelop.AspNetCore/AspNetCoreProjectExtension.cs b/main/src/addins/MonoDevelop.AspNetCore/MonoDevelop.AspNetCore/AspNetCoreProjectExtension.cs index 4347eb730d..0abaff837d 100644 --- a/main/src/addins/MonoDevelop.AspNetCore/MonoDevelop.AspNetCore/AspNetCoreProjectExtension.cs +++ b/main/src/addins/MonoDevelop.AspNetCore/MonoDevelop.AspNetCore/AspNetCoreProjectExtension.cs @@ -24,10 +24,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; +using System.Linq; using System.Threading.Tasks; using MonoDevelop.Core; using MonoDevelop.Core.Execution; using MonoDevelop.DotNetCore; +using MonoDevelop.Ide; using MonoDevelop.Projects; namespace MonoDevelop.AspNetCore @@ -35,6 +37,8 @@ namespace MonoDevelop.AspNetCore [ExportProjectModelExtension] class AspNetCoreProjectExtension : DotNetCoreProjectExtension { + public const string TypeScriptCompile = "TypeScriptCompile"; + protected override ProjectRunConfiguration OnCreateRunConfiguration (string name) { return new AspNetCoreRunConfiguration (name); @@ -96,5 +100,13 @@ namespace MonoDevelop.AspNetCore } await base.OnExecute (monitor, context, configuration, runConfiguration); } + + protected override string OnGetDefaultBuildAction (string fileName) + { + if (DesktopService.GetMimeTypeInheritanceChainForFile (fileName).Contains ("text/x-typescript")) + return TypeScriptCompile; + + return base.OnGetDefaultBuildAction (fileName); + } } } diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Visualizer/TextVisualizer.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Visualizer/TextVisualizer.cs index 64b1153aea..ef9bda47f8 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Visualizer/TextVisualizer.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Visualizer/TextVisualizer.cs @@ -112,7 +112,7 @@ namespace MonoDevelop.Debugger.Visualizer if (value.TypeName == "string") { rawString = value.GetRawValue (options) as RawValueString; - length = rawString.Length; + length = rawString?.Length ?? 0; offset = 0; if (length > 0) { diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebugCommands.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebugCommands.cs index 27259be283..c508b4c654 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebugCommands.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebugCommands.cs @@ -444,6 +444,7 @@ namespace MonoDevelop.Debugger protected override void Update (CommandInfo info) { info.Visible = DebuggingService.IsFeatureSupported (DebuggerFeatures.Disassembly); + info.Enabled = IdeApp.Workspace.IsOpen; } } @@ -497,7 +498,7 @@ namespace MonoDevelop.Debugger protected override void Update (CommandInfo info) { info.Visible = DebuggingService.IsFeatureSupported (DebuggerFeatures.Breakpoints); - info.Enabled = !DebuggingService.Breakpoints.IsReadOnly; + info.Enabled = !DebuggingService.Breakpoints.IsReadOnly && IdeApp.Workspace.IsOpen; } } @@ -517,7 +518,7 @@ namespace MonoDevelop.Debugger protected override void Update (CommandInfo info) { info.Visible = DebuggingService.IsFeatureSupported (DebuggerFeatures.Breakpoints); - info.Enabled = !DebuggingService.Breakpoints.IsReadOnly; + info.Enabled = !DebuggingService.Breakpoints.IsReadOnly && IdeApp.Workspace.IsOpen; } } @@ -537,7 +538,7 @@ namespace MonoDevelop.Debugger protected override void Update (CommandInfo info) { info.Visible = DebuggingService.IsFeatureSupported (DebuggerFeatures.Catchpoints); - info.Enabled = !DebuggingService.Breakpoints.IsReadOnly; + info.Enabled = !DebuggingService.Breakpoints.IsReadOnly && IdeApp.Workspace.IsOpen; } } @@ -550,6 +551,11 @@ namespace MonoDevelop.Debugger breakpointsPad.BringToFront (); } } + + protected override void Update (CommandInfo info) + { + info.Enabled = IdeApp.Workspace.IsOpen; + } } class RunToCursorHandler : CommandHandler diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs index 456df5df01..b4ac8469c8 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs @@ -1,4 +1,4 @@ -// +// // ExceptionCaughtDialog.cs // // Authors: Lluis Sanchez Gual <lluis@novell.com> @@ -60,7 +60,7 @@ namespace MonoDevelop.Debugger protected Label ExceptionMessageLabel { get; private set; } - protected Label ExceptionHelpLinkLabel { get; private set; } + protected Button ExceptionHelpLinkButton { get; private set; } protected Label ExceptionTypeLabel { get; private set; } @@ -105,8 +105,8 @@ namespace MonoDevelop.Debugger ExceptionTypeLabel = new Label { Xalign = 0.0f, Selectable = true, CanFocus = false }; ExceptionMessageLabel = new Label { Wrap = true, Xalign = 0.0f, Selectable = true, CanFocus = false }; - ExceptionHelpLinkLabel = new Label { Wrap = true, Xalign = 0.0f, Selectable = true, CanFocus = false, UseMarkup = true, LineWrapMode = Pango.WrapMode.Char }; - ExceptionHelpLinkLabel.Name = "exception_help_link_label"; + ExceptionHelpLinkButton = new Button { HasFocus = true, Xalign = 0, Relief = ReliefStyle.None, BorderWidth = 0 }; + ExceptionHelpLinkButton.Name = "exception_help_link_label"; Gtk.Rc.ParseString (@"style ""exception-help-link-label"" { GtkWidget::link-color = ""#ffffff"" @@ -114,23 +114,26 @@ namespace MonoDevelop.Debugger } widget ""*.exception_help_link_label"" style ""exception-help-link-label"" "); - var textColor = Styles.ExceptionCaughtDialog.HeaderTextColor.ToGdkColor (); + var headerColor = Styles.ExceptionCaughtDialog.HeaderBackgroundColor.ToGdkColor (); + + ExceptionHelpLinkButton.ModifyBg (StateType.Selected, headerColor); + + ExceptionHelpLinkButton.Clicked += ExceptionHelpLinkLabel_Clicked; + ExceptionHelpLinkButton.KeyPressEvent += EventBoxLink_KeyPressEvent; - ExceptionHelpLinkLabel.ModifyBase (StateType.Prelight, Styles.ExceptionCaughtDialog.HeaderBackgroundColor.ToGdkColor ()); - ExceptionHelpLinkLabel.SetLinkHandler ((str) => IdeServices.DesktopService.ShowUrl (str)); ExceptionTypeLabel.ModifyFg (StateType.Normal, textColor); ExceptionMessageLabel.ModifyFg (StateType.Normal, textColor); - ExceptionHelpLinkLabel.ModifyFg (StateType.Normal, textColor); + ExceptionHelpLinkButton.ModifyFg (StateType.Normal, textColor); if (Platform.IsWindows) { ExceptionTypeLabel.ModifyFont (Pango.FontDescription.FromString ("bold 19")); ExceptionMessageLabel.ModifyFont (Pango.FontDescription.FromString ("10")); - ExceptionHelpLinkLabel.ModifyFont (Pango.FontDescription.FromString ("10")); + ExceptionHelpLinkButton.ModifyFont (Pango.FontDescription.FromString ("10")); } else { ExceptionTypeLabel.ModifyFont (Pango.FontDescription.FromString ("21")); ExceptionMessageLabel.ModifyFont (Pango.FontDescription.FromString ("12")); - ExceptionHelpLinkLabel.ModifyFont (Pango.FontDescription.FromString ("12")); + ExceptionHelpLinkButton.ModifyFont (Pango.FontDescription.FromString ("12")); } //Force rendering of background with EventBox @@ -141,8 +144,13 @@ widget ""*.exception_help_link_label"" style ""exception-help-link-label"" leftVBox.PackStart (icon, false, false, (uint)(Platform.IsWindows ? 5 : 0)); // as we change frame.BorderWidth below, we need to compensate rightVBox.PackStart (ExceptionTypeLabel, false, false, (uint)(Platform.IsWindows ? 0 : 2)); + + var exceptionHContainer = new HBox (); + exceptionHContainer.PackStart (ExceptionHelpLinkButton, false, false, 0); + exceptionHContainer.PackStart (new Fixed (), true, true, 0); + rightVBox.PackStart (ExceptionMessageLabel, true, true, (uint)(Platform.IsWindows ? 6 : 5)); - rightVBox.PackStart (ExceptionHelpLinkLabel, false, false, 2); + rightVBox.PackStart (exceptionHContainer, false, false, 2); hBox.PackStart (leftVBox, false, false, (uint)(Platform.IsWindows ? 5 : 0)); // as we change frame.BorderWidth below, we need to compensate hBox.PackStart (rightVBox, true, true, (uint)(Platform.IsWindows ? 5 : 10)); @@ -155,19 +163,27 @@ widget ""*.exception_help_link_label"" style ""exception-help-link-label"" eventBox.Add (frame); eventBox.ShowAll (); - eventBox.ModifyBg (StateType.Normal, Styles.ExceptionCaughtDialog.HeaderBackgroundColor.ToGdkColor ()); + eventBox.ModifyBg (StateType.Normal, headerColor); return eventBox; } + void ExceptionHelpLinkLabel_Clicked (object sender, EventArgs e) => DesktopService.ShowUrl (exceptionHelpLink); + + void EventBoxLink_KeyPressEvent (object o, KeyPressEventArgs args) + { + if (args.Event.Key == Gdk.Key.KP_Enter || args.Event.Key == Gdk.Key.KP_Space) + DesktopService.ShowUrl (exceptionHelpLink); + } + + void EventBoxLink_ExceptionHelpLink (object o, ButtonPressEventArgs args) => DesktopService.ShowUrl (exceptionHelpLink); + protected override void OnSizeAllocated (Gdk.Rectangle allocation) { base.OnSizeAllocated (allocation); ExceptionMessageLabel.WidthRequest = rightVBox.Allocation.Width; - ExceptionHelpLinkLabel.WidthRequest = rightVBox.Allocation.Width; if (vboxAroundInnerExceptionMessage != null) { InnerExceptionMessageLabel.WidthRequest = vboxAroundInnerExceptionMessage.Allocation.Width; - InnerExceptionHelpLinkLabel.WidthRequest = vboxAroundInnerExceptionMessage.Allocation.Width; } } @@ -180,6 +196,7 @@ widget ""*.exception_help_link_label"" style ""exception-help-link-label"" ExceptionValueTreeView.AllowExpanding = true; ExceptionValueTreeView.AllowPinning = false; ExceptionValueTreeView.AllowEditing = false; + ExceptionValueTreeView.CanFocus = true; ExceptionValueTreeView.AllowAdding = false; ExceptionValueTreeView.RulesHint = true; ExceptionValueTreeView.ModifyFont (Pango.FontDescription.FromString (Platform.IsWindows ? "9" : "11")); @@ -189,6 +206,7 @@ widget ""*.exception_help_link_label"" style ""exception-help-link-label"" var scrolled = new ScrolledWindow { HeightRequest = 180, + CanFocus = true, HscrollbarPolicy = PolicyType.Automatic, VscrollbarPolicy = PolicyType.Automatic }; @@ -230,7 +248,7 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" expander.Child = widget; expander.Spacing = 0; expander.Show (); - expander.CanFocus = false; + expander.CanFocus = true; expander.UseMarkup = true; expander.Expanded = true; expander.Activated += Expander_Activated; @@ -399,7 +417,7 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" Label InnerExceptionTypeLabel; Label InnerExceptionMessageLabel; - Label InnerExceptionHelpLinkLabel; + Button InnerExceptionHelpLinkButton; Widget CreateInnerExceptionMessage () { @@ -425,24 +443,39 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" InnerExceptionMessageLabel.Xalign = 0; InnerExceptionMessageLabel.ModifyFont (Pango.FontDescription.FromString (Platform.IsWindows ? "9" : "11")); - InnerExceptionHelpLinkLabel = new Label (); - InnerExceptionHelpLinkLabel.UseMarkup = true; - InnerExceptionHelpLinkLabel.Wrap = true; - InnerExceptionHelpLinkLabel.LineWrapMode = Pango.WrapMode.Char; - InnerExceptionHelpLinkLabel.Selectable = true; - InnerExceptionHelpLinkLabel.CanFocus = false; - InnerExceptionHelpLinkLabel.Xalign = 0; - InnerExceptionHelpLinkLabel.ModifyFont (Pango.FontDescription.FromString (Platform.IsWindows ? "9" : "11")); - InnerExceptionHelpLinkLabel.SetLinkHandler ((str) => IdeServices.DesktopService.ShowUrl (str)); + InnerExceptionHelpLinkButton = new Button { + CanFocus = true, + BorderWidth = 0, + Relief = ReliefStyle.Half, + Xalign = 0 + }; + InnerExceptionHelpLinkButton.ModifyFont (Pango.FontDescription.FromString (Platform.IsWindows ? "9" : "11")); + InnerExceptionHelpLinkButton.KeyPressEvent += InnerExceptionHelpLinkLabel_KeyPressEvent; + InnerExceptionHelpLinkButton.Clicked += InnerExceptionHelpLinkLabel_Pressed; + + InnerExceptionHelpLinkButton.ModifyBg (StateType.Selected, Styles.ExceptionCaughtDialog.TreeSelectedBackgroundColor.ToGdkColor ()); vboxAroundInnerExceptionMessage.PackStart (hbox, false, true, 0); vboxAroundInnerExceptionMessage.PackStart (InnerExceptionMessageLabel, true, true, 10); - vboxAroundInnerExceptionMessage.PackStart (InnerExceptionHelpLinkLabel, true, true, 2); + + var innerExceptionHContainer = new HBox (); + + innerExceptionHContainer.PackStart (InnerExceptionHelpLinkButton, false, false, 0); + innerExceptionHContainer.PackStart (new Fixed (), true, true, 0); + + vboxAroundInnerExceptionMessage.PackStart (innerExceptionHContainer, true, true, 2); hboxMain.PackStart (vboxAroundInnerExceptionMessage, true, true, 10); hboxMain.ShowAll (); return hboxMain; } + void InnerExceptionHelpLinkLabel_Pressed (object sender, EventArgs e) => DesktopService.ShowUrl (innerExceptionHelpLink); + void InnerExceptionHelpLinkLabel_KeyPressEvent (object o, KeyPressEventArgs args) + { + if (args.Event.Key == Gdk.Key.KP_Enter || args.Event.Key == Gdk.Key.KP_Space) + IdeServices.DesktopService.ShowUrl (innerExceptionHelpLink); + } + TreeStore InnerExceptionsStore; class InnerExceptionsTree : TreeView @@ -606,14 +639,19 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" InnerExceptionTypeLabel.Markup = "<b>" + GLib.Markup.EscapeText (ex.Type) + "</b>"; InnerExceptionMessageLabel.Text = ex.Message; if (!string.IsNullOrEmpty (ex.HelpLink)) { - InnerExceptionHelpLinkLabel.Markup = GetHelpLinkMarkup (ex); - InnerExceptionHelpLinkLabel.Show (); + InnerExceptionHelpLinkButton.Label = GettextCatalog.GetString ("Read More…"); + innerExceptionHelpLink = ex.HelpLink; + InnerExceptionHelpLinkButton.Show (); } else { - InnerExceptionHelpLinkLabel.Hide (); + innerExceptionHelpLink = string.Empty; + InnerExceptionHelpLinkButton.Hide (); } } } + string innerExceptionHelpLink; + string exceptionHelpLink; + void UpdateDisplay () { if (destroyed) @@ -622,20 +660,16 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" ExceptionTypeLabel.Text = exception.Type; ExceptionMessageLabel.Text = exception.Message ?? string.Empty; if (!string.IsNullOrEmpty (exception.HelpLink)) { - ExceptionHelpLinkLabel.Show (); - ExceptionHelpLinkLabel.Markup = GetHelpLinkMarkup (exception); + ExceptionHelpLinkButton.Show (); + exceptionHelpLink = exception.HelpLink; + ExceptionHelpLinkButton.Label = GettextCatalog.GetString ("More information"); } else { - ExceptionHelpLinkLabel.Hide (); + ExceptionHelpLinkButton.Hide (); } UpdateSelectedException (exception); } - internal static string GetHelpLinkMarkup (ExceptionInfo exception) - { - return $"<a href=\"{System.Security.SecurityElement.Escape (exception.HelpLink)}\">{GettextCatalog.GetString ("More information")}</a>"; - } - void ExceptionChanged (object sender, EventArgs e) { Application.Invoke ((o, args) => { diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Projects/ProjectFileDescriptor.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Projects/ProjectFileDescriptor.cs index 6a08c70968..b51ee3c13b 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Projects/ProjectFileDescriptor.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Projects/ProjectFileDescriptor.cs @@ -51,16 +51,16 @@ namespace MonoDevelop.DesignerSupport void OnFilePropertyChangedInProject (object sender, ProjectFileEventArgs args)
{
- var pad = IdeApp.Workbench.GetPad <PropertyPad> ();
+ var pad = IdeApp.Workbench.GetPad <IPropertyPad> ();
if (pad == null)
return;
- var grid = ((PropertyPad)pad.Content).PropertyGrid;
- if (grid.IsEditing)
+ var propertyPad = (IPropertyPad)pad.Content;
+ if (propertyPad.IsGridEditing)
return;
if (args.Any (arg => arg.ProjectFile == file))
- grid.Populate (saveEditSession: false);
+ propertyPad.PopulateGrid (saveEditSession: false);
}
void IDisposable.Dispose ()
diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/Styles.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/Styles.cs index ff77360350..6b62628491 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/Styles.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/Styles.cs @@ -1,8 +1,9 @@ #if MAC
using AppKit;
using MonoDevelop.Ide; +using Xamarin.PropertyEditing.Mac; -namespace MonoDevelop.DesignerSupport.Toolbox +namespace MonoDevelop.DesignerSupport { static class Styles {
@@ -28,12 +29,14 @@ namespace MonoDevelop.DesignerSupport.Toolbox public static void LoadStyles ()
{ if (IdeApp.Preferences.UserInterfaceTheme == Theme.Light) { + PropertyEditorPanel.ThemeManager.Theme = Xamarin.PropertyEditing.Themes.PropertyEditorTheme.Light; HeaderBackgroundColor = NSColor.FromRgb (0.98f, 0.98f, 0.98f); HeaderBorderBackgroundColor = NSColor.FromRgb (0.96f, 0.96f, 0.96f);
LabelSelectedForegroundColor = NSColor.Highlight;
ToolbarBackgroundColor = NSColor.White;
CellBackgroundSelectedColor = NSColor.FromRgb (0.36f, 0.54f, 0.90f);
} else {
+ PropertyEditorPanel.ThemeManager.Theme = Xamarin.PropertyEditing.Themes.PropertyEditorTheme.Dark;
CellBackgroundSelectedColor = NSColor.FromRgb (0.38f, 0.55f, 0.91f);
HeaderBackgroundColor = NSColor.FromRgb (0.29f, 0.29f, 0.29f);
HeaderBorderBackgroundColor = NSColor.FromRgb (0.29f, 0.29f, 0.29f);
diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.csproj b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.csproj index 8526f08323..98dca9926b 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.csproj +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.csproj @@ -18,10 +18,18 @@ <Private>False</Private> </Reference> <ProjectReference Include="..\..\..\external\xwt\Xwt.XamMac\Xwt.XamMac.csproj"> - <Project>{B7C1673E-5124-4BE5-8D21-EC8B12F85B6B}</Project> + <Project>{B7C1673E-5124-4BE5-8D21-EC8B12F85B6B}</Project> <Name>Xwt.XamMac</Name> <Private>False</Private> - </ProjectReference> + </ProjectReference> + <ProjectReference Include="..\..\..\external\Xamarin.PropertyEditing\Xamarin.PropertyEditing.Mac\Xamarin.PropertyEditing.Mac.csproj"> + <Project>{E8F4F0EB-C2B1-4116-8459-E076E0E0E485}</Project> + <Name>Xamarin.PropertyEditing.Mac</Name> + </ProjectReference> + <ProjectReference Include="..\..\..\external\Xamarin.PropertyEditing\Xamarin.PropertyEditing\Xamarin.PropertyEditing.csproj"> + <Project>{A0B6FE73-D046-4E1C-BA9D-F20683889C5A}</Project> + <Name>Xamarin.PropertyEditing</Name> + </ProjectReference> </ItemGroup> <ItemGroup> <Reference Include="System" /> @@ -149,6 +157,18 @@ <Compile Include="MonoDevelop.DesignerSupport.Toolbox\MacToolboxWidgetFlowLayoutDelegate.cs" /> <Compile Include="MonoDevelop.DesignerSupport.Toolbox\Styles.cs" /> <Compile Include="MonoDevelop.DesignerSupport.Toolbox\Toolbox.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\MacPropertyGrid.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyPadObjectEditor.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyPadEditorProvider.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyProviderTypeInfo.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyDescriptorEventInfo.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyInfo\DescriptorPropertyInfo.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyInfo\EnumDescriptorPropertyInfo.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyInfo\StringStandardValuesPropertyInfo.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyInfo\FilePathPropertyInfo.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyInfo\DirectoryPathPropertyInfo.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\IPropertyPad.cs" /> + <Compile Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyInfo\FlagDescriptorPropertyInfo.cs" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="MonoDevelop.DesignerSupport.addin.xml" /> @@ -188,6 +208,8 @@ <ItemGroup> <Folder Include="MonoDevelop.DesignerSupport.Toolbox\Helpers\" /> <Folder Include="MonoDevelop.DesignerSupport.Toolbox\NativeViews\" /> + <Folder Include="MonoDevelop.DesignerSupport\NativePropertyEditors\" /> + <Folder Include="MonoDevelop.DesignerSupport\NativePropertyEditors\PropertyInfo\" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> </Project> diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/DesignerSupportService.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/DesignerSupportService.cs index 10f6e33171..822dce6947 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/DesignerSupportService.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/DesignerSupportService.cs @@ -43,7 +43,7 @@ namespace MonoDevelop.DesignerSupport { public class DesignerSupportService { - PropertyPad propertyPad = null; + IPropertyPad propertyPad = null; ToolboxService toolboxService = null; IPropertyProvider[] providers; @@ -53,13 +53,13 @@ namespace MonoDevelop.DesignerSupport #region PropertyPad - public PropertyPad PropertyPad { + public IPropertyPad PropertyPad { get { return propertyPad; } } - internal void SetPad (PropertyPad pad) + internal void SetPad (IPropertyPad pad) { propertyPad = pad; @@ -67,26 +67,28 @@ namespace MonoDevelop.DesignerSupport if (lastPadProvider != null) { object[] provs = GetProvidersForObject (lastComponent, lastPadProvider.GetProvider ()); if (provs.Length > 0) - propertyPad.PropertyGrid.SetCurrentObject (lastComponent, provs); + propertyPad.SetCurrentObject (lastComponent, provs); else propertyPad.BlankPad (); - - var customizer = lastPadProvider as IPropertyPadCustomizer; - if (customizer != null) - customizer.Customize (pad.PadWindow, pad.PropertyGrid); - propertyPad.PropertyGrid.Changed += OnPropertyGridChanged; + + if (lastPadProvider is IPropertyPadCustomizer customizer && pad is PropertyPad ppad) { + customizer.Customize (ppad.PadWindow, ppad.PropertyGrid); + } + propertyPad.PropertyGridChanged += OnPropertyGridChanged; } else if (lastCustomProvider != null) { - try { - var currentCustomWidget = lastCustomProvider.GetCustomPropertyWidget (); - if (currentCustomWidget != null) { - propertyPad.UseCustomWidget (currentCustomWidget); - if (lastCustomProvider is IPropertyPadCustomizer customizer) - customizer.Customize (pad.PadWindow, null); + if (propertyPad is PropertyPad ppad) { + try { + var currentCustomWidget = lastCustomProvider.GetCustomPropertyWidget (); + if (currentCustomWidget != null) { + ppad.UseCustomWidget (currentCustomWidget); + if (lastCustomProvider is IPropertyPadCustomizer customizer) + customizer.Customize (ppad.PadWindow, null); + } + } catch (Exception ex) { + LoggingService.LogInternalError ($"There was an error trying to GetCustomPropertyWidget from '{lastCustomProvider.GetType ()}' provider", ex); + ReSetPad (); } - } catch (Exception ex) { - LoggingService.LogInternalError ($"There was an error trying to GetCustomPropertyWidget from '{lastCustomProvider.GetType ()}' provider", ex); - ReSetPad (); } } } @@ -95,8 +97,8 @@ namespace MonoDevelop.DesignerSupport void DisposePropertyPadProvider () { if (lastPadProvider != null) { - if (propertyPad != null && propertyPad.PropertyGrid != null) - propertyPad.PropertyGrid.Changed -= OnPropertyGridChanged; + if (propertyPad != null) + propertyPad.PropertyGridChanged -= OnPropertyGridChanged; lastPadProvider.OnEndEditing (lastComponent); lastPadProvider = null; lastComponent = null; @@ -144,17 +146,19 @@ namespace MonoDevelop.DesignerSupport object[] provs = GetProvidersForObject (comp, provider.GetProvider ()); if (provs.Length > 0) { - propertyPad.PropertyGrid.SetCurrentObject (comp, provs); - propertyPad.CommandRouteOrigin = commandRouteOrigin; + propertyPad.SetCurrentObject (comp, provs); + + if (propertyPad is PropertyPad propPad) { + propPad.CommandRouteOrigin = commandRouteOrigin; + } } else propertyPad.BlankPad (); - - var customizer = provider as IPropertyPadCustomizer; - if (customizer != null) - customizer.Customize (propertyPad.PadWindow, propertyPad.PropertyGrid); - - propertyPad.PropertyGrid.Changed += OnPropertyGridChanged; + + if (provider is IPropertyPadCustomizer customizer && propertyPad is PropertyPad ppad) + customizer.Customize (ppad.PadWindow, ppad.PropertyGrid); + + propertyPad.PropertyGridChanged += OnPropertyGridChanged; } else { ReSetPad (); @@ -178,17 +182,16 @@ namespace MonoDevelop.DesignerSupport DisposeCustomPropertyPadProvider (); lastCustomProvider = provider; - - if (propertyPad != null) { + + if (propertyPad != null && propertyPad is PropertyPad ppad) { try { var customWidget = provider.GetCustomPropertyWidget (); if (customWidget != null) { - propertyPad.UseCustomWidget (customWidget); - propertyPad.CommandRouteOrigin = commandRouteOrigin; - + ppad.UseCustomWidget (customWidget); + ppad.CommandRouteOrigin = commandRouteOrigin; var customizer = provider as IPropertyPadCustomizer; if (customizer != null) - customizer.Customize (propertyPad.PadWindow, null); + customizer.Customize (ppad.PadWindow, null); } else { propertyPad?.BlankPad (); return; @@ -225,9 +228,9 @@ namespace MonoDevelop.DesignerSupport lastPadProvider.OnChanged (lastComponent); } - #endregion +#endregion - #region Toolbox +#region Toolbox public ToolboxService ToolboxService { get{ @@ -240,7 +243,7 @@ namespace MonoDevelop.DesignerSupport } } - #endregion +#endregion internal DesignerSupportService () { diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/IPropertyPad.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/IPropertyPad.cs new file mode 100644 index 0000000000..18c610f946 --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/IPropertyPad.cs @@ -0,0 +1,16 @@ + +using System; + +namespace MonoDevelop.DesignerSupport +{ + public interface IPropertyPad + { + bool IsGridEditing { get; } + + event EventHandler PropertyGridChanged; + + void SetCurrentObject (object lastComponent, object [] propertyProviders); + void BlankPad (); + void PopulateGrid (bool saveEditSession); + } +} diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/MacPropertyGrid.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/MacPropertyGrid.cs new file mode 100644 index 0000000000..de3512f570 --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/MacPropertyGrid.cs @@ -0,0 +1,164 @@ + +/* + * PropertyPad.cs: The pad that holds the MD property grid. Can also + * hold custom grid widgets. + * + * Author: + * Jose Medrano <josmed@microsoft.com> + * + * Copyright (C) 2018 Microsoft, Corp + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#if MAC + +using MonoDevelop.Ide.Gui; +using MonoDevelop.Components.Commands; +using System; +using MonoDevelop.Components; +using Xamarin.PropertyEditing; +using Xamarin.PropertyEditing.Mac; +using MonoDevelop.Components.Mac; +using MonoDevelop.Ide; +using MonoDevelop.Ide.Commands; +using MonoDevelop.Components.Theming; +using AppKit; +using CoreGraphics; +using Foundation; + +namespace MonoDevelop.DesignerSupport +{ + class MacPropertyGrid : NSStackView, IPropertyGrid + { + MacPropertyEditorPanel propertyEditorPanel; + + PropertyPadEditorProvider editorProvider; + + NSScrollView scrollView; + + public event EventHandler Focused; + + public bool IsEditing => false; + + public event EventHandler PropertyGridChanged; + + public MacPropertyGrid () + { + Orientation = NSUserInterfaceLayoutOrientation.Vertical; + Alignment = NSLayoutAttribute.Leading; + Spacing = 10; + Distribution = NSStackViewDistribution.Fill; + + propertyEditorPanel = new MacPropertyEditorPanel (); + + scrollView = new NSScrollView () { + HasVerticalScroller = true, + HasHorizontalScroller = false, + }; + scrollView.WantsLayer = true; + scrollView.BackgroundColor = Styles.HeaderBackgroundColor; + scrollView.DocumentView = propertyEditorPanel; + + AddArrangedSubview (scrollView); + + propertyEditorPanel.Focused += PropertyEditorPanel_Focused; + + //propertyEditorPanel.PropertiesChanged += PropertyEditorPanel_PropertiesChanged; + } + + void Widget_Focused (object o, Gtk.FocusedArgs args) + { + propertyEditorPanel.BecomeFirstResponder (); + } + + void PropertyEditorPanel_Focused (object sender, EventArgs e) => Focused?.Invoke (this, EventArgs.Empty); + + public override void SetFrameSize (CGSize newSize) + { + scrollView.SetFrameSize (newSize); + base.SetFrameSize (newSize); + } + + void PropertyEditorPanel_PropertiesChanged (object sender, EventArgs e) => PropertyGridChanged?.Invoke (this, e); + + public void BlankPad () + { + propertyEditorPanel.SelectedItems.Clear (); + currentSelectedObject = null; + } + + public void OnPadContentShown () + { + if (editorProvider == null) { + editorProvider = new PropertyPadEditorProvider (); + propertyEditorPanel.TargetPlatform = new TargetPlatform (editorProvider) { + AutoExpandGroups = new string [] { "Build", "Misc", "NuGet", "Reference" } + }; + propertyEditorPanel.ArrangeMode = PropertyArrangeMode.Category; + } + } + + Tuple<object, object []> currentSelectedObject; + + public void SetCurrentObject (object lastComponent, object [] propertyProviders) + { + if (lastComponent != null) { + var selection = new Tuple<object, object []> (lastComponent, propertyProviders); + if (currentSelectedObject != selection) { + propertyEditorPanel.SelectedItems.Clear (); + propertyEditorPanel.SelectedItems.Add (new Tuple<object, object []> (lastComponent, propertyProviders)); + currentSelectedObject = selection; + } + } + } + + protected override void Dispose (bool disposing) + { + if (propertyEditorPanel != null) { + propertyEditorPanel.Focused -= PropertyEditorPanel_Focused; + } + base.Dispose (disposing); + } + + public void Populate (bool saveEditSession) + { + //not implemented + } + + public void SetToolbarProvider (Components.PropertyGrid.PropertyGrid.IToolbarProvider toolbarProvider) + { + //not implemented + } + } + + class MacPropertyEditorPanel : PropertyEditorPanel + { + public EventHandler Focused; + + public override bool BecomeFirstResponder () + { + Focused?.Invoke (this, EventArgs.Empty); + return base.BecomeFirstResponder (); + } + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyDescriptorEventInfo.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyDescriptorEventInfo.cs new file mode 100644 index 0000000000..12c8609994 --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyDescriptorEventInfo.cs @@ -0,0 +1,66 @@ +// +// PropertyPadObjectEditor.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if MAC + +using System; +using System.Collections.Generic; +using Xamarin.PropertyEditing; +using System.Linq; +using System.Reflection; + +namespace MonoDevelop.DesignerSupport +{ + class PropertyDescriptorEventInfo + : IEventInfo + { + public PropertyDescriptorEventInfo (EventInfo info) + { + this.info = info ?? throw new ArgumentNullException (nameof (info)); + } + + public string Name => this.info.Name; + + public IReadOnlyList<string> GetHandlers (object target) + { + //TODO: + if (target == null) + return Array.Empty<string> (); + + Type targetType = target.GetType (); + FieldInfo field = targetType.GetField ($"Event{Name}", BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.IgnoreCase); + Delegate d = field?.GetValue (target) as Delegate; + if (d == null) + return Array.Empty<string> (); + + return d.GetInvocationList ().Select (i => i.Method.Name).ToList (); + } + + private readonly EventInfo info; + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/DescriptorPropertyInfo.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/DescriptorPropertyInfo.cs new file mode 100644 index 0000000000..033470859b --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/DescriptorPropertyInfo.cs @@ -0,0 +1,155 @@ +// +// DescriptorPropertyInfo.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if MAC + +using System.Linq; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xamarin.PropertyEditing; +using Xamarin.PropertyEditing.Reflection; +using System.Reflection; +using System.ComponentModel; +using System.Collections; +using MonoDevelop.Core; + +namespace MonoDevelop.DesignerSupport +{ + class DescriptorPropertyInfo + : IPropertyInfo, IEquatable<DescriptorPropertyInfo> + { + public PropertyDescriptor PropertyDescriptor { get; private set; } + public object PropertyProvider { get; private set; } + readonly ValueSources valueSources; + static readonly IAvailabilityConstraint [] EmptyConstraints = new IAvailabilityConstraint [0]; + static readonly PropertyVariationOption [] EmptyVariationOptions = new PropertyVariationOption [0]; + + public DescriptorPropertyInfo (PropertyDescriptor propertyInfo, object propertyProvider, ValueSources valueSources) + { + this.PropertyDescriptor = propertyInfo; + this.PropertyProvider = propertyProvider; + this.valueSources = valueSources; + } + + public string Name => PropertyDescriptor.DisplayName; + + public string Description => PropertyDescriptor.Description; + + public virtual Type Type => PropertyDescriptor.PropertyType; + + public ITypeInfo RealType => ToTypeInfo (PropertyDescriptor, PropertyProvider, Type); + + public string Category => PropertyDescriptor.Category; + + public bool CanWrite => !PropertyDescriptor.IsReadOnly; + + public ValueSources ValueSources => valueSources; + + public IReadOnlyList<PropertyVariationOption> Variations => EmptyVariationOptions; + + public IReadOnlyList<IAvailabilityConstraint> AvailabilityConstraints => EmptyConstraints; + + public bool IsUncommon => false; + + public bool Equals (DescriptorPropertyInfo other) + { + if (other is null) + return false; + if (ReferenceEquals (this, other)) + return true; + + return PropertyDescriptor.Equals (other.PropertyDescriptor); + } + + public override bool Equals (object obj) + { + return obj is DescriptorPropertyInfo info && Equals (info); + } + + public override int GetHashCode () + { + return PropertyDescriptor.GetHashCode (); + } + + public static Type GetCollectionItemType (Type colType) + { + foreach (var member in colType.GetDefaultMembers ()) { + var prop = member as PropertyInfo; + if (prop != null && prop.Name == "Item" && prop.PropertyType != typeof (object)) + return prop.PropertyType; + } + return null; + } + + public static ITypeInfo ToTypeInfo (PropertyDescriptor propertyDescriptor, object propertyProvider, Type type, bool isRelevant = true) + { + var asm = type.Assembly.GetName ().Name; + return new PropertyProviderTypeInfo (propertyDescriptor, propertyProvider, new AssemblyInfo (asm, isRelevant), type.Namespace, type.Name); + } + + const string ErrorOnGetValueMessage = "Error in GetValueAsync<T> using property descriptor converter"; + protected void LogGetValueAsyncError (Exception ex) => LoggingService.LogError (ErrorOnGetValueMessage, ex); + + internal virtual Task<T> GetValueAsync<T> (object target) + { + object value = null; + + try { + value = PropertyDescriptor.GetValue (PropertyProvider); + TypeConverter tc = PropertyDescriptor.Converter; + if (tc.CanConvertTo (typeof (T))) { + value = tc.ConvertTo (value, typeof (T)); + } + return Task.FromResult ((T)value); + } catch (Exception ex) { + LogGetValueAsyncError (ex); + } + + T converted = default; + try { + if (value != null && !(value is T)) { + if (typeof (T) == typeof (string)) { + value = value.ToString (); + } else { + value = Convert.ChangeType (value, typeof (T)); + } + } + return Task.FromResult ((T)value); + } catch (Exception ex) { + LogGetValueAsyncError (ex); + } + return Task.FromResult (converted); + } + + internal virtual void SetValue<T> (object target, T value) + { + PropertyDescriptor.SetValue (PropertyProvider, value); + } + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/DirectoryPathPropertyInfo.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/DirectoryPathPropertyInfo.cs new file mode 100644 index 0000000000..6a070d80c1 --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/DirectoryPathPropertyInfo.cs @@ -0,0 +1,71 @@ +// +// DescriptorPropertyInfo.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if MAC + +using System; +using System.Threading.Tasks; +using Xamarin.PropertyEditing; +using System.ComponentModel; +using MonoDevelop.Core; + +namespace MonoDevelop.DesignerSupport +{ + class DirectoryPathPropertyInfo + : DescriptorPropertyInfo, IEquatable<DescriptorPropertyInfo> + { + public DirectoryPathPropertyInfo (PropertyDescriptor propertyInfo, object propertyProvider, ValueSources valueSources) : base (propertyInfo, propertyProvider, valueSources) + { + } + + public override Type Type => typeof (DirectoryPath); + + internal override void SetValue<T> (object target, T value) + { + if (value is DirectoryPath directoryPath) { + PropertyDescriptor.SetValue (PropertyProvider, new MonoDevelop.Core.FilePath (directoryPath.Source)); + } else { + throw new Exception (string.Format ("Value: {0} of type {1} is not a DirectoryPath", value, value.GetType ())); + } + } + + internal override Task<T> GetValueAsync<T> (object target) + { + try { + if (target is Core.FilePath directoryPath) { + T result = (T)(object)new DirectoryPath (directoryPath.FullPath); + return Task.FromResult (result); + } + LoggingService.LogWarning (string.Format ("Value: {0} of type {1} is not a DirectoryPath", target, target.GetType ())); + } catch (Exception e) { + LogGetValueAsyncError (e); + } + return base.GetValueAsync<T> (target); + } + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/EnumDescriptorPropertyInfo.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/EnumDescriptorPropertyInfo.cs new file mode 100644 index 0000000000..dc5a6c03f3 --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/EnumDescriptorPropertyInfo.cs @@ -0,0 +1,111 @@ +// +// EnumDescriptorPropertyInfo.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if MAC + +using System; +using Xamarin.PropertyEditing; +using System.ComponentModel; +using System.Collections.Generic; +using System.Collections; +using System.Threading.Tasks; +using MonoDevelop.Core; + +namespace MonoDevelop.DesignerSupport +{ + class EnumDescriptorPropertyInfo + : DescriptorPropertyInfo, IHavePredefinedValues<string> //, IValidator<IReadOnlyList<string>>, ICoerce<IReadOnlyList<string>> + { + Hashtable names = new Hashtable (); + Array values; + public EnumDescriptorPropertyInfo (PropertyDescriptor propertyDescriptor, object propertyProvider, ValueSources valueSources) + : base (propertyDescriptor, propertyProvider, valueSources) + { + values = Enum.GetValues (PropertyDescriptor.PropertyType); + var fields = PropertyDescriptor.PropertyType.GetFields (); + + names = new Hashtable (); + foreach (System.Reflection.FieldInfo f in fields) { + var att = (DescriptionAttribute)Attribute.GetCustomAttribute (f, typeof (DescriptionAttribute)); + if (att != null) + names [f.Name] = att.Description; + else + names [f.Name] = f.Name; + } + + for (int i = 0; i < values.Length; i++) { + //name + var value = values.GetValue (i); + string str = PropertyDescriptor.Converter.ConvertToString (value); + if (names.Contains (str)) + str = (string)names [str]; + predefinedValues.Add (str, i.ToString ()); + } + } + + internal override void SetValue<T> (object target, T value) + { + var tc = PropertyDescriptor.Converter; + if (tc.CanConvertFrom (typeof (T))) { + if (int.TryParse ((string)(object)value, out var index)) { + PropertyDescriptor.SetValue (PropertyProvider, values.GetValue (index)); + } else { + throw new Exception (string.Format ("Error in SetValue<T> parsing {0} as integer in {1} (enum descriptor)", value, PropertyDescriptor.DisplayName)); + } + } + } + + internal override Task<T> GetValueAsync<T> (object target) + { + //we need set the index of the selected item + T result = default; + try { + TypeConverter tc = PropertyDescriptor.Converter; + object cob = PropertyDescriptor.GetValue (PropertyProvider); + var index = Array.IndexOf (values, cob); + if (index > -1) { + result = (T)(object) index.ToString (); + } else { + LoggingService.LogError ("Error in GetValueAsync<T> parsing {0} as integer in {1} (enum descriptor)", index, PropertyDescriptor.DisplayName); + } + } catch (Exception ex) { + LogGetValueAsyncError (ex); + } + return Task.FromResult (result); + } + + public bool IsConstrainedToPredefined => false; + + public bool IsValueCombinable { + get; + } + + readonly protected Dictionary<string, string> predefinedValues = new Dictionary<string, string> (); + public IReadOnlyDictionary<string, string> PredefinedValues => predefinedValues; + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/FilePathPropertyInfo.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/FilePathPropertyInfo.cs new file mode 100644 index 0000000000..563c5eee6b --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/FilePathPropertyInfo.cs @@ -0,0 +1,66 @@ +// +// DescriptorPropertyInfo.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if MAC + +using System; +using System.Threading.Tasks; +using Xamarin.PropertyEditing; +using System.ComponentModel; + +namespace MonoDevelop.DesignerSupport +{ + class FilePathPropertyInfo + : DescriptorPropertyInfo, IEquatable<DescriptorPropertyInfo> + { + public FilePathPropertyInfo (PropertyDescriptor propertyInfo, object propertyProvider, ValueSources valueSources) : base (propertyInfo, propertyProvider, valueSources) + { + } + + public override Type Type => typeof (FilePath); + + internal override void SetValue<T> (object target, T value) + { + if (value is FilePath filePath) { + PropertyDescriptor.SetValue (PropertyProvider, new MonoDevelop.Core.FilePath (filePath.Source)); + } else { + throw new Exception (string.Format ("Value: {0} of type {1} is not a DirectoryPath", value, value.GetType ())); + } + } + + internal override Task<T> GetValueAsync<T> (object target) + { + if (target is Core.FilePath directoryPath) { + T result = (T)(object)new FilePath (directoryPath.FullPath); + return Task.FromResult (result); + } + Core.LoggingService.LogWarning ("Value: {0} of type {1} is not a DirectoryPath", target, target.GetType ()); + return base.GetValueAsync<T> (target); + } + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/FlagDescriptorPropertyInfo.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/FlagDescriptorPropertyInfo.cs new file mode 100644 index 0000000000..d485704ca8 --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/FlagDescriptorPropertyInfo.cs @@ -0,0 +1,91 @@ +// +// FlagDescriptorPropertyInfo.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if MAC + +using Xamarin.PropertyEditing; +using System.ComponentModel; +using System.Threading.Tasks; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace MonoDevelop.DesignerSupport +{ + class FlagDescriptorPropertyInfo + : DescriptorPropertyInfo, IHavePredefinedValues<string> + { + public FlagDescriptorPropertyInfo (PropertyDescriptor propertyDescriptor, object propertyProvider, ValueSources valueSources) : base (propertyDescriptor, propertyProvider, valueSources) + { + IsValueCombinable = true; + foreach (object value in System.Enum.GetValues (propertyDescriptor.PropertyType)) { + ulong uintVal = Convert.ToUInt64 (value); + predefinedValues.Add (value.ToString (), uintVal.ToString ()); + } + } + + internal override void SetValue<T> (object target, T value) + { + var selectedValues = (System.String []) (object)value; + ulong result = default; + foreach (var selectedValue in selectedValues) { + result += Convert.ToUInt64 (selectedValue); + } + PropertyDescriptor.SetValue (PropertyProvider, Enum.ToObject (PropertyDescriptor.PropertyType, result)); + } + + internal override Task<T> GetValueAsync<T> (object target) + { + //we need set the index of the selected item + try { + var myValue = (Enum)target; + var result = new List<string> (); + foreach (Enum item in Enum.GetValues (target.GetType ())) + { + if (myValue.HasFlag (item)) { + ulong uintVal = Convert.ToUInt64 (item); + result.Add (uintVal.ToString ()); + } + } + return Task.FromResult<T> ((T)(object)result); + } catch (Exception ex) { + LogGetValueAsyncError (ex); + } + return Task.FromResult <T> (default); + } + + public bool IsConstrainedToPredefined => true; + + public bool IsValueCombinable { + get; + } + + readonly protected Dictionary<string, string> predefinedValues = new Dictionary<string, string> (); + public IReadOnlyDictionary<string, string> PredefinedValues => predefinedValues; + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/StringStandardValuesPropertyInfo.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/StringStandardValuesPropertyInfo.cs new file mode 100644 index 0000000000..6f1eb5d724 --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyInfo/StringStandardValuesPropertyInfo.cs @@ -0,0 +1,57 @@ +// +// StringStandardValuesPropertyInfo.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if MAC + +using Xamarin.PropertyEditing; +using System.ComponentModel; +using System.Collections.Generic; + +namespace MonoDevelop.DesignerSupport +{ + class StringStandardValuesPropertyInfo + : DescriptorPropertyInfo, IHavePredefinedValues<string> + { + public StringStandardValuesPropertyInfo (PropertyDescriptor propertyDescriptor, object propertyProvider, ValueSources valueSources) : base (propertyDescriptor, propertyProvider, valueSources) + { + foreach (object stdValue in PropertyDescriptor.Converter.GetStandardValues ()) { + var value = PropertyDescriptor.Converter.ConvertToString (stdValue); + predefinedValues.Add (value, value); + } + } + + public bool IsConstrainedToPredefined => false; + + public bool IsValueCombinable { + get; + } + + protected Dictionary<string, string> predefinedValues = new Dictionary<string, string> (); + public IReadOnlyDictionary<string, string> PredefinedValues => predefinedValues; + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyPadEditorProvider.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyPadEditorProvider.cs new file mode 100644 index 0000000000..1d74baaf33 --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyPadEditorProvider.cs @@ -0,0 +1,80 @@ +// +// PropertyPadEditorProvider.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if MAC + +using System.Linq; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xamarin.PropertyEditing; +using Xamarin.PropertyEditing.Common; +using Xamarin.PropertyEditing.Reflection; +using System.Reflection; +using System.ComponentModel; + +namespace MonoDevelop.DesignerSupport +{ + class PropertyPadEditorProvider + : IEditorProvider + { + Tuple<object, object[]> currentObject; + IObjectEditor currentEditor; + + public IReadOnlyDictionary<Type, ITypeInfo> KnownTypes { + get; + } = new Dictionary<Type, ITypeInfo> (); + + public Task<IObjectEditor> GetObjectEditorAsync (object item) + { + if (item is Tuple<object, object []> tuple) { + this.currentObject = tuple; + this.currentEditor = new PropertyPadObjectEditor (tuple); ; + return Task.FromResult (currentEditor); + } + return Task.FromResult<IObjectEditor> (null); + } + + public Task<object> CreateObjectAsync (ITypeInfo type) + { + var realType = Type.GetType ($"{type.NameSpace}.{type.Name}, {type.Assembly.Name}"); + if (realType == null) + return Task.FromResult<object> (null); + return Task.FromResult (Activator.CreateInstance (realType)); + } + + public Task<IReadOnlyCollection<IPropertyInfo>> GetPropertiesForTypeAsync (ITypeInfo type) + => Task.FromResult<IReadOnlyCollection<IPropertyInfo>> (new List<IPropertyInfo> ()); + + public Task<AssignableTypesResult> GetAssignableTypesAsync (ITypeInfo type, bool childTypes) + => Task.FromResult (new AssignableTypesResult (new List<ITypeInfo> ())); + + public Task<IReadOnlyList<object>> GetChildrenAsync (object item) + => Task.FromResult<IReadOnlyList<object>> (Array.Empty<object> ()); + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyPadObjectEditor.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyPadObjectEditor.cs new file mode 100644 index 0000000000..40fa6dc49a --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyPadObjectEditor.cs @@ -0,0 +1,235 @@ +// +// PropertyPadObjectEditor.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if MAC + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xamarin.PropertyEditing; +using Xamarin.PropertyEditing.Reflection; +using System.Linq; +using System.ComponentModel; +using System.Collections; +using MonoDevelop.Core; + +namespace MonoDevelop.DesignerSupport +{ + class PropertyPadObjectEditor + : IObjectEditor, INameableObject, IObjectEventEditor + { + private readonly object target; + public string Name { + get; + private set; + } + + private readonly List<IPropertyInfo> properties = new List<IPropertyInfo> (); + private static readonly IObjectEditor [] EmptyDirectChildren = new IObjectEditor [0]; + private readonly List<PropertyDescriptorEventInfo> events = new List<PropertyDescriptorEventInfo> (); + + public object Target => this.target; + + public ITypeInfo TargetType => ToTypeInfo (Target.GetType ()); + + public static ITypeInfo ToTypeInfo (Type type, bool isRelevant = true) + { + var asm = type.Assembly.GetName ().Name; + return new TypeInfo (new AssemblyInfo (asm, isRelevant), type.Namespace, type.Name); + } + + public IReadOnlyCollection<IPropertyInfo> Properties => this.properties; + + public IReadOnlyDictionary<IPropertyInfo, KnownProperty> KnownProperties => null; + + public IObjectEditor Parent => throw new NotImplementedException (); + + public IReadOnlyList<IObjectEditor> DirectChildren => EmptyDirectChildren; + + public IReadOnlyCollection<IEventInfo> Events => this.events; + + public event EventHandler<EditorPropertyChangedEventArgs> PropertyChanged; + + public PropertyPadObjectEditor (Tuple<object, object []> target) + { + this.target = target.Item1; + foreach (object propertyProvider in target.Item2) { + var props = GetProperties (propertyProvider, null); + + for (int i = 0; i < props.Count; i++) { + var prop = props [i] as PropertyDescriptor; + if (prop.IsBrowsable) { + properties.Add (CreatePropertyInfo (prop, propertyProvider)); + } + } + } + } + + bool HasStandardValues (PropertyDescriptor propertyDescriptor) + { + if (propertyDescriptor.Converter.GetStandardValuesSupported ()) { + if (!propertyDescriptor.Converter.GetStandardValuesExclusive () && propertyDescriptor.Converter.CanConvertFrom (typeof (string))) { + return true; + } + } + return false; + } + + PropertyDescriptor GetPropertyDescriptor (PropertyDescriptorCollection collection, string name) + { + for (int i = 0; i < collection.Count; i++) { + if (collection[i].Name == name) { + return collection [i]; + } + } + return null; + } + + protected IPropertyInfo CreatePropertyInfo (PropertyDescriptor propertyDescriptor, object PropertyProvider) + { + var valueSources = ValueSources.Local | ValueSources.Default; + if (propertyDescriptor.PropertyType.IsEnum) { + if (propertyDescriptor.PropertyType.IsDefined (typeof (FlagsAttribute), true)) + return new FlagDescriptorPropertyInfo (propertyDescriptor, PropertyProvider, valueSources); + return new EnumDescriptorPropertyInfo (propertyDescriptor, PropertyProvider, valueSources); + } + + if (propertyDescriptor.PropertyType.IsAssignableFrom (typeof (Core.FilePath))) { + var isDirectoryPropertyDescriptor = GetPropertyDescriptor (propertyDescriptor.GetChildProperties (), nameof (Core.FilePath.IsDirectory)); + if (isDirectoryPropertyDescriptor != null && (bool)isDirectoryPropertyDescriptor.GetValue (target)) { + return new DirectoryPathPropertyInfo (propertyDescriptor, PropertyProvider, valueSources); + } + return new FilePathPropertyInfo (propertyDescriptor, PropertyProvider, valueSources); + } + + if (HasStandardValues (propertyDescriptor)) { + return new StringStandardValuesPropertyInfo (propertyDescriptor, PropertyProvider, valueSources); + } + + return new DescriptorPropertyInfo (propertyDescriptor, PropertyProvider, valueSources); + } + + public static PropertyDescriptorCollection GetProperties (object component, Attribute [] attributes) + { + if (component == null) + return new PropertyDescriptorCollection (new PropertyDescriptor [] { }); + return TypeDescriptor.GetProperties (component); + } + + #region NOT SUPORTED + + public Task AttachHandlerAsync (IEventInfo ev, string handlerName) + { + throw new NotImplementedException (); + } + + public Task DetachHandlerAsync (IEventInfo ev, string handlerName) + { + throw new NotImplementedException (); + } + + #endregion + + public async Task<AssignableTypesResult> GetAssignableTypesAsync (IPropertyInfo property, bool childTypes) + { + return new AssignableTypesResult (Array.Empty<ITypeInfo> ()); + } + + public Task<IReadOnlyList<string>> GetHandlersAsync (IEventInfo ev) + { + if (ev == null) + throw new ArgumentNullException (nameof (ev)); + + if (!(ev is Xamarin.PropertyEditing.Reflection.ReflectionEventInfo info)) + throw new ArgumentException (); + + return Task.FromResult (info.GetHandlers (this.target)); + } + + public Task<string> GetNameAsync () + { + return Task.FromResult (Name); + } + + public Task<IReadOnlyCollection<PropertyVariation>> GetPropertyVariantsAsync (IPropertyInfo property) + { + return Task.FromResult<IReadOnlyCollection<PropertyVariation>> (new PropertyVariation [0]); + } + + public async Task<ValueInfo<T>> GetValueAsync<T> (IPropertyInfo property, PropertyVariation variations = null) + { + if (property == null) + throw new ArgumentNullException (nameof (property)); + + if (!(property is DescriptorPropertyInfo info)) + throw new ArgumentException (); + + T value = await info.GetValueAsync<T> (this.target); + + return new ValueInfo<T> { + Value = value, + Source = ValueSource.Local, + //ValueDescriptor = valueInfoString.ValueDescriptor, + //CustomExpression = valueString + }; + } + + public Task RemovePropertyVariantAsync (IPropertyInfo property, PropertyVariation variant) + { + return Task.CompletedTask; + } + + public Task SetNameAsync (string name) + { + Name = name; + return Task.FromResult (true); + } + + public Task SetValueAsync<T> (IPropertyInfo propertyInfo, ValueInfo<T> valueInfo, PropertyVariation variations = null) + { + if (propertyInfo == null) + throw new ArgumentNullException (nameof (propertyInfo)); + + if (propertyInfo is DescriptorPropertyInfo info && info.CanWrite) { + try { + info.SetValue (this.target, valueInfo.Value); + } catch (Exception ex) { + LoggingService.LogError ("Error setting the value", ex); + } + + OnPropertyChanged (info); + } + return Task.CompletedTask; + } + + protected virtual void OnPropertyChanged (IPropertyInfo property) + { + PropertyChanged?.Invoke (this, new EditorPropertyChangedEventArgs (property)); + } + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyProviderTypeInfo.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyProviderTypeInfo.cs new file mode 100644 index 0000000000..2ac8c97afe --- /dev/null +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/NativePropertyEditors/PropertyProviderTypeInfo.cs @@ -0,0 +1,47 @@ +// +// DescriptorPropertyInfo.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#if MAC + +using Xamarin.PropertyEditing; +using System.ComponentModel; + +namespace MonoDevelop.DesignerSupport +{ + class PropertyProviderTypeInfo : TypeInfo + { + public object PropertyProvider { get; } + public PropertyDescriptor PropertyDescriptor { get; } + + public PropertyProviderTypeInfo (PropertyDescriptor propertyDescriptor, object propertyProvider, IAssemblyInfo assembly, string nameSpace, string name) : base (assembly, nameSpace, name) + { + PropertyDescriptor = propertyDescriptor; + PropertyProvider = propertyProvider; + } + } +} + +#endif
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/PropertyPad.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/PropertyPad.cs index fb18f70238..8631e439d1 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/PropertyPad.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/PropertyPad.cs @@ -30,11 +30,8 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -using System; - using MonoDevelop.Ide.Gui; -using MonoDevelop.DesignerSupport; using pg = MonoDevelop.Components.PropertyGrid; using MonoDevelop.Components.Docking; using System.Collections.Generic; @@ -42,34 +39,84 @@ using MonoDevelop.Components.Commands; using MonoDevelop.Ide; using MonoDevelop.Ide.Commands; using MonoDevelop.Components; +using System; +using Gtk; +using MonoDevelop.Core.FeatureConfiguration; namespace MonoDevelop.DesignerSupport { - - public class PropertyPad : PadContent, ICommandDelegator + public class PropertyPad : PadContent, ICommandDelegator, IPropertyPad { + public event EventHandler PropertyGridChanged; + + readonly bool isNative; + readonly IPropertyGrid propertyGrid; +#if MAC + MacPropertyGrid nativeGrid; + Gtk.Widget gtkWidget; +#endif pg.PropertyGrid grid; + InvisibleFrame frame; bool customWidget; IPadWindow container; DockToolbarProvider toolbarProvider = new DockToolbarProvider (); internal object CommandRouteOrigin { get; set; } - + + public PropertyPad () { - grid = new pg.PropertyGrid (); frame = new InvisibleFrame (); - frame.Add (grid); - + +#if MAC + isNative = FeatureSwitchService.IsFeatureEnabled ("NativePropertyPanel") ?? false; + + if (isNative) { + + nativeGrid = new MacPropertyGrid (); + propertyGrid = nativeGrid; + + gtkWidget = Components.Mac.GtkMacInterop.NSViewToGtkWidget (nativeGrid); + gtkWidget.CanFocus = true; + gtkWidget.Sensitive = true; + gtkWidget.Focused += Widget_Focused; + + nativeGrid.Focused += PropertyGrid_Focused; + frame.Add (gtkWidget); + } else { +#endif + grid = new pg.PropertyGrid (); + propertyGrid = grid; + grid.Changed += Grid_Changed; + frame.Add (grid); +#if MAC + } +#endif frame.ShowAll (); } - + + void Grid_Changed (object sender, EventArgs e) + { + PropertyGridChanged?.Invoke (this, e); + } +#if MAC + void Widget_Focused (object o, Gtk.FocusedArgs args) + { + nativeGrid.BecomeFirstResponder (); + } +#endif protected override void Initialize (IPadWindow container) { base.Initialize (container); toolbarProvider.Attach (container.GetToolbar (DockPositionType.Top)); - grid.SetToolbarProvider (toolbarProvider); + + propertyGrid.SetToolbarProvider (toolbarProvider); + + //native cocoa needs content shown to initialize stuff + if (isNative) { + container.PadContentShown += Window_PadContentShown; + } this.container = container; DesignerSupport.Service.SetPad (this); } @@ -78,7 +125,7 @@ namespace MonoDevelop.DesignerSupport get { return container; } } - #region AbstractPadContent implementations +#region AbstractPadContent implementations public override Control Control { get { return frame; } @@ -86,13 +133,25 @@ namespace MonoDevelop.DesignerSupport public override void Dispose() { +#if MAC + if (isNative) { + container.PadContentShown -= Window_PadContentShown; + nativeGrid.Focused -= PropertyGrid_Focused; + gtkWidget.Focused -= Widget_Focused; + } else { +#endif + grid.Changed -= Grid_Changed; +#if MAC + } +#endif + propertyGrid.Dispose (); DesignerSupport.Service.SetPad (null); base.Dispose (); } - #endregion +#endregion - #region ICommandDelegatorRouter implementation +#region ICommandDelegatorRouter implementation object ICommandDelegator.GetDelegatedCommandTarget () { @@ -105,28 +164,64 @@ namespace MonoDevelop.DesignerSupport return null; } - #endregion - - //Grid consumers must call this when they lose focus! +#endregion + + public bool IsGridEditing { + get { + AttachToolbarIfCustomWidget (); + return propertyGrid.IsEditing; + } + } + + //HACK: Mocked gtk property grid to satisfy for customizer.Customize call + readonly static pg.PropertyGrid pGrid = new pg.PropertyGrid (); + internal pg.PropertyGrid PropertyGrid { + get { + AttachToolbarIfCustomWidget (); + return isNative ? pGrid : grid; + } + } + public void BlankPad () { - PropertyGrid.CurrentObject = null; + if (isNative) { + AttachToolbarIfCustomWidget (); + } + propertyGrid.BlankPad (); CommandRouteOrigin = null; } - - internal pg.PropertyGrid PropertyGrid { - get { - if (customWidget) { - customWidget = false; - frame.Remove (frame.Child); + + void Window_PadContentShown (object sender, EventArgs e) + { + propertyGrid.OnPadContentShown (); + } +#if MAC + void PropertyGrid_Focused (object sender, EventArgs e) + { + if (!gtkWidget.HasFocus) { + gtkWidget.HasFocus = true; + } + } +#endif + void AttachToolbarIfCustomWidget () + { + if (customWidget) { + customWidget = false; + frame.Remove (frame.Child); + +#if MAC + if (isNative) { + frame.Add (gtkWidget); + } else { +#endif frame.Add (grid); - toolbarProvider.Attach (container.GetToolbar (DockPositionType.Top)); +#if MAC } - - return grid; +#endif + toolbarProvider.Attach (container.GetToolbar (DockPositionType.Top)); } } - + internal void UseCustomWidget (Gtk.Widget widget) { toolbarProvider.Attach (null); @@ -145,8 +240,19 @@ namespace MonoDevelop.DesignerSupport toolbar.Remove (w); } } + + public void SetCurrentObject (object lastComponent, object [] propertyProviders) + { + AttachToolbarIfCustomWidget (); + propertyGrid.SetCurrentObject (lastComponent, propertyProviders); + } + + public void PopulateGrid (bool saveEditSession) + { + propertyGrid.Populate (saveEditSession); + } } - + class DockToolbarProvider: pg.PropertyGrid.IToolbarProvider { DockItemToolbar tb; @@ -171,7 +277,7 @@ namespace MonoDevelop.DesignerSupport } } - #region IToolbarProvider implementation +#region IToolbarProvider implementation public void Insert (Gtk.Widget w, int pos) { if (tb != null) @@ -213,7 +319,7 @@ namespace MonoDevelop.DesignerSupport } } - #endregion +#endregion } class InvisibleFrame : Gtk.Alignment @@ -232,4 +338,5 @@ namespace MonoDevelop.DesignerSupport return old; } } + } diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/PropertyPadVisitor.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/PropertyPadVisitor.cs index 27d76ec90f..44ceb4acf5 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/PropertyPadVisitor.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/PropertyPadVisitor.cs @@ -88,7 +88,7 @@ namespace MonoDevelop.DesignerSupport found = true; return true; } - if (ob is PropertyPad) { + if (ob is IPropertyPad) { // Don't change the property grid selection when the focus is inside the property grid itself found = true; return true; diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxPad.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxPad.cs index b69333c078..0a9c270f1b 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxPad.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxPad.cs @@ -32,6 +32,7 @@ using System; using MonoDevelop.Ide.Gui; using MonoDevelop.Components; using Xwt; +using Gtk; #if MAC using MonoDevelop.Components.Mac; #endif @@ -44,73 +45,108 @@ namespace MonoDevelop.DesignerSupport #if MAC Toolbox.MacToolbox toolbox; + IPadWindow window; #endif - protected override void Initialize (IPadWindow container) + protected override void Initialize (IPadWindow window) { - base.Initialize (container); + base.Initialize (window); #if MAC - toolbox = new Toolbox.MacToolbox (DesignerSupport.Service.ToolboxService, container); + this.window = window; + toolbox = new Toolbox.MacToolbox (DesignerSupport.Service.ToolboxService, window); widget = GtkMacInterop.NSViewToGtkWidget (toolbox); widget.CanFocus = true; widget.Sensitive = true; widget.KeyPressEvent += toolbox.OnKeyPressed; widget.KeyReleaseEvent += toolbox.KeyReleased; - widget.DragBegin += (o, args) => { - if (!isDragging) { - DesignerSupport.Service.ToolboxService.DragSelectedItem (widget, args.Context); - isDragging = true; - } - }; + widget.DragBegin += Widget_DragBegin; + widget.DragEnd += Widget_DragEnd; + widget.Focused += Widget_Focused; - widget.DragEnd += (o, args) => { - isDragging = false; - }; + this.window.PadContentShown += Container_PadContentShown; + this.window.PadContentHidden += Container_PadContentHidden; - widget.Focused += (s, e) => { - toolbox.FocusSelectedView (); - }; + toolbox.ContentFocused += Toolbox_ContentFocused; + toolbox.DragSourceSet += Toolbox_DragSourceSet; + toolbox.DragBegin += Toolbox_DragBegin; - toolbox.ContentFocused += (s, e) => { - if (!widget.HasFocus) { - widget.HasFocus = true; - toolbox.FocusSelectedView (); - } - }; - toolbox.DragSourceSet += (s, e) => { - targets = new Gtk.TargetList (); - targets.AddTable (e); - }; - toolbox.DragBegin += (object sender, EventArgs e) => { - var selectedNode = toolbox.SelectedNode; - if (!isDragging && selectedNode != null) { + widget.ShowAll (); +#else + widget = new Toolbox.Toolbox (DesignerSupport.Service.ToolboxService, window); +#endif + } - DesignerSupport.Service.ToolboxService.SelectItem (selectedNode); +#if MAC - Gtk.Drag.SourceUnset (widget); + void Container_PadContentShown (object sender, EventArgs args) => toolbox.Hidden = false; + void Container_PadContentHidden (object sender, EventArgs args) => toolbox.Hidden = true; - // Gtk.Application.CurrentEvent and other copied gdk_events seem to have a problem - // when used as they use gdk_event_copy which seems to crash on de-allocating the private slice. - IntPtr currentEvent = GtkWorkarounds.GetCurrentEventHandle (); - Gtk.Drag.Begin (widget, targets, Gdk.DragAction.Copy | Gdk.DragAction.Move, 1, new Gdk.Event (currentEvent, false)); + private void Widget_DragEnd (object o, DragEndArgs args) + { + isDragging = false; + } - // gtk_drag_begin does not store the event, so we're okay - GtkWorkarounds.FreeEvent (currentEvent); + void Widget_Focused (object sender, EventArgs args) + { + toolbox.FocusSelectedView(); + } - } - }; + void Widget_DragBegin (object sender, DragBeginArgs args) + { + if (!isDragging) { + DesignerSupport.Service.ToolboxService.DragSelectedItem (widget, args.Context); + isDragging = true; + } + } - widget.ShowAll (); -#else - widget = new Toolbox.Toolbox (DesignerSupport.Service.ToolboxService, container); -#endif + void Toolbox_DragSourceSet (object sender, Gtk.TargetEntry [] e) + { + targets = new Gtk.TargetList (); + targets.AddTable (e); } -#if MAC + + void Toolbox_ContentFocused (object sender, EventArgs args) + { + if (!widget.HasFocus) { + widget.HasFocus = true; + toolbox.FocusSelectedView (); + } + } + + void Toolbox_DragBegin (object sender, EventArgs args) + { + var selectedNode = toolbox.SelectedNode; + if (!isDragging && selectedNode != null) { + + DesignerSupport.Service.ToolboxService.SelectItem (selectedNode); + + Gtk.Drag.SourceUnset (widget); + + // Gtk.Application.CurrentEvent and other copied gdk_events seem to have a problem + // when used as they use gdk_event_copy which seems to crash on de-allocating the private slice. + IntPtr currentEvent = GtkWorkarounds.GetCurrentEventHandle (); + Gtk.Drag.Begin (widget, targets, Gdk.DragAction.Copy | Gdk.DragAction.Move, 1, new Gdk.Event (currentEvent, false)); + + // gtk_drag_begin does not store the event, so we're okay + GtkWorkarounds.FreeEvent (currentEvent); + } + } + Gtk.TargetList targets = new Gtk.TargetList (); - bool isDragging = false; + bool isDragging; + public override void Dispose () { + if (window != null) { + window.PadContentShown -= Container_PadContentShown; + window.PadContentHidden -= Container_PadContentHidden; + window = null; + } + if (widget != null) { + widget.DragBegin -= Widget_DragBegin; + widget.DragEnd -= Widget_DragEnd; + widget.Focused -= Widget_Focused; widget.KeyPressEvent -= toolbox.OnKeyPressed; widget.KeyReleaseEvent -= toolbox.KeyReleased; widget.Destroy (); @@ -118,6 +154,9 @@ namespace MonoDevelop.DesignerSupport widget = null; } if (toolbox != null) { + toolbox.ContentFocused -= Toolbox_ContentFocused; + toolbox.DragBegin -= Toolbox_DragBegin; + toolbox.DragSourceSet -= Toolbox_DragSourceSet; toolbox.Dispose (); toolbox = null; } diff --git a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Templating/DotNetCoreProjectTemplateStringTagProvider.cs b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Templating/DotNetCoreProjectTemplateStringTagProvider.cs index 182ea1a873..21f55b3f6d 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Templating/DotNetCoreProjectTemplateStringTagProvider.cs +++ b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Templating/DotNetCoreProjectTemplateStringTagProvider.cs @@ -56,13 +56,11 @@ namespace MonoDevelop.DotNetCore.Templating $"DotNetCoreSdk.{SupportedSDK [i]}.Templates.Web.ProjectTemplates.nupkg", GettextCatalog.GetString (string.Format (".NET Core SDK {0} Web Project Templates NuGet package path", SupportedSDK[i])) ); - - if (i > 0 ) { //2.2 and before - yield return new StringTagDescription ( - $"DotNetCoreSdk.{SupportedSDK [i]}.Templates.NUnit3.DotNetNew.Template.nupkg", - GettextCatalog.GetString (string.Format (".NET Core SDK {0} NUnit Project Templates NuGet package path", SupportedSDK [i])) - ); - } + + yield return new StringTagDescription ( + $"DotNetCoreSdk.{SupportedSDK [i]}.Templates.NUnit3.DotNetNew.Template.nupkg", + GettextCatalog.GetString (string.Format (".NET Core SDK {0} NUnit Project Templates NuGet package path", SupportedSDK [i])) + ); } } diff --git a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Tests/MonoDevelop.DotNetCore.Tests/DotNetCoreProjectTests.cs b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Tests/MonoDevelop.DotNetCore.Tests/DotNetCoreProjectTests.cs index 591703c5c2..0d59090465 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Tests/MonoDevelop.DotNetCore.Tests/DotNetCoreProjectTests.cs +++ b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Tests/MonoDevelop.DotNetCore.Tests/DotNetCoreProjectTests.cs @@ -291,6 +291,10 @@ namespace MonoDevelop.DotNetCore.Tests File.WriteAllText (fileName, string.Empty); Assert.AreEqual ("EmbeddedResource", project.GetDefaultBuildAction (fileName)); + fileName = project.BaseDirectory.Combine ("sample.ts"); + File.WriteAllText (fileName, string.Empty); + Assert.AreEqual ("TypeScriptCompile", project.GetDefaultBuildAction (fileName)); + fileName = project.BaseDirectory.Combine ("wwwroot", "MyPage.html"); Directory.CreateDirectory (fileName.ParentDirectory); File.WriteAllText (fileName, string.Empty); diff --git a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectExtension.cs b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectExtension.cs index a005019c12..d44a4f2ff9 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectExtension.cs +++ b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectExtension.cs @@ -167,8 +167,10 @@ namespace MonoDevelop.DotNetCore else outputFileName = GetOutputFileName (configuration); + var workingDirectory = Project.GetOutputFileName (configSel).ParentDirectory; + return new DotNetCoreExecutionCommand ( - string.IsNullOrEmpty (dotnetCoreRunConfiguration?.StartWorkingDirectory) ? Project.BaseDirectory : dotnetCoreRunConfiguration.StartWorkingDirectory, + string.IsNullOrEmpty (dotnetCoreRunConfiguration?.StartWorkingDirectory) ? workingDirectory : dotnetCoreRunConfiguration.StartWorkingDirectory, outputFileName, dotnetCoreRunConfiguration?.StartArguments ) { diff --git a/main/src/addins/MonoDevelop.DotNetCore/Properties/MonoDevelop.DotNetCore.addin.xml b/main/src/addins/MonoDevelop.DotNetCore/Properties/MonoDevelop.DotNetCore.addin.xml index 3dea9bc528..f79b5f284f 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/Properties/MonoDevelop.DotNetCore.addin.xml +++ b/main/src/addins/MonoDevelop.DotNetCore/Properties/MonoDevelop.DotNetCore.addin.xml @@ -109,6 +109,42 @@ condition="UseNetCore21=true" category="netcore/app/general"/> </Condition> + <Template + id="NUnit3.DotNetNew.Template.CSharp" + templateId="NUnit3.DotNetNew.Template.CSharp" + _overrideName="NUnit Test Project" + _overrideDescription="Creates a new NUnit test project." + path="${DotNetCoreSdk.2.1.Templates.NUnit3.DotNetNew.Template.nupkg}" + icon="md-netcore-test-project" + imageId="md-netcore-test-project" + wizard="MonoDevelop.DotNetCore.ProjectTemplateWizard" + condition="UseNetCore21=true" + category="netcore/test/general" /> + <Template + id="NUnit3.DotNetNew.Template.FSharp" + templateId="NUnit3.DotNetNew.Template.FSharp" + _overrideName="NUnit Test Project" + _overrideDescription="Creates a new NUnit test project." + path="${DotNetCoreSdk.2.1.Templates.NUnit3.DotNetNew.Template.nupkg}" + icon="md-netcore-test-project" + imageId="md-netcore-test-project" + wizard="MonoDevelop.DotNetCore.ProjectTemplateWizard" + condition="UseNetCore21=true" + category="netcore/test/general" /> + <Condition id="FeatureSwitch" name="VBNetDotnetCoreTemplates"> + <Template + id="NUnit3.DotNetNew.Template.VisualBasic" + templateId="NUnit3.DotNetNew.Template.VisualBasic" + _overrideName="NUnit Test Project" + _overrideDescription="Creates a new NUnit test project." + overrideLanguage="VBNet" + path="${DotNetCoreSdk.2.1.Templates.NUnit3.DotNetNew.Template.nupkg}" + icon="md-netcore-test-project" + imageId="md-netcore-test-project" + wizard="MonoDevelop.DotNetCore.ProjectTemplateWizard" + condition="UseNetCore21=true" + category="netcore/test/general" /> + </Condition> </Condition> <Condition id="DotNetCoreSdkInstalled" sdkVersion="2.2"> <Template @@ -973,7 +1009,7 @@ icon="md-netcore-test-project" imageId="md-netcore-test-project" wizard="MonoDevelop.DotNetCore.ProjectTemplateWizard" - condition="UseNetCore21=true" + condition="UseNetCore22=true" category="netcore/test/general" /> <Template id="NUnit3.DotNetNew.Template.FSharp" @@ -984,7 +1020,7 @@ icon="md-netcore-test-project" imageId="md-netcore-test-project" wizard="MonoDevelop.DotNetCore.ProjectTemplateWizard" - condition="UseNetCore21=true" + condition="UseNetCore22=true" category="netcore/test/general" /> <Condition id="FeatureSwitch" name="VBNetDotnetCoreTemplates"> <Template @@ -997,7 +1033,7 @@ icon="md-netcore-test-project" imageId="md-netcore-test-project" wizard="MonoDevelop.DotNetCore.ProjectTemplateWizard" - condition="UseNetCore21=true" + condition="UseNetCore22=true" category="netcore/test/general" /> </Condition> </Condition> diff --git a/main/src/addins/MonoDevelop.GtkCore/MonoDevelop.GtkCore.GuiBuilder/GuiBuilderService.cs b/main/src/addins/MonoDevelop.GtkCore/MonoDevelop.GtkCore.GuiBuilder/GuiBuilderService.cs index bb545fe25d..70c3b40850 100644 --- a/main/src/addins/MonoDevelop.GtkCore/MonoDevelop.GtkCore.GuiBuilder/GuiBuilderService.cs +++ b/main/src/addins/MonoDevelop.GtkCore/MonoDevelop.GtkCore.GuiBuilder/GuiBuilderService.cs @@ -218,7 +218,7 @@ namespace MonoDevelop.GtkCore.GuiBuilder if (!exists) { Pad p = IdeApp.Workbench.GetPad<MonoDevelop.DesignerSupport.ToolboxPad> (); if (p != null) p.Visible = true; - p = IdeApp.Workbench.GetPad<MonoDevelop.DesignerSupport.PropertyPad> (); + p = IdeApp.Workbench.GetPad<MonoDevelop.DesignerSupport.IPropertyPad> (); if (p != null) p.Visible = true; } } diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.csproj b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.csproj index 46ef173f35..a8dd71b6de 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.csproj +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.csproj @@ -199,6 +199,7 @@ <Compile Include="MonoDevelop.PackageManagement.Tests\ProjectReferenceMaintainerTests.cs" /> <Compile Include="MonoDevelop.PackageManagement.Tests\FullyQualifiedReferencePathTests.cs" /> <Compile Include="MonoDevelop.PackageManagement.Tests\MSBuildPackageSpecCreatorTests.cs" /> + <Compile Include="MonoDevelop.PackageManagement.Tests\InstallPackageWithAvailableItemNameTests.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\..\core\MonoDevelop.Core\MonoDevelop.Core.csproj"> diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/InstallPackageWithAvailableItemNameTests.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/InstallPackageWithAvailableItemNameTests.cs new file mode 100644 index 0000000000..c32ec699b7 --- /dev/null +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/InstallPackageWithAvailableItemNameTests.cs @@ -0,0 +1,74 @@ +// +// InstallPackageWithAvailableItemNameTests.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2019 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Linq; +using System.Threading.Tasks; +using MonoDevelop.PackageManagement.Tests.Helpers; +using MonoDevelop.Projects; +using NuGet.Versioning; +using NUnit.Framework; +using UnitTests; + +namespace MonoDevelop.PackageManagement.Tests +{ + [TestFixture] + public class InstallPackageWithAvailableItemNameTests : RestoreTestBase + { + [Test] + public async Task InstallPackage_PackageDefinesCustomAvailableItemNames_BuildActionsIncludeCustomAvailableItemNames () + { + string solutionFileName = Util.GetSampleProject ("RestoreStylePackageReference", "RestoreStylePackageReference.sln"); + using (solution = (Solution)await Services.ProjectService.ReadWorkspaceItem (Util.GetMonitor (), solutionFileName)) { + CreateNuGetConfigFile (solution.BaseDirectory); + var project = (DotNetProject)solution.FindProjectByName ("RestoreStylePackageReference"); + + var originalBuildActions = project.GetBuildActions (); + await InstallNuGetPackage (project, "Test.Xam.AvailableItemName", "0.1.0"); + var updatedBuildActions = project.GetBuildActions (); + + Assert.IsFalse (originalBuildActions.Contains ("TestXamAvailableItem")); + Assert.That (updatedBuildActions, Contains.Item ("TestXamAvailableItem")); + } + } + + Task InstallNuGetPackage (DotNetProject project, string packageId, string packageVersion) + { + var solutionManager = new MonoDevelopSolutionManager (project.ParentSolution); + var context = CreateNuGetProjectContext (solutionManager.Settings); + var sources = solutionManager.CreateSourceRepositoryProvider ().GetRepositories (); + + var action = new InstallNuGetPackageAction (sources, solutionManager, new DotNetProjectProxy (project), context); + action.LicensesMustBeAccepted = false; + action.OpenReadmeFile = false; + action.PackageId = packageId; + action.Version = NuGetVersion.Parse (packageVersion); + + return Task.Run (() => { + action.Execute (); + }); + } + } +} diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/PackageReferenceNuGetProjectTests.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/PackageReferenceNuGetProjectTests.cs index 2167100d23..23aaedc1fa 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/PackageReferenceNuGetProjectTests.cs +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/PackageReferenceNuGetProjectTests.cs @@ -24,12 +24,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using MonoDevelop.Core; using MonoDevelop.PackageManagement.Tests.Helpers; using MonoDevelop.Projects; +using NuGet.PackageManagement; using NuGet.Packaging.Core; using NuGet.ProjectManagement; using NuGet.ProjectModel; @@ -388,5 +390,45 @@ namespace MonoDevelop.PackageManagement.Tests int projectItemsCount = dotNetProject.Files.Count; Assert.AreEqual (1, projectItemsCount); } + + [Test] + public void OnAfterExecuteActions_PackageInstallAction_PackageInstalledEventFired () + { + CreateNuGetProject (); + var packageIdentity = new PackageIdentity ("Test", NuGetVersion.Parse ("1.2")); + var actions = new List<NuGetProjectAction> (); + var action = NuGetProjectAction.CreateInstallProjectAction (packageIdentity, null, project); + actions.Add (action); + PackageManagementEventArgs eventArgs = null; + project.PackageManagementEvents.PackageInstalled += (sender, e) => { + eventArgs = e; + }; + + project.OnAfterExecuteActions (actions); + + Assert.AreEqual ("Test", eventArgs.Id); + Assert.AreEqual ("1.2", eventArgs.Version.ToString ()); + Assert.AreEqual (packageIdentity, eventArgs.Package); + } + + [Test] + public void OnAfterExecuteActions_PackageUninstallAction_PackageUninstalledEventFired () + { + CreateNuGetProject (); + var packageIdentity = new PackageIdentity ("Test", NuGetVersion.Parse ("1.2")); + var actions = new List<NuGetProjectAction> (); + var action = NuGetProjectAction.CreateUninstallProjectAction (packageIdentity, project); + actions.Add (action); + PackageManagementEventArgs eventArgs = null; + project.PackageManagementEvents.PackageUninstalled += (sender, e) => { + eventArgs = e; + }; + + project.OnAfterExecuteActions (actions); + + Assert.AreEqual ("Test", eventArgs.Id); + Assert.AreEqual ("1.2", eventArgs.Version.ToString ()); + Assert.AreEqual (packageIdentity, eventArgs.Package); + } } } diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/PackageReferenceNuGetProject.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/PackageReferenceNuGetProject.cs index 575200ece5..4892485906 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/PackageReferenceNuGetProject.cs +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/PackageReferenceNuGetProject.cs @@ -51,19 +51,20 @@ namespace MonoDevelop.PackageManagement { DotNetProject project; ConfigurationSelector configuration; - IPackageManagementEvents packageManagementEvents; + PackageManagementEvents packageManagementEvents; string msbuildProjectPath; string projectName; + bool reevaluationRequired; public PackageReferenceNuGetProject (DotNetProject project, ConfigurationSelector configuration) - : this (project, configuration, PackageManagementServices.PackageManagementEvents) + : this (project, configuration, (PackageManagementEvents)PackageManagementServices.PackageManagementEvents) { } public PackageReferenceNuGetProject ( DotNetProject project, ConfigurationSelector configuration, - IPackageManagementEvents packageManagementEvents) + PackageManagementEvents packageManagementEvents) { this.project = project; this.configuration = configuration; @@ -241,13 +242,16 @@ namespace MonoDevelop.PackageManagement throw new InvalidOperationException (GettextCatalog.GetString ("Unable to create package spec for project. '{0}'", project.FileName)); } - public override Task PostProcessAsync (INuGetProjectContext nuGetProjectContext, CancellationToken token) + public override async Task PostProcessAsync (INuGetProjectContext nuGetProjectContext, CancellationToken token) { - Runtime.RunInMainThread (() => { + await Runtime.RunInMainThread (async () => { + if (reevaluationRequired) { + await DotNetProject.ReevaluateProject (new ProgressMonitor ()); + } DotNetProject.NotifyModified ("References"); }); - return base.PostProcessAsync (nuGetProjectContext, token); + await base.PostProcessAsync (nuGetProjectContext, token); } public void OnBeforeUninstall (IEnumerable<NuGetProjectAction> actions) @@ -256,6 +260,16 @@ namespace MonoDevelop.PackageManagement public void OnAfterExecuteActions (IEnumerable<NuGetProjectAction> actions) { + reevaluationRequired = actions.Any (action => action.NuGetProjectActionType == NuGetProjectActionType.Install); + + foreach (var action in actions) { + var eventArgs = new PackageEventArgs (this, action.PackageIdentity, null); + if (action.NuGetProjectActionType == NuGetProjectActionType.Install) { + packageManagementEvents.OnPackageInstalled (Project, eventArgs); + } else if (action.NuGetProjectActionType == NuGetProjectActionType.Uninstall) { + packageManagementEvents.OnPackageUninstalled (Project, eventArgs); + } + } } public void NotifyProjectReferencesChanged (bool includeTransitiveProjectReferences) diff --git a/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging/NuGetPackageMetadata.cs b/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging/NuGetPackageMetadata.cs index 572ef2910a..d786129336 100644 --- a/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging/NuGetPackageMetadata.cs +++ b/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging/NuGetPackageMetadata.cs @@ -70,6 +70,7 @@ namespace MonoDevelop.Packaging Id = GetProperty (propertyGroup, PackageIdPropertyName); Version = GetProperty (propertyGroup, "PackageVersion"); Authors = GetProperty (propertyGroup, "Authors"); + Copyright = GetProperty (propertyGroup, "Copyright"); DevelopmentDependency = GetProperty (propertyGroup, "DevelopmentDependency", false); IconUrl = GetProperty (propertyGroup, "PackageIconUrl"); Language = GetProperty (propertyGroup, "NeutralLanguage"); @@ -107,6 +108,7 @@ namespace MonoDevelop.Packaging SetProperty (propertyGroup, PackageIdPropertyName, Id); SetProperty (propertyGroup, "PackageVersion", Version); SetProperty (propertyGroup, "Authors", Authors); + SetProperty (propertyGroup, "Copyright", Copyright); SetProperty (propertyGroup, "DevelopmentDependency", DevelopmentDependency); SetProperty (propertyGroup, "PackageIconUrl", IconUrl); SetProperty (propertyGroup, "NeutralLanguage", Language); diff --git a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs index d2f924732c..c91f7683c1 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs @@ -1487,8 +1487,9 @@ namespace MonoDevelop.SourceEditor } else if (args.Button == 1) { if (!string.IsNullOrEmpty (Document.FileName)) { if (args.LineSegment != null) { - int column = TextEditor.Caret.Line == args.LineNumber ? TextEditor.Caret.Column : 1; - + int column = TextEditor.Caret.Line == args.LineNumber ? + Math.Min (TextEditor.Caret.Column, args.LineSegment.Length) : 1; + lock (breakpoints) breakpoints.Toggle (Document.FileName, args.LineNumber, column); } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj index 7d832ef65b..570f99ad9a 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj @@ -64,6 +64,7 @@ <Compile Include="Gui\MonoDevelop.VersionControl.Git.StashManagerDialog.cs" /> <Compile Include="Gui\MonoDevelop.VersionControl.Git.UserGitConfigDialog.cs" /> <Compile Include="Gui\MonoDevelop.VersionControl.Git.UserInfoConflictDialog.cs" /> + <Compile Include="MonoDevelop.VersionControl.Git\IGitCredentialsProvider.cs" /> <Compile Include="MonoDevelop.VersionControl.Git\XwtCredentialsDialog.cs" /> </ItemGroup> <ItemGroup> diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitCredentials.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitCredentials.cs index 3f20f8158c..acec1f971a 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitCredentials.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitCredentials.cs @@ -23,14 +23,16 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -using System; +using System; using MonoDevelop.Core; using MonoDevelop.Ide; using LibGit2Sharp; using System.IO; using System.Collections.Generic; using MonoDevelop.Components; +using System.Linq; +using Mono.Addins; namespace MonoDevelop.VersionControl.Git { @@ -120,9 +122,7 @@ namespace MonoDevelop.VersionControl.Git // if the password store contains an invalid password/no password if ((types & SupportedCredentialTypes.UsernamePassword) != 0) { if (Uri.TryCreate (url, UriKind.RelativeOrAbsolute, out uri)) { - string username; - string password; - if (!state.NativePasswordUsed && TryGetUsernamePassword (uri, out username, out password)) { + if (!state.NativePasswordUsed && TryGetUsernamePassword (uri, out var username, out var password)) { state.NativePasswordUsed = true; return new UsernamePasswordCredentials { Username = username, @@ -202,7 +202,25 @@ namespace MonoDevelop.VersionControl.Git return cred; } - if (XwtCredentialsDialog.Run (url, types, cred).Result) { + var gitCredentialsProviders = AddinManager.GetExtensionObjects<IGitCredentialsProvider> (); + + if (gitCredentialsProviders != null) { + foreach (var gitCredentialsProvider in gitCredentialsProviders) { + if (gitCredentialsProvider.SupportsUrl (url)) { + var providerResult = GetCredentialsFromProvider (gitCredentialsProvider, url, types, cred); + if (providerResult == GitCredentialsProviderResult.Cancelled) + throw new UserCancelledException (UserCancelledExceptionMessage); + if (result = providerResult == GitCredentialsProviderResult.Found) + break; + } + } + } + + if (!result) { + result = GetCredentials (url, types, cred); + } + + if (result) { if ((types & SupportedCredentialTypes.UsernamePassword) != 0) { var upcred = (UsernamePasswordCredentials)cred; if (!string.IsNullOrEmpty (upcred.Password) && uri != null) { @@ -215,6 +233,26 @@ namespace MonoDevelop.VersionControl.Git throw new UserCancelledException (UserCancelledExceptionMessage); } + static GitCredentialsProviderResult GetCredentialsFromProvider (IGitCredentialsProvider gitCredentialsProvider, string uri, SupportedCredentialTypes type, Credentials cred) + { + if (type != SupportedCredentialTypes.UsernamePassword) + return GitCredentialsProviderResult.NotFound; + + var (result, credentials) = gitCredentialsProvider.TryGetCredentialsAsync (uri).Result; + + if (result == GitCredentialsProviderResult.Found) { + ((UsernamePasswordCredentials)cred).Username = credentials.Username; + ((UsernamePasswordCredentials)cred).Password = credentials.Password; + } + + return result; + } + + static bool GetCredentials (string uri, SupportedCredentialTypes type, Credentials cred) + { + return XwtCredentialsDialog.Run (uri, type, cred).Result; + } + internal static bool KeyHasPassphrase (string key) { return File.ReadAllText (key).Contains ("Proc-Type: 4,ENCRYPTED"); @@ -235,6 +273,10 @@ namespace MonoDevelop.VersionControl.Git static bool TryGetUsernamePassword (Uri uri, out string username, out string password) { var cred = PasswordService.GetWebUserNameAndPassword (uri); + // if the Uri has a path, fallback to base Uri if available + if (cred == null && !string.IsNullOrEmpty (uri.PathAndQuery) && Uri.TryCreate (uri.GetLeftPart (UriPartial.Authority), UriKind.Absolute, out var baseUri)) { + cred = PasswordService.GetWebUserNameAndPassword (baseUri); + } if (cred != null) { username = cred.Item1; password = cred.Item2; diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitVersionControl.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitVersionControl.cs index 23b0c85e4b..eaa0f68658 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitVersionControl.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitVersionControl.cs @@ -32,7 +32,9 @@ namespace MonoDevelop.VersionControl.Git { abstract class GitVersionControl : VersionControlSystem {
- string version = null;
+ string version = null; + + const string GitExtension = ".git";
public override string Name { get { return "Git"; } @@ -72,10 +74,19 @@ namespace MonoDevelop.VersionControl.Git string repo = LibGit2Sharp.Repository.Discover (path.ResolveLinks ()); if (!string.IsNullOrEmpty (repo)) { repo = repo.TrimEnd ('\\', '/'); - if (repo.EndsWith (".git", System.StringComparison.OrdinalIgnoreCase)) + if (repo.EndsWith (GitExtension, System.StringComparison.OrdinalIgnoreCase)) repo = Path.GetDirectoryName (repo); } return repo; } +
+ public override string GetRelativeCheckoutPathForRemote (string remoteRelativePath) + {
+ remoteRelativePath = base.GetRelativeCheckoutPathForRemote (remoteRelativePath);
+ if (remoteRelativePath.EndsWith (GitExtension, System.StringComparison.CurrentCultureIgnoreCase)) { + remoteRelativePath = remoteRelativePath.Substring (0, remoteRelativePath.Length - GitExtension.Length); + } + return remoteRelativePath; + } } } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/IGitCredentialsProvider.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/IGitCredentialsProvider.cs new file mode 100644 index 0000000000..abf7bda887 --- /dev/null +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/IGitCredentialsProvider.cs @@ -0,0 +1,57 @@ +// +// IGitCredentialsProvider.cs +// +// Author: +// Javier Suárez Ruiz <jsuarez@microsoft.com> +// +// Copyright (c) 2019 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Threading.Tasks;
+using Mono.Addins; + +namespace MonoDevelop.VersionControl.Git +{ + [TypeExtensionPoint] + public interface IGitCredentialsProvider + { + bool SupportsUrl (string url); + Task<(GitCredentialsProviderResult result, GitCredential credentials)> TryGetCredentialsAsync (string url); + } + + public enum GitCredentialsProviderResult + { + Found, + NotFound, + Cancelled, + } + + public class GitCredential + { + public GitCredential (string username, string password) + { + Username = username; + Password = password; + } + + public string Username { get; } + public string Password { get; } + } +}
\ No newline at end of file diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Dialogs/SelectRepositoryDialog.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Dialogs/SelectRepositoryDialog.cs index 9df102723f..718adcd7ab 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Dialogs/SelectRepositoryDialog.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Dialogs/SelectRepositoryDialog.cs @@ -37,6 +37,8 @@ namespace MonoDevelop.VersionControl.Dialogs { Build (); + GtkWorkarounds.DisableMinimizeMaximizeButtons (this); + Modal = true; foreach (VersionControlSystem vcs in VersionControlService.GetVersionControlSystems ()) { if (vcs.IsInstalled) { repCombo.AppendText (vcs.Name); @@ -389,7 +391,7 @@ namespace MonoDevelop.VersionControl.Dialogs void AppendRelativePath () { - UrlBasedRepositoryEditor edit = currentEditor as UrlBasedRepositoryEditor; + var edit = currentEditor as UrlBasedRepositoryEditor; if (edit == null) return; @@ -399,7 +401,8 @@ namespace MonoDevelop.VersionControl.Dialogs return; } - entryFolder.Text = defaultPath + edit.RelativePath.Replace ('/', System.IO.Path.DirectorySeparatorChar); + var vcs = systems [repCombo.Active]; + entryFolder.Text = defaultPath + vcs.GetRelativeCheckoutPathForRemote (edit.RelativePath); } } } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/MergeView.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/MergeView.cs index 8d9d056455..848979ccbe 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/MergeView.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/MergeView.cs @@ -25,6 +25,7 @@ // THE SOFTWARE. using MonoDevelop.Components; using MonoDevelop.Core; +using System.Linq; namespace MonoDevelop.VersionControl.Views { @@ -34,36 +35,113 @@ namespace MonoDevelop.VersionControl.Views class MergeView : BaseView, IMergeView { - VersionControlDocumentInfo info; + readonly VersionControlDocumentInfo info; + readonly FileEventInfo fileEventInfo; MergeWidget widget; + readonly MergeWidgetContainer widgetContainer; + readonly Gtk.Label NoMergeConflictsLabel; - public override Control Control { - get { + public override Control Control => widgetContainer; + + public MergeView (VersionControlDocumentInfo info) : base (GettextCatalog.GetString ("Merge"), GettextCatalog.GetString ("Shows the merge view for the current file")) + { + this.info = info; + fileEventInfo = new FileEventInfo (info.Item.Path.FullPath, info.Item.IsDirectory); + widgetContainer = new MergeWidgetContainer (); + NoMergeConflictsLabel = new Gtk.Label () { Text = GettextCatalog.GetString ("No merge conflicts detected.") }; + FileService.FileChanged += FileService_FileChanged; + } + + void RefreshContent () + { + var isConflicted = info?.Item?.VersionInfo?.Status.HasFlag (VersionStatus.Conflicted) ?? false; + if (isConflicted) { if (widget == null) { widget = new MergeWidget (); widget.Load (info); } - - return widget; + if (widgetContainer.Content != widget) { + widgetContainer.Content = widget; + } + } else { + if (widgetContainer.Content != NoMergeConflictsLabel) { + widgetContainer.Content = NoMergeConflictsLabel; + } } } - public MergeView (VersionControlDocumentInfo info) : base (GettextCatalog.GetString ("Merge"), GettextCatalog.GetString ("Shows the merge view for the current file")) + void FileService_FileChanged (object sender, FileEventArgs e) { - this.info = info; + //content is null when is deselected + if (widgetContainer.Content == null) { + return; + } + + //continue only if file server detected some change in this file + if (e.All (s => s.FileName.CompareTo (fileEventInfo.FileName) < 0)) { + return; + } + + //if it is shown we refresh we show the content and refresh the editor (probably this nee + info.Start (); + RefreshContent (); + RefreshMergeEditor (); } protected override void OnSelected () { - widget.UpdateLocalText (); - widget.info.Start (); + info.Start (); + RefreshContent (); + RefreshMergeEditor (); + } - var buffer = info.Document.GetContent<MonoDevelop.Ide.Editor.TextEditor> (); - if (buffer != null) { - var loc = buffer.CaretLocation; - int line = loc.Line < 1 ? 1 : loc.Line; - int column = loc.Column < 1 ? 1 : loc.Column; - widget.MainEditor.SetCaretTo (line, column); + void RefreshMergeEditor () + { + if (widgetContainer.Content is MergeWidget) { + widget.UpdateLocalText (); + var buffer = info.Document.GetContent<MonoDevelop.Ide.Editor.TextEditor> (); + if (buffer != null) { + var loc = buffer.CaretLocation; + int line = loc.Line < 1 ? 1 : loc.Line; + int column = loc.Column < 1 ? 1 : loc.Column; + widget.MainEditor.SetCaretTo (line, column); + } + } + } + + void ClearContainer () => widgetContainer.Clear (); + + protected override void OnDeselected () => ClearContainer (); + + public override void Dispose () + { + ClearContainer (); + FileService.FileChanged -= FileService_FileChanged; + base.Dispose (); + } + + class MergeWidgetContainer : Gtk.VBox + { + Gtk.Widget content; + public Gtk.Widget Content { + get => content; + set { + if (content == value) { + return; + } + Clear (); + content = value; + PackStart (value, true, true, 0); + ShowAll (); + } + } + + public void Clear () + { + if (content != null) { + Remove (content); + content = null; + } } } } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlService.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlService.cs index 60484eb459..18f500f298 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlService.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlService.cs @@ -17,6 +17,7 @@ using Mono.Addins; using MonoDevelop.Ide; using MonoDevelop.Core.ProgressMonitoring; using MonoDevelop.Core.Instrumentation; +using System.Collections.Concurrent; namespace MonoDevelop.VersionControl { @@ -104,14 +105,23 @@ namespace MonoDevelop.VersionControl VersionControlSystem vcs; try { - vcs = (VersionControlSystem) args.ExtensionObject; + vcs = (VersionControlSystem)args.ExtensionObject; } catch (Exception e) { LoggingService.LogError ("Failed to initialize VersionControlSystem type.", e); return; } if (args.Change == ExtensionChange.Add) { - handlers.Add (vcs); + IComparer<VersionControlSystem> compare = new CompareVersionControlSystem (); + + int search = handlers.BinarySearch (vcs, compare); + + if (search < 0) + handlers.Insert (~search, vcs); + else { + LoggingService.LogError ("Adding new version control system {0} failed, the name {1} is already reserved.", vcs.GetType ().Name, vcs.Name); + return; + } try { // Include the repository type in the serialization context, so repositories // of this type can be deserialized from the configuration file. @@ -122,8 +132,7 @@ namespace MonoDevelop.VersionControl } catch (Exception e) { LoggingService.LogError ("Error while adding version control system.", e); } - } - else { + } else { handlers.Remove (vcs); } } @@ -202,7 +211,7 @@ namespace MonoDevelop.VersionControl return String.Empty;
} - internal static Dictionary<Repository, InternalRepositoryReference> referenceCache = new Dictionary<Repository, InternalRepositoryReference> (); + internal static ConcurrentDictionary<Repository, InternalRepositoryReference> referenceCache = new ConcurrentDictionary<Repository, InternalRepositoryReference> (); public static Repository GetRepository (WorkspaceObject entry) { if (IsGloballyDisabled) @@ -216,17 +225,14 @@ namespace MonoDevelop.VersionControl InternalRepositoryReference rref = null; if (repo != null) { repo.AddRef (); - if (!referenceCache.TryGetValue (repo, out rref)) { - rref = new InternalRepositoryReference (repo); - referenceCache [repo] = rref; - } + rref = referenceCache.GetOrAdd (repo, r => new InternalRepositoryReference (r)); } entry.ExtendedProperties [typeof(InternalRepositoryReference)] = rref; return repo; } - internal static readonly Dictionary<FilePath,Repository> repositoryCache = new Dictionary<FilePath,Repository> (); + internal static readonly ConcurrentDictionary<FilePath,Repository> repositoryCache = new ConcurrentDictionary<FilePath,Repository> (); public static Repository GetRepositoryReference (string path, string id) { VersionControlSystem detectedVCS = null; @@ -249,18 +255,21 @@ namespace MonoDevelop.VersionControl } bestMatch = bestMatch.CanonicalPath; - if (repositoryCache.TryGetValue (bestMatch, out var repository)) - return repository; try { - var repo = detectedVCS?.GetRepositoryReference (bestMatch, id); - if (repo != null) { - repositoryCache.Add (bestMatch, repo); - Instrumentation.Repositories.Inc (new RepositoryMetadata (detectedVCS)); - } - return repo; + return repositoryCache.GetOrAdd (bestMatch, p => { + var result = detectedVCS?.GetRepositoryReference (p, id); + if (result != null) { + Instrumentation.Repositories.Inc (new RepositoryMetadata (detectedVCS)); + return result; + } + // never add null values + throw new ArgumentNullException ("result"); + }); } catch (Exception e) { - LoggingService.LogError ($"Could not query {detectedVCS.Name} repository reference", e); + // ArgumentNullException for "result" is expected when GetRepositoryReference returns null, no need to log + if (!(e is ArgumentNullException ne) || ne.ParamName != "result") + LoggingService.LogInternalError ($"Could not query {detectedVCS.Name} repository reference", e); return null; } } @@ -824,8 +833,8 @@ namespace MonoDevelop.VersionControl public void Dispose () { - VersionControlService.referenceCache.Remove (repo); - VersionControlService.repositoryCache.Remove (repo.RootPath.CanonicalPath); + VersionControlService.referenceCache.TryRemove (repo, out _); + VersionControlService.repositoryCache.TryRemove (repo.RootPath.CanonicalPath, out _); repo.Unref (); } } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlSystem.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlSystem.cs index 43a54eb653..7c77fe5a88 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlSystem.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlSystem.cs @@ -1,3 +1,5 @@ +using System;
+using System.Collections.Generic; using MonoDevelop.Core; namespace MonoDevelop.VersionControl @@ -28,9 +30,9 @@ namespace MonoDevelop.VersionControl /// The default implementation returns the full name of the class. /// </remarks> public virtual string Id { - get { return GetType().ToString(); } + get { return GetType ().ToString (); } } - + /// <summary> /// Display name of the version control system /// </summary> @@ -54,12 +56,12 @@ namespace MonoDevelop.VersionControl public virtual bool IsInstalled { get { return false; } } - + /// <summary> /// Creates an instance of a repository for this version control system /// </summary> protected abstract Repository OnCreateRepositoryInstance (); - + /// <summary> /// Creates an editor object for a repository. /// </summary> @@ -69,7 +71,7 @@ namespace MonoDevelop.VersionControl /// <param name='repo'> /// A repository /// </param> - public abstract IRepositoryEditor CreateRepositoryEditor (Repository repo);
+ public abstract IRepositoryEditor CreateRepositoryEditor (Repository repo); /// <summary> /// Gets a repository for a given local path and identifier @@ -111,5 +113,37 @@ namespace MonoDevelop.VersionControl /// <param name="path">The path to start the repository detection from.</param> /// <param name="id">An identifier. This identifier is generated by MD and normally identifiers a project.</param> protected abstract FilePath OnGetRepositoryPath (FilePath path, string id); + + /// <summary>
+ /// Gets the output directory path.
+ /// </summary>
+ /// <returns>Returns the relative path based on the remote path.</returns>
+ /// <param name="remoteRelativePath">Remote Relative Path.</param>
+ public virtual string GetRelativeCheckoutPathForRemote (string remoteRelativePath) + { + return remoteRelativePath.Replace ('/', System.IO.Path.DirectorySeparatorChar); + } + } + + public class CompareVersionControlSystem : IComparer<VersionControlSystem> + { + public int Compare (VersionControlSystem vcs1, VersionControlSystem vcs2) + { + int result; + + if (ReferenceEquals (vcs1, vcs2)) { + result = 0; + } else { + if (vcs1 is null) { + result = 1; + } else if (vcs2 is null) { + result = -1; + } else { + result = string.Compare(vcs1.Name, vcs2.Name, StringComparison.InvariantCultureIgnoreCase); + } + } + + return result; + } } -} +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs index 5eb20c4b65..81221d827b 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs @@ -334,8 +334,6 @@ namespace MonoDevelop.Core public static void NotifyFilesChanged (IEnumerable<FilePath> files, bool autoReload) { try { - foreach (var fsFiles in files.GroupBy (f => GetFileSystemForPath (f, false))) - fsFiles.Key.NotifyFilesChanged (fsFiles); OnFileChanged (new FileEventArgs (files, false)); } catch (Exception ex) { LoggingService.LogError ("File change notification failed", ex); @@ -1015,6 +1013,16 @@ namespace MonoDevelop.Core foreach (var ev in pendingProcess.Events) ev.Invoke (); }).Ignore (); + + Task.Run (() => { + foreach (var ev in pendingProcess.Events) { + if (!(ev is FileEventData fev) || fev.Kind != FileService.EventDataKind.Changed) + continue; + + foreach (var fsFiles in fev.Args.GroupBy (f => FileService.GetFileSystemForPath (f.FileName, false))) + fsFiles.Key.NotifyFilesChanged (fsFiles.Select (x => x.FileName)); + } + }).Ignore (); } public void RaiseEvent (FileService.EventDataKind kind, FileEventArgs args) @@ -1030,6 +1038,11 @@ namespace MonoDevelop.Core } } + if (kind == FileService.EventDataKind.Changed) { + foreach (var fsFiles in args.GroupBy (f => FileService.GetFileSystemForPath (f.FileName, false))) + fsFiles.Key.NotifyFilesChanged (fsFiles.Select (x => x.FileName)); + } + if (Runtime.IsMainThread) { RaiseSync (kind, args); } else { @@ -1332,17 +1345,24 @@ namespace MonoDevelop.Core internal void OnFileCreated (FileEventArgs args) { - FileCreated?.Invoke (this, args); + FileCreated?.Invoke (this, Clone (args)); } internal void OnFileRemoved (FileEventArgs args) { - FileRemoved?.Invoke (this, args); + FileRemoved?.Invoke (this, Clone (args)); } internal void OnFileRenamed (FileCopyEventArgs args) { - FileRenamed?.Invoke (this, args); + FileRenamed?.Invoke (this, Clone (args)); + } + + static T Clone<T> (T args) where T : FileEventArgs, new() + { + var result = new T (); + result.AddRange (args); + return result; } } diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngine.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngine.cs index 44b6f9dda3..ccf066aed9 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngine.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngine.cs @@ -171,7 +171,8 @@ namespace MonoDevelop.Projects.MSBuild try { await connection.SendMessage (new UnloadProjectRequest { ProjectId = projectId }).ConfigureAwait (false); } catch (Exception ex) { - LoggingService.LogError ("Project unloading failed", ex); + if (alive) + LoggingService.LogError ("Project unloading failed", ex); if (!await CheckDisconnected ()) throw; } diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.SharedAssetsProjects/SharedAssetsProject.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.SharedAssetsProjects/SharedAssetsProject.cs index 447484027d..5c208ca77f 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.SharedAssetsProjects/SharedAssetsProject.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.SharedAssetsProjects/SharedAssetsProject.cs @@ -107,8 +107,16 @@ namespace MonoDevelop.Projects.SharedAssetsProjects base.OnReadProject (monitor, msproject); var import = msproject.Imports.FirstOrDefault (im => im.Label == "Shared"); - if (import == null) + if (import == null) { + // Sanity check. + if (!StringComparer.OrdinalIgnoreCase.Equals (msproject.FileName.Extension, FileName.Extension)) { + // ProjectTypeGuid mismatch in solution file. + throw new InvalidOperationException (GettextCatalog.GetString ( + "Project {0} is being loaded as a Shared Assets project but has a different file extension. Please check the project type GUID in the solution file is correct.", + Name)); + } return; + } // TODO: load the type from msbuild foreach (var item in msproject.Imports) { @@ -183,7 +191,7 @@ namespace MonoDevelop.Projects.SharedAssetsProjects msproject.AddNewImport (@"$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props"); import = msproject.AddNewImport (MSBuildProjectService.ToMSBuildPath (FileName.ParentDirectory, projItemsPath)); import.Label = "Shared"; - if (LanguageName.Equals("C#", StringComparison.OrdinalIgnoreCase)) { + if (LanguageName == null || LanguageName.Equals("C#", StringComparison.OrdinalIgnoreCase)) { msproject.AddNewImport (CSharptargets); } else if (LanguageName.Equals("F#", StringComparison.OrdinalIgnoreCase)) { diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/AsyncCriticalSection.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/AsyncCriticalSection.cs index 98c6874003..78d8b18fa5 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/AsyncCriticalSection.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/AsyncCriticalSection.cs @@ -67,7 +67,13 @@ namespace MonoDevelop.Projects locked = true; return Task.FromResult (criticalSectionDisposer); } - var s = new TaskCompletionSource<IDisposable> (); + + // When the TaskCompletionSource's SetResult method is called then all the async continuations waiting + // on this lock may be invoked synchronously. This can cause a stack overflow if many tasks are queued. + // To avoid this the continuations are run asynchronously by creating the TaskCompletionSource with + // TaskCreationOptions.RunContinuationsAsynchronously. + // https://stackoverflow.com/questions/28321457/taskcontinuationoptions-runcontinuationsasynchronously-and-stack-dives + var s = new TaskCompletionSource<IDisposable> (TaskCreationOptions.RunContinuationsAsynchronously); queue.Enqueue (s); return s.Task; } diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProjectConfiguration.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProjectConfiguration.cs index 5b77e8be8e..2cf1311924 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProjectConfiguration.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProjectConfiguration.cs @@ -63,6 +63,7 @@ namespace MonoDevelop.Projects assembly = pset.GetValue ("AssemblyName"); signAssembly = pset.GetValue<bool> ("SignAssembly"); delaySign = pset.GetValue<bool> ("DelaySign"); + PublicSign = pset.GetValue<bool> (nameof(PublicSign)); assemblyKeyFile = pset.GetPathValue ("AssemblyOriginatorKeyFile", FilePath.Empty); if (string.IsNullOrEmpty (assemblyKeyFile)) assemblyKeyFile = pset.GetPathValue ("AssemblyKeyFile", FilePath.Empty); @@ -76,6 +77,7 @@ namespace MonoDevelop.Projects pset.SetValue ("AssemblyName", assembly, mergeToMainGroup: true); pset.SetValue ("SignAssembly", signAssembly, defaultValue:false, mergeToMainGroup: true); pset.SetValue ("DelaySign", delaySign, defaultValue:false, mergeToMainGroup:true); + pset.SetValue (nameof(PublicSign), PublicSign, defaultValue: false, mergeToMainGroup: true); pset.SetValue ("AssemblyOriginatorKeyFile", assemblyKeyFile, defaultValue:FilePath.Empty, mergeToMainGroup:true); if (compilationParameters != null) compilationParameters.Write (pset); @@ -93,6 +95,8 @@ namespace MonoDevelop.Projects set { delaySign = value; } } + public bool PublicSign { get; set; } + internal string OldAssemblyKeyFile { set { assemblyKeyFile = value; } } diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs index 2fcd862be2..d5599be9d8 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs @@ -2772,6 +2772,9 @@ namespace MonoDevelop.Projects // Read available item types loadedAvailableItemNames = msproject.EvaluatedItems.Where (i => i.Name == "AvailableItemName").Select (i => i.Include).ToArray (); + + // Ensure buildActions are refreshed if loadedAvailableItemNames have been updated. + buildActions = null; } List<ConfigData> GetConfigData (MSBuildProject msproject, bool includeEvaluated) diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/WorkspaceObject.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/WorkspaceObject.cs index e16b5ec29a..459a5b2359 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/WorkspaceObject.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/WorkspaceObject.cs @@ -99,8 +99,11 @@ namespace MonoDevelop.Projects public Task<T> BindTask<T> (Func<CancellationToken, Task<T>> f) { var t = f (disposeCancellation.Token); - lock (activeTasks) + lock (activeTasks) { + if (disposeCancellation.IsCancellationRequested) + return Task.FromCanceled<T> (disposeCancellation.Token); activeTasks.Add (t); + } t.ContinueWith (tr => { lock (activeTasks) activeTasks.Remove (t); @@ -118,8 +121,11 @@ namespace MonoDevelop.Projects public Task BindTask (Func<CancellationToken, Task> f) { var t = f (disposeCancellation.Token); - lock (activeTasks) + lock (activeTasks) { + if (disposeCancellation.IsCancellationRequested) + return Task.FromCanceled (disposeCancellation.Token); activeTasks.Add (t); + } t.ContinueWith (tr => { lock (activeTasks) activeTasks.Remove (t); @@ -128,6 +134,22 @@ namespace MonoDevelop.Projects } /// <summary> + /// Gathers all the tasks for this WorkspaceObject and all children that need to finish before + /// this object can be disposed. + /// </summary> + internal void GetAllActiveTasksForDispose (List<Task> tasks) + { + lock (activeTasks) { + disposeCancellation.Cancel (); + tasks.AddRange (activeTasks); + } + + foreach (var child in GetChildren ()) { + child.GetAllActiveTasksForDispose (tasks); + } + } + + /// <summary> /// Gets a value indicating whether this instance is shared. /// </summary> /// <remarks>Shared objects can only be modified in the main thread</remarks> @@ -210,14 +232,10 @@ namespace MonoDevelop.Projects Disposed = true; - disposeCancellation.Cancel (); - - Task[] allTasks; - - lock (activeTasks) - allTasks = activeTasks.ToArray (); + var allTasks = new List<Task> (); + GetAllActiveTasksForDispose (allTasks); - if (allTasks.Length > 0) + if (allTasks.Count > 0) Task.WhenAll (allTasks).ContinueWith (t => OnDispose (), TaskScheduler.FromCurrentSynchronizationContext ()); else OnDispose (); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AtkCocoaHelper/AtkCocoaHelperMac.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AtkCocoaHelper/AtkCocoaHelperMac.cs index 60314a14b0..64150d0e48 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AtkCocoaHelper/AtkCocoaHelperMac.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AtkCocoaHelper/AtkCocoaHelperMac.cs @@ -311,6 +311,7 @@ namespace MonoDevelop.Components.AtkCocoaHelper } static readonly IntPtr selSetAccessibilityServesAsTitleForUIElements_Handle = Selector.GetHandle ("setAccessibilityServesAsTitleForUIElements:"); + static readonly IntPtr selAccessibilityServesAsTitleForUIElements_Handle = Selector.GetHandle ("accessibilityServesAsTitleForUIElements:"); public static void SetTitleFor (this Atk.Object o, params Atk.Object [] objects) { var nsa = GetNSAccessibilityElement (o); @@ -348,7 +349,6 @@ namespace MonoDevelop.Components.AtkCocoaHelper } } - static readonly IntPtr selAccessibilityServesAsTitleForUIElements_Handle = Selector.GetHandle ("setAccessibilityServesAsTitleForUIElements:"); public static void AddElementToTitle (this Atk.Object title, Atk.Object o) { var titleNsa = GetNSAccessibilityElement (title); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid/IPropertyGrid.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid/IPropertyGrid.cs new file mode 100644 index 0000000000..802c392dbc --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid/IPropertyGrid.cs @@ -0,0 +1,49 @@ +/*
+ * PropertyGrid.cs - A Gtk# widget that displays and allows
+ * editing of all of an object's public properties
+ *
+ * Authors:
+ * Michael Hutchinson <m.j.hutchinson@gmail.comk>
+ * Eric Butler <eric@extremeboredom.net> + * Lluis Sanchez Gual <lluis@novell.com>
+ *
+ * Copyright (C) 2005 Michael Hutchinson
+ * Copyright (C) 2005 Eric Butler
+ * Copyright (C) 2007 Novell, Inc (http://www.novell.com)
+ *
+ * This sourcecode is licenced under The MIT License:
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */ + +using System; + +namespace MonoDevelop.Components +{ + public interface IPropertyGrid : IDisposable
+ {
+ bool IsEditing { get; }
+ void SetToolbarProvider (PropertyGrid.PropertyGrid.IToolbarProvider toolbarProvider);
+ void SetCurrentObject (object obj, object [] propertyProviders);
+ void BlankPad (); + void OnPadContentShown ();
+ void Populate (bool saveEditSession); + }
+} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid/PropertyGrid.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid/PropertyGrid.cs index 3a23f70e0f..9d2330cbe2 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid/PropertyGrid.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid/PropertyGrid.cs @@ -51,7 +51,7 @@ namespace MonoDevelop.Components.PropertyGrid { [System.ComponentModel.Category("MonoDevelop.Components")]
[System.ComponentModel.ToolboxItem(true)]
- public class PropertyGrid: Gtk.VBox + public class PropertyGrid: Gtk.VBox, IPropertyGrid { object currentObject; object[] propertyProviders;
@@ -288,11 +288,11 @@ namespace MonoDevelop.Components.PropertyGrid QueueDraw (); }
- internal bool IsEditing { + public bool IsEditing { get { return tree.IsEditing; } }
- internal void Populate (bool saveEditSession) + public void Populate (bool saveEditSession) { PropertyDescriptorCollection properties; @@ -422,7 +422,14 @@ namespace MonoDevelop.Components.PropertyGrid descTitle = descText = null;
UpdateHelp ();
}
-
+ + public void BlankPad () => CurrentObject = null; + + public void OnPadContentShown () + { + //not implemented + } + public interface IToolbarProvider
{
void Insert (Widget w, int pos);
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Mac/NativeViewHelper.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Mac/NativeViewHelper.cs index cc7255ad44..f5c475a683 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Mac/NativeViewHelper.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Mac/NativeViewHelper.cs @@ -34,6 +34,22 @@ namespace MonoDevelop.Components.Mac { static class NativeViewHelper { + public static NSStackView CreateVerticalStackView (int spacing = 10, bool translatesAutoresizingMaskIntoConstraints = false) => new NSStackView () { + Orientation = NSUserInterfaceLayoutOrientation.Vertical, + Alignment = NSLayoutAttribute.Leading, + Spacing = spacing, + Distribution = NSStackViewDistribution.Fill, + TranslatesAutoresizingMaskIntoConstraints = translatesAutoresizingMaskIntoConstraints + }; + + public static NSStackView CreateHorizontalStackView (int spacing = 10) => new NSStackView () { + Orientation = NSUserInterfaceLayoutOrientation.Horizontal, + Alignment = NSLayoutAttribute.CenterY, + Spacing = spacing, + Distribution = NSStackViewDistribution.Fill, + TranslatesAutoresizingMaskIntoConstraints = false + }; + public static NSAttributedString GetAttributedStringFromFormattedText (string formattedText) { formattedText = formattedText.Replace ("&", "&"); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs index 02d8da925c..e735c48d29 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs @@ -110,7 +110,7 @@ namespace MonoDevelop.Ide.Commands protected override void Run () { using (var dlg = new NewFileDialog (null, null)) // new file seems to fail if I pass the project IdeApp.ProjectOperations.CurrentSelectedProject - MessageService.ShowCustomDialog (dlg, IdeApp.Workbench.RootWindow); + MessageService.ShowCustomDialog (dlg, DesktopService.GetFocusedTopLevelWindow ()); } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ViewCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ViewCommands.cs index 4e5121d2b4..eb7b7d43d9 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ViewCommands.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ViewCommands.cs @@ -222,6 +222,7 @@ namespace MonoDevelop.Ide.Commands info.Text = IdeApp.Workbench.FullScreen ? GettextCatalog.GetString ("Exit Full Screen") : GettextCatalog.GetString ("Enter Full Screen"); + info.Enabled = IdeApp.Workbench.RootWindow.Visible && !WelcomePage.WelcomePageService.WelcomeWindowVisible; } else if (Platform.IsWindows) { //this is currently a no-op on Windows as it's broken, so hide it info.Visible = info.Enabled = false; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/ProgressDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/ProgressDialog.cs index 578ee869a2..63bec17924 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/ProgressDialog.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/ProgressDialog.cs @@ -57,8 +57,6 @@ namespace MonoDevelop.Ide.Gui.Dialogs ActionArea.Hide (); DefaultHeight = 5; - TransientFor = parent.nativeWidget as Gtk.Window; - btnCancel.Visible = allowCancel; expander.Visible = showDetails; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ProgressMonitoring/MessageDialogProgressMonitor.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ProgressMonitoring/MessageDialogProgressMonitor.cs index 3e606b20c7..74e88fd5da 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ProgressMonitoring/MessageDialogProgressMonitor.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ProgressMonitoring/MessageDialogProgressMonitor.cs @@ -57,12 +57,19 @@ namespace MonoDevelop.Ide.ProgressMonitoring { } - public MessageDialogProgressMonitor (bool showProgress, bool allowCancel, bool showDetails, bool hideWhenDone): base (Runtime.MainSynchronizationContext) + public MessageDialogProgressMonitor (bool showProgress, bool allowCancel, bool showDetails, bool hideWhenDone) + : this (showProgress, allowCancel, showDetails, hideWhenDone, null) + { + } + + public MessageDialogProgressMonitor (bool showProgress, bool allowCancel, bool showDetails, bool hideWhenDone, Components.Window parent) + : base (Runtime.MainSynchronizationContext) { if (showProgress) { - dialog = new ProgressDialog (MessageService.RootWindow, allowCancel, showDetails); + var parentWindow = parent ?? DesktopService.GetFocusedTopLevelWindow (); + dialog = new ProgressDialog (parentWindow, allowCancel, showDetails); dialog.Message = ""; - MessageService.PlaceDialog (dialog, MessageService.RootWindow); + MessageService.PlaceDialog (dialog, parentWindow); dialog.Show (); dialog.CancellationTokenSource = CancellationTokenSource; DispatchService.RunPendingEvents (); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/NewFileDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/NewFileDialog.cs index 1b1774908d..d60e6866f5 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/NewFileDialog.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/NewFileDialog.cs @@ -75,7 +75,6 @@ namespace MonoDevelop.Ide.Projects this.basePath = basePath; BorderWidth = 6; - TransientFor = IdeApp.Workbench.RootWindow; HasSeparator = false; InitializeComponents (); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.MetadataReferenceHandler.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.MetadataReferenceHandler.cs index 999d6d6164..ef203184d9 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.MetadataReferenceHandler.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.MetadataReferenceHandler.cs @@ -1,4 +1,4 @@ -// +// // MonoDevelopWorkspace.MetadataReferenceHandler.cs // // Author: @@ -92,9 +92,6 @@ namespace MonoDevelop.Ide.TypeSystem if (!await AddMetadataAssemblyReferences (data)) return ImmutableArray<MonoDevelopMetadataReference>.Empty; - - if (!AddMetadataProjectReferences (data)) - return ImmutableArray<MonoDevelopMetadataReference>.Empty; return data.Result.ToImmutableArray (); } @@ -112,6 +109,9 @@ namespace MonoDevelop.Ide.TypeSystem try { var referencedAssemblies = await data.Project.GetReferencedAssemblies (data.ConfigurationSelector, false).ConfigureAwait (false); foreach (var file in referencedAssemblies) { + if (file.IsProjectReference) + continue; + if (data.Token.IsCancellationRequested) return false; @@ -132,34 +132,6 @@ namespace MonoDevelop.Ide.TypeSystem } } - bool AddMetadataProjectReferences (AddMetadataReferencesData data) - { - try { - var referencedProjects = data.Project.GetReferencedItems (data.ConfigurationSelector); - foreach (var pr in referencedProjects) { - if (data.Token.IsCancellationRequested) - return false; - - if (!(pr is MonoDevelop.Projects.DotNetProject referencedProject) || !IdeApp.TypeSystemService.IsOutputTrackedProject (referencedProject)) - continue; - - var fileName = referencedProject.GetOutputFileName (data.ConfigurationSelector); - if (!data.Visited.Add (fileName)) - continue; - - var metadataReference = manager.GetOrCreateMetadataReference (fileName, MetadataReferenceProperties.Assembly); - if (metadataReference != null) - data.Result.Add (metadataReference); - } - } catch (Exception e) { - LoggingService.LogError ("Error while getting referenced assemblies", e); - // TODO: Check whether this should return false, I retained compat for now. - return true; - } - - return true; - } - async Task<ImmutableArray<ProjectReference>> CreateProjectReferences (MonoDevelop.Projects.Project p, CancellationToken token) { if (!(p is MonoDevelop.Projects.DotNetProject netProj)) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/WelcomePageCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/WelcomePageCommands.cs index 8ba73b2f08..d365140109 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/WelcomePageCommands.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/WelcomePageCommands.cs @@ -47,6 +47,8 @@ namespace MonoDevelop.Ide.WelcomePage protected override void Update (CommandInfo info) { info.Text = WelcomePageService.HasWindowImplementation ? GettextCatalog.GetString ("Start Window") : GettextCatalog.GetString ("Welcome Page"); + info.Enabled = (WelcomePageService.HasWindowImplementation && !WelcomePageService.WelcomeWindowVisible) + || (!WelcomePageService.HasWindowImplementation && !WelcomePageService.WelcomePageVisible); base.Update (info); } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj index 2e8725280a..6550abf5a3 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj @@ -3168,7 +3168,6 @@ <Compile Include="MonoDevelop.Ide.Gui\Document.cs" /> <Compile Include="MonoDevelop.Ide.Gui\Pad.cs" /> <Compile Include="MonoDevelop.Ide.Gui\Workbench.cs" /> - <Compile Include="MonoDevelop.Ide.Gui\StartupInfo.cs" /> <Compile Include="MonoDevelop.Ide.Gui\ProgressMonitors.cs" /> <Compile Include="MonoDevelop.Ide.TypeSystem\IMonoDevelopHostDocument.cs" /> <Compile Include="MonoDevelop.Ide.TypeSystem\MonoDevelopPersistentStorageLocationService.cs" /> @@ -4237,6 +4236,10 @@ <Compile Include="MonoDevelop.Ide.Projects\NewSolutionRunConfigurationDialog.cs" /> <Compile Include="MonoDevelop.Ide.TypeSystem\HackyWorkspaceFilesCache.cs" /> <Compile Include="MonoDevelop.Ide.Gui.Dialogs\NewFolderDialog.cs" /> + <Compile Include="MonoDevelop.Components.PropertyGrid\IPropertyGrid.cs" /> + <Compile Include="MonoDevelop.Ide\MonoDevelopOptions.cs" /> + <Compile Include="MonoDevelop.Ide\AddinError.cs" /> + <Compile Include="MonoDevelop.Ide.Extensions\StartupInfo.cs" /> </ItemGroup> <ItemGroup> <Data Include="options\DefaultEditingLayout.xml" /> diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Ide.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Ide.cs index 3c9fc3d78d..b09ed7619e 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Ide.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Ide.cs @@ -1,4 +1,4 @@ -// +// // IdeApp.cs // // Author: @@ -53,6 +53,7 @@ using MonoDevelop.Ide.TextEditing; using MonoDevelop.Ide.Navigation; using MonoDevelop.Ide.Fonts; using MonoDevelop.Ide.Composition; +using System.Diagnostics; namespace MonoDevelop.Ide { @@ -98,8 +99,23 @@ namespace MonoDevelop.Ide }); } } - internal static void OnStartupCompleted () + + static TimeToCodeMetadata ttcMetadata; + static Stopwatch ttcStopwatch; + static long startupCompletedTicks; + static long ttcDuration = 3 * TimeSpan.TicksPerSecond; // Wait 3 seconds before ignoring TTC events + + internal static void OnStartupCompleted (StartupMetadata startupMetadata, Stopwatch ttcTimer) { + ttcMetadata = new TimeToCodeMetadata { + StartupTime = startupMetadata.CorrectedStartupTime + }; + ttcMetadata.AddProperties (startupMetadata); + + ttcStopwatch = ttcTimer; + startupCompletedTicks = ttcStopwatch.ElapsedTicks; + LoggingService.LogDebug ("TTC starting"); + startupCompleted?.Invoke (null, EventArgs.Empty); } @@ -177,20 +193,6 @@ namespace MonoDevelop.Ide } } - // This flag tells us whether or not the solution being loaded was from the file manager. - static bool reportTimeToCode; - static bool fmTimeoutExpired; - public static bool ReportTimeToCode { - get => reportTimeToCode && !fmTimeoutExpired; - set { - reportTimeToCode = value; - if (fmTimeoutId > 0) { - GLib.Source.Remove (fmTimeoutId); - fmTimeoutId = 0; - } - } - } - public static async Task Initialize (ProgressMonitor monitor) { // Already done in IdeSetup, but called again since unit tests don't use IdeSetup. @@ -325,20 +327,45 @@ namespace MonoDevelop.Ide Ide.IdeApp.Workbench.StatusBar.ShowWarning (e.Message); } - static readonly uint fmTimeoutMs = 2500; - static uint fmTimeoutId; - internal static void StartFMOpenTimer (Action timeCompletion) + internal static void TrackTimeToCode (TimeToCodeMetadata.DocumentType documentType) { - // We only track time to code if the reportTimeToCode flag is set within fmTimeoutMs from this method being called - fmTimeoutId = GLib.Timeout.Add (fmTimeoutMs, () => FMOpenTimerExpired (timeCompletion)); + LoggingService.LogDebug("Tracking TTC"); + if (ttcStopwatch == null || timeToCodeSolutionTimer == null) { + LoggingService.LogDebug("Ignoring TTC"); + return; + } + + ttcStopwatch.Stop (); + timeToCodeSolutionTimer.Stop (); + + if (ttcMetadata == null) { + timeToCodeSolutionTimer = null; + ttcStopwatch = null; + throw new Exception ("SendTimeToCode called before initialisation completed"); + } + + LoggingService.LogDebug ("Processing TTC"); + ttcMetadata.SolutionLoadTime = timeToCodeSolutionTimer.ElapsedMilliseconds; + + ttcMetadata.CorrectedDuration = ttcStopwatch.ElapsedMilliseconds; + ttcMetadata.Type = documentType; + + Counters.TimeToCode.Inc ("SolutionLoaded", ttcMetadata); + + timeToCodeSolutionTimer = null; } - static bool FMOpenTimerExpired (Action timeCompletion) + static Stopwatch timeToCodeSolutionTimer = new Stopwatch (); + internal static bool StartTimeToCodeLoadTimer () { - fmTimeoutExpired = true; - fmTimeoutId = 0; - timeCompletion (); - return false; + if (ttcStopwatch.ElapsedTicks - startupCompletedTicks > ttcDuration) { + LoggingService.LogDebug ($"Not starting TTC timer: {ttcStopwatch.ElapsedTicks - startupCompletedTicks}"); + return false; + } + LoggingService.LogDebug ("Starting TTC timer"); + timeToCodeSolutionTimer.Start (); + + return true; } public static void BringToFront () @@ -356,10 +383,10 @@ namespace MonoDevelop.Ide } //this method is MIT/X11, 2009, Michael Hutchinson / (c) Novell - internal static async void OpenFiles (IEnumerable<FileOpenInformation> files, OpenWorkspaceItemMetadata metadata) + internal static async Task<bool> OpenFiles (IEnumerable<FileOpenInformation> files, OpenWorkspaceItemMetadata metadata) { if (!files.Any ()) - return; + return false; if (!IsInitialized) { EventHandler onInit = null; @@ -368,7 +395,7 @@ namespace MonoDevelop.Ide OpenFiles (files, metadata); }; Initialized += onInit; - return; + return false; } var filteredFiles = new List<FileOpenInformation> (); @@ -412,6 +439,8 @@ namespace MonoDevelop.Ide } Workbench.Present (); + + return true; } static bool FileServiceErrorHandler (string message, Exception ex) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdeStartup.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdeStartup.cs index 69fe4db2d7..b12343faf4 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdeStartup.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdeStartup.cs @@ -282,7 +282,7 @@ namespace MonoDevelop.Ide // XBC #33699 Counters.Initialization.Trace ("Initializing IdeApp"); - hideWelcomePage = startupInfo.HasFiles || IdeApp.Preferences.StartupBehaviour.Value != OnStartupBehaviour.ShowStartWindow; + hideWelcomePage = options.NoStartWindow || startupInfo.HasFiles || IdeApp.Preferences.StartupBehaviour.Value != OnStartupBehaviour.ShowStartWindow; await IdeApp.Initialize (monitor); sectionTimings ["AppInitialization"] = startupSectionTimer.ElapsedMilliseconds; @@ -378,17 +378,6 @@ namespace MonoDevelop.Ide startupTimer.Stop (); startupSectionTimer.Stop (); - // Need to start this timer because we don't know yet if we've been asked to open a solution from the file manager. - timeToCodeTimer.Start (); - ttcMetadata = new TimeToCodeMetadata { - StartupTime = startupTimer.ElapsedMilliseconds - }; - - // Start this timer to limit the time to decide if the app was opened by a file manager - IdeApp.StartFMOpenTimer (FMOpenTimerExpired); - IdeApp.Workspace.FirstWorkspaceItemOpened += CompleteSolutionTimeToCode; - IdeApp.Workbench.DocumentOpened += CompleteFileTimeToCode; - CreateStartupMetadata (startupInfo, sectionTimings); GLib.Idle.Add (OnIdle); @@ -402,17 +391,6 @@ namespace MonoDevelop.Ide Runtime.RegisterServiceType<IShell, DefaultWorkbench> (); } - void FMOpenTimerExpired () - { - IdeApp.Workspace.FirstWorkspaceItemOpened -= CompleteSolutionTimeToCode; - IdeApp.Workbench.DocumentOpened -= CompleteFileTimeToCode; - - timeToCodeTimer.Stop (); - timeToCodeTimer = null; - - ttcMetadata = null; - } - /// <summary> /// Resolves MSBuild 15.0 assemblies that are used by MonoDevelop.Ide and are included with Mono. /// </summary> @@ -481,49 +459,7 @@ namespace MonoDevelop.Ide var startupMetadata = GetStartupMetadata (si, result, timings); Counters.Startup.Inc (startupMetadata); - if (ttcMetadata != null) { - ttcMetadata.AddProperties (startupMetadata); - } - - IdeApp.OnStartupCompleted (); - } - - enum TimeToCodeFileType - { - Solution, - Document - } - - static void CompleteSolutionTimeToCode (object sender, EventArgs args) - { - CompleteTimeToCode (TimeToCodeMetadata.DocumentType.Solution); - } - - static void CompleteFileTimeToCode (object sender, EventArgs args) - { - CompleteTimeToCode (TimeToCodeMetadata.DocumentType.File); - } - - static void CompleteTimeToCode (TimeToCodeMetadata.DocumentType type) - { - IdeApp.Workspace.FirstWorkspaceItemOpened -= CompleteSolutionTimeToCode; - IdeApp.Workbench.DocumentOpened -= CompleteFileTimeToCode; - - if (timeToCodeTimer == null) { - return; - } - - timeToCodeTimer.Stop (); - ttcMetadata.SolutionLoadTime = timeToCodeTimer.ElapsedMilliseconds; - - ttcMetadata.CorrectedDuration = ttcMetadata.StartupTime + ttcMetadata.SolutionLoadTime; - ttcMetadata.Type = type; - - if (IdeApp.ReportTimeToCode) { - Counters.TimeToCode.Inc ("SolutionLoaded", ttcMetadata); - IdeApp.ReportTimeToCode = false; - } - timeToCodeTimer = null; + IdeApp.OnStartupCompleted (startupMetadata, timeToCodeTimer); } static DateTime lastIdle; @@ -776,6 +712,7 @@ namespace MonoDevelop.Ide // set as a metadata property on the Counters.Startup counter. startupTimer.Start (); startupSectionTimer.Start (); + timeToCodeTimer.Start (); var options = MonoDevelopOptions.Parse (args); if (options.ShowHelp || options.Error != null) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MessageService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MessageService.cs index 125a1aa8e7..286cd56b87 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MessageService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MessageService.cs @@ -348,10 +348,16 @@ namespace MonoDevelop.Ide Runtime.RunInMainThread (() => { // If there is a native NSWindow model window running, we need // to show the new dialog over that window. - if (NSApplication.SharedApplication.ModalWindow != null) - dialog.Shown += HandleShown; - else + if (NSApplication.SharedApplication.ModalWindow != null || (parent.nativeWidget is NSWindow && dialog.Modal)) { + EventHandler shownHandler = null; + shownHandler = (s,e) => { + ShowCustomModalDialog (dialog, parent); + dialog.Shown -= shownHandler; + }; + dialog.Shown += shownHandler; + } else { PlaceDialog (dialog, parent); + } }).Wait (); #endif @@ -359,7 +365,13 @@ namespace MonoDevelop.Ide try { Xwt.MessageDialog.RootWindow = Xwt.Toolkit.CurrentEngine.WrapWindow (dialog); IdeApp.DisableIdleActions (); - return GtkWorkarounds.RunDialogWithNotification (dialog); + int result = GtkWorkarounds.RunDialogWithNotification (dialog); + // Focus parent window once the dialog is ran, as focus gets lost + if (parent != null) { + DesktopService.FocusWindow (parent); + } + + return result; } finally { Xwt.MessageDialog.RootWindow = initialRootWindow; IdeApp.EnableIdleActions (); @@ -367,11 +379,11 @@ namespace MonoDevelop.Ide } #if MAC - static void HandleShown (object sender, EventArgs e) + static void ShowCustomModalDialog (Gtk.Window dialog, Window parent) { - var dialog = (Gtk.Window)sender; - var nsdialog = GtkMacInterop.GetNSWindow (dialog); + CenterWindow (dialog, parent); + var nsdialog = GtkMacInterop.GetNSWindow (dialog); // Make the GTK window modal WRT the current modal NSWindow var s = NSApplication.SharedApplication.BeginModalSession (nsdialog); @@ -381,7 +393,6 @@ namespace MonoDevelop.Ide dialog.Unrealized -= unrealizer; }; dialog.Unrealized += unrealizer; - dialog.Shown -= HandleShown; } #endif @@ -417,16 +428,16 @@ namespace MonoDevelop.Ide if (nsParent == null || !nsParent.IsVisible) { nsChild.Center (); } else { - int x = (int)(Math.Max (0, nsParent.Frame.Left + (nsParent.Frame.Width - nsChild.Frame.Width) / 2)); - int y = (int)(Math.Max (0, nsParent.Frame.Top + (nsParent.Frame.Height - nsChild.Frame.Height) / 2)); - nsChild.SetFrame (new CoreGraphics.CGRect (x, y, nsChild.Frame.Width, nsChild.Frame.Height), true); + int x = (int) Math.Max (0, nsParent.Frame.Left + (nsParent.Frame.Width - nsChild.Frame.Width) / 2); + int y = (int) Math.Max (0, nsParent.Frame.Top + (nsParent.Frame.Height - nsChild.Frame.Height) / 2); + nsChild.SetFrameOrigin (new CoreGraphics.CGPoint (x, y)); } return; } #endif if (gtkChild != null) { - gtkChild.Child.Show (); + gtkChild.Show (); int x, y; gtkChild.GetSize (out var w, out var h); if (gtkParent != null) { @@ -438,9 +449,10 @@ namespace MonoDevelop.Ide gtkChild.Move (x, y); #if MAC } else if (nsParent != null) { - x = (int)(Math.Max (0, nsParent.Frame.Left + (nsParent.Frame.Width - w) / 2)); - y = (int)(Math.Max (0, nsParent.Frame.Top + (nsParent.Frame.Height - h) / 2)); - gtkChild.Move (x, y); + nsChild = GtkMacInterop.GetNSWindow (gtkChild); + x = (int) Math.Max (0, nsParent.Frame.Left + (nsParent.Frame.Width - w) / 2); + y = (int) Math.Max (0, nsParent.Frame.Top + (nsParent.Frame.Height - h) / 2); + nsChild.SetFrameOrigin (new CoreGraphics.CGPoint (x, y)); #endif } else { gtkChild.SetPosition (Gtk.WindowPosition.Center); @@ -589,7 +601,6 @@ namespace MonoDevelop.Ide Caption = caption, Value = initialValue, IsPassword = isPassword, - TransientFor = parent ?? DesktopService.GetParentForModalWindow () }; if (dialog.Run ()) return dialog.Value; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MonoDevelopOptions.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MonoDevelopOptions.cs index b3a54c6ba4..8291d85155 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MonoDevelopOptions.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MonoDevelopOptions.cs @@ -47,6 +47,8 @@ namespace MonoDevelop.Ide Mono.Options.OptionSet GetOptionSet () { return new Mono.Options.OptionSet { + { "no-splash", "Do not display splash screen (deprecated).", s => {} }, + { "no-start-window", "Do not display start window", s => NoStartWindow = true }, { "ipc-tcp", "Use the Tcp channel for inter-process communication.", s => IpcTcp = true }, { "new-window", "Do not open in an existing instance of " + BrandingService.ApplicationName, s => NewWindow = true }, { "h|?|help", "Show help", s => ShowHelp = true }, @@ -84,6 +86,7 @@ namespace MonoDevelop.Ide return opt; } + public bool NoStartWindow { get; set; } public bool IpcTcp { get; set; } public bool NewWindow { get; set; } public bool ShowHelp { get; set; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs index b26cf6a7ba..cbc299d62f 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs @@ -1951,7 +1951,7 @@ namespace MonoDevelop.Ide ProgressMonitor monitor = null; if (files.Length > 10) { - monitor = new MessageDialogProgressMonitor (true); + monitor = new MessageDialogProgressMonitor (true, true, true, true, MessageService.RootWindow); monitor.BeginTask (GettextCatalog.GetString("Adding files..."), files.Length); } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Services.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Services.cs index 87f4b79ef3..4f8ff5739d 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Services.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Services.cs @@ -161,7 +161,8 @@ namespace MonoDevelop.Ide { public enum DocumentType { Solution, - File + File, + Unknown }; public long CorrectedDuration { |