diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseAcceptanceDialog.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseAcceptanceDialog.cs
index 852aa83080d..0e1a16751ea 100644
--- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseAcceptanceDialog.cs
+++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseAcceptanceDialog.cs
@@ -28,9 +28,11 @@
//
using System;
+using System.Collections.Generic;
using System.Linq;
using MonoDevelop.Core;
-using MonoDevelop.Ide;
+using MonoDevelop.Ide;
+using NuGet.PackageManagement.UI;
using Xwt;
namespace MonoDevelop.PackageManagement
@@ -46,7 +48,6 @@ internal class LicenseAcceptanceDialog : Dialog
public LicenseAcceptanceDialog (LicenseAcceptanceViewModel viewModel)
{
Height = 350;
- Resizable = false;
Padding = 0;
Title = GettextCatalog.GetString ("License Acceptance");
this.viewModel = viewModel;
@@ -126,21 +127,20 @@ protected override void OnShown ()
void AddPackage (PackageLicenseViewModel package)
{
+ var label = new Label {
+ Markup = string.Format ("{0} – {1}", package.Id, package.Author),
+ };
var titleBox = new VBox ();
titleBox.Spacing = 0;
titleBox.MarginBottom = 4;
- titleBox.PackStart (new Label {
- Markup = string.Format ("{0} – {1}", package.Id, package.Author),
- });
- var licenseLabel = new LinkLabel (GettextCatalog.GetString ("View License"));
- licenseLabel.Uri = package.LicenseUrl;
- licenseLabel.LinkClicked += (sender, e) => IdeServices.DesktopService.ShowUrl (e.Target.AbsoluteUri);
- titleBox.PackStart (licenseLabel);
+ titleBox.PackStart (label);
+ AddLicenseInfo (package, titleBox);
var rowBox = new HBox ();
rowBox.Margin = rowMargin;
var icon = new ImageView (ImageService.GetIcon ("md-package", Gtk.IconSize.Dnd));
+ icon.Accessible.LabelWidget = label;
if (package.IconUrl != null && !string.IsNullOrEmpty (package.IconUrl.AbsoluteUri))
imageLoader.LoadFrom (package.IconUrl, icon);
@@ -151,6 +151,108 @@ void AddPackage (PackageLicenseViewModel package)
packagesList.PackStart (rowBox);
}
+ static void AddLicenseInfo (PackageLicenseViewModel package, VBox parentVBox)
+ {
+ if (package.LicenseLinks.Any ()) {
+ if (package.LicenseLinks.Count == 1) {
+ IText textLink = package.LicenseLinks [0];
+ if (textLink is LicenseText licenseText) {
+ AddLicenseLinkLabel (licenseText.Link, licenseText.Text, parentVBox);
+ return;
+ } else if (textLink is LicenseFileText licenseFileText) {
+ AddFileLicenseLinkLabel (licenseFileText, parentVBox);
+ return;
+ } else {
+ // Warning or free text fallback to showing an expression which will handle this.
+ }
+ }
+ AddLicenseExpressionLabel (package, parentVBox);
+ } else {
+ AddLicenseLinkLabel (package.LicenseUrl, GettextCatalog.GetString ("View License"), parentVBox);
+ }
+ }
+
+ static void AddLicenseExpressionLabel (PackageLicenseViewModel package, VBox parentVBox)
+ {
+ // Using the HBox as an accessibility group since the license will have multiple parts.
+ // Cannot use a single label with markup for the license expression since it is not accessible,
+ // also when using the native toolkit the hyperlinks cannot be clicked.
+ var hbox = new HBox ();
+ hbox.Spacing = 0;
+ hbox.Accessible.Role = Xwt.Accessibility.Role.Group;
+ hbox.Accessible.Label = GettextCatalog.GetString ("License Expression");
+
+ // Get any warnings.
+ List warnings = null;
+ foreach (IText textLink in package.LicenseLinks) {
+ if (textLink is WarningText warning) {
+ warnings ??= new List ();
+ warnings.Add (warning);
+ }
+ }
+
+ if (warnings?.Any () == true) {
+ var warningTextBuilder = StringBuilderCache.Allocate ();
+ foreach (WarningText warning in warnings) {
+ warningTextBuilder.Append (warning.Text);
+ warningTextBuilder.Append (' ');
+ }
+
+ var warningWidget = new MonoDevelop.Components.InformationPopoverWidget ();
+ warningWidget.Severity = Ide.Tasks.TaskSeverity.Warning;
+ warningWidget.Message = StringBuilderCache.ReturnAndFree (warningTextBuilder).TrimEnd ();
+ warningWidget.MarginRight = 6;
+
+ // Disable focus. With focus enabled the info popup ends up being displayed and focused
+ // if it is the first package license on opening the dialog. Unable to set focus to a
+ // dialog button.
+ warningWidget.CanGetFocus = false;
+
+ hbox.PackStart (warningWidget);
+ }
+
+ foreach (IText textLink in package.LicenseLinks) {
+ if (textLink is LicenseText licenseText) {
+ var label = new LinkLabel (licenseText.Text);
+ label.TextAlignment = Alignment.Start;
+ label.Uri = licenseText.Link;
+ label.LinkClicked += (sender, e) => IdeServices.DesktopService.ShowUrl (e.Target.AbsoluteUri);
+ hbox.PackStart (label, false, false);
+ } else if (textLink is WarningText warning) {
+ // Ignore.
+ } else if (textLink is LicenseFileText licenseFileText) {
+ // Should not happen. A license expression should not contain a license file.
+ LoggingService.LogError ("Unexpected LicenseFileText when building licence expression {0}", licenseFileText.Text);
+ } else {
+ var label = new Label (textLink.Text);
+ label.TextAlignment = Alignment.Start;
+ hbox.PackStart (label, false, false);
+ }
+ }
+
+ parentVBox.PackStart (hbox, false, false);
+ }
+
+ static void AddFileLicenseLinkLabel (LicenseFileText licenseFileText, VBox parentVBox)
+ {
+ var licenseLabel = new LinkLabel (GettextCatalog.GetString ("View License"));
+ licenseLabel.Uri = licenseFileText.CreateLicenseFileUri ();
+ licenseLabel.Tag = licenseFileText;
+ licenseLabel.NavigateToUrl += (sender, e) => {
+ e.SetHandled ();
+ ShowFileDialog ((LinkLabel)sender);
+ };
+ parentVBox.PackStart (licenseLabel);
+ }
+
+ static void AddLicenseLinkLabel (Uri licenseUrl, string labelText, VBox parentVBox)
+ {
+ var licenseLabel = new LinkLabel (labelText);
+ licenseLabel.Uri = licenseUrl;
+ licenseLabel.LinkClicked += (sender, e) => IdeServices.DesktopService.ShowUrl (e.Target.AbsoluteUri);
+ parentVBox.PackStart (licenseLabel);
+ }
+
public new bool Run (WindowFrame parentWindow)
{
return base.Run (parentWindow) == Command.Ok;
@@ -162,6 +264,16 @@ protected override void Dispose (bool disposing)
imageLoader.Dispose ();
base.Dispose (disposing);
}
+
+ static void ShowFileDialog (LinkLabel label)
+ {
+ var licenseFileText = (LicenseFileText)label.Tag;
+ Xwt.Toolkit.NativeEngine.Invoke (delegate {
+ using (var dialog = new LicenseFileDialog (licenseFileText)) {
+ dialog.Run (label.ParentWindow);
+ }
+ });
+ }
}
}
diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseFileDialog.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseFileDialog.cs
new file mode 100644
index 00000000000..4ed7da4312b
--- /dev/null
+++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseFileDialog.cs
@@ -0,0 +1,91 @@
+//
+// LicenseFileDialog.cs
+//
+// Author:
+// Matt Ward
+//
+// Copyright (c) 2019 Microsoft Corporation
+//
+// 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.ComponentModel;
+using NuGet.PackageManagement.UI;
+using Xwt;
+using Xwt.Formats;
+
+namespace MonoDevelop.PackageManagement
+{
+ sealed class LicenseFileDialog : Dialog
+ {
+ RichTextView textView;
+ LicenseFileText licenseFileText;
+
+ public LicenseFileDialog (LicenseFileText licenseFileText)
+ {
+ this.licenseFileText = licenseFileText;
+
+ Build ();
+ LoadText ();
+
+ licenseFileText.PropertyChanged += LicenseFileTextPropertyChanged;
+ licenseFileText.LoadLicenseFile ();
+ }
+
+ void Build ()
+ {
+ Height = 450;
+ Width = 450;
+ Title = licenseFileText.LicenseHeader;
+
+ var scrollView = new ScrollView ();
+ scrollView.HorizontalScrollPolicy = ScrollPolicy.Never;
+ Content = scrollView;
+
+ textView = new RichTextView ();
+ textView.ReadOnly = true;
+ scrollView.Content = textView;
+
+ var okCommand = new Command ("Ok", Core.GettextCatalog.GetString ("OK"));
+ Buttons.Add ();
+ DefaultCommand = okCommand;
+ }
+
+ void LicenseFileTextPropertyChanged (object sender, PropertyChangedEventArgs e)
+ {
+ LoadText ();
+
+ // Need to refresh the dialog after the license text has been loaded. Otherwise when using the
+ // native toolkit the vertical scrollbar is not enabled unless you re-size the dialog.
+ OnReallocate ();
+ }
+
+ void LoadText ()
+ {
+ textView.LoadText (licenseFileText.LicenseText, TextFormat.Plain);
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ base.Dispose (disposing);
+ if (disposing) {
+ licenseFileText.PropertyChanged -= LicenseFileTextPropertyChanged;
+ }
+ }
+ }
+}
diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseFileTextExtensions.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseFileTextExtensions.cs
new file mode 100644
index 00000000000..154255e3ce0
--- /dev/null
+++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseFileTextExtensions.cs
@@ -0,0 +1,39 @@
+//
+// LicenseFileTextExtensions.cs
+//
+// Author:
+// Matt Ward
+//
+// Copyright (c) 2019 Microsoft Corporation
+//
+// 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;
+using NuGet.PackageManagement.UI;
+
+namespace MonoDevelop.PackageManagement
+{
+ static class LicenseFileTextExtensions
+ {
+ public static Uri CreateLicenseFileUri (this LicenseFileText licenseFileText)
+ {
+ return new Uri ($"file:///{licenseFileText.Text}");
+ }
+ }
+}
diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseLinkMarkupBuilder.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseLinkMarkupBuilder.cs
new file mode 100644
index 00000000000..3a6149526d1
--- /dev/null
+++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/LicenseLinkMarkupBuilder.cs
@@ -0,0 +1,73 @@
+//
+// LicenseLinkMarkupBuilder.cs
+//
+// Author:
+// Matt Ward
+//
+// Copyright (c) 2019 Microsoft Corporation
+//
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security;
+using MonoDevelop.Core;
+using NuGet.PackageManagement.UI;
+
+namespace MonoDevelop.PackageManagement
+{
+ sealed class LicenseLinkMarkupBuilder
+ {
+ List warnings;
+
+ public string GetMarkup (IReadOnlyList textLinks)
+ {
+ var markupBuilder = StringBuilderCache.Allocate ();
+
+ foreach (IText textLink in textLinks) {
+ if (textLink is LicenseText licenseText) {
+ markupBuilder.Append (GetUriMarkup (licenseText.Link, licenseText.Text));
+ } else if (textLink is LicenseFileText licenseFileText) {
+ // Should not happen. Building an expression should not contain a license file.
+ LoggingService.LogError ("Unexpected LicenseFileText when building markup {0}", licenseFileText.Text);
+ } else if (textLink is WarningText warning) {
+ warnings ??= new List ();
+ warnings.Add (warning);
+ } else {
+ markupBuilder.Append (textLink.Text);
+ }
+ }
+
+ return StringBuilderCache.ReturnAndFree (markupBuilder);
+ }
+
+ public IEnumerable Warnings {
+ get { return warnings ?? Enumerable.Empty (); }
+ }
+
+ static string GetUriMarkup (Uri uri, string text)
+ {
+ return string.Format (
+ "{1}",
+ uri != null ? SecurityElement.Escape (uri.ToString ()) : string.Empty,
+ SecurityElement.Escape (text));
+ }
+ }
+}
diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/ManagePackagesDialog.UI.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/ManagePackagesDialog.UI.cs
index 5a23a14c4d9..8c594e2a57d 100644
--- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/ManagePackagesDialog.UI.cs
+++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/ManagePackagesDialog.UI.cs
@@ -47,7 +47,10 @@ internal partial class ManagePackagesDialog : ExtendedTitleBarDialog
Label packageAuthor;
Label packagePublishedDate;
Label packageDownloads;
+ Label packageLicenseLabel;
LinkLabel packageLicenseLink;
+ InformationPopoverWidget packageLicenseMetadataWarningInfoPopoverWidget;
+ Label packageLicenseMetadataLinkLabel;
LinkLabel packageProjectPageLink;
Label packageDependenciesList;
HBox packageDependenciesHBox;
@@ -346,15 +349,27 @@ void Build ()
var packageLicenseHBox = new HBox ();
packageInfoVBox.PackStart (packageLicenseHBox);
- var packageLicenseLabel = new Label ();
+ packageLicenseLabel = new Label ();
packageLicenseLabel.Text = GettextCatalog.GetString ("License");
packageLicenseLabel.Font = packageInfoBoldFont;
- packageLicenseHBox.PackStart (packageLicenseLabel);
+ packageLicenseHBox.PackStart (packageLicenseLabel, vpos: WidgetPlacement.Start);
packageLicenseLink = new LinkLabel ();
packageLicenseLink.Text = GettextCatalog.GetString ("View License");
packageLicenseLink.Font = packageInfoSmallFont;
- packageLicenseHBox.PackEnd (packageLicenseLink);
+ packageLicenseLink.TextAlignment = Alignment.End;
+ packageLicenseHBox.PackStart (packageLicenseLink, true);
+
+ packageLicenseMetadataLinkLabel = new Label ();
+ packageLicenseMetadataLinkLabel.Wrap = WrapMode.Word;
+ packageLicenseMetadataLinkLabel.Font = packageInfoSmallFont;
+ packageLicenseMetadataLinkLabel.Accessible.LabelWidget = packageLicenseLabel;
+ packageLicenseMetadataLinkLabel.TextAlignment = Alignment.End;
+ packageLicenseHBox.PackStart (packageLicenseMetadataLinkLabel, true, vpos: WidgetPlacement.Start);
+
+ packageLicenseMetadataWarningInfoPopoverWidget = new InformationPopoverWidget ();
+ packageLicenseMetadataWarningInfoPopoverWidget.Severity = Ide.Tasks.TaskSeverity.Warning;
+ packageLicenseHBox.PackStart (packageLicenseMetadataWarningInfoPopoverWidget, vpos: WidgetPlacement.Start, hpos: WidgetPlacement.End);
// Package project page.
var packageProjectPageHBox = new HBox ();
diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/ManagePackagesDialog.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/ManagePackagesDialog.cs
index b5b1fac718b..af960723bbb 100644
--- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/ManagePackagesDialog.cs
+++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Gui/ManagePackagesDialog.cs
@@ -27,10 +27,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Security;
using MonoDevelop.Components.AtkCocoaHelper;
using MonoDevelop.Core;
using MonoDevelop.Ide;
using MonoDevelop.Projects;
+using NuGet.PackageManagement.UI;
using NuGet.Versioning;
using Xwt;
using Xwt.Drawing;
@@ -98,6 +100,7 @@ public ManagePackagesDialog (
LoadViewModel (initialSearch);
closeButton.Clicked += CloseButtonClicked;
+ packageLicenseLink.NavigateToUrl += PackageLicenseNavigateToUrl;
showPrereleaseCheckBox.Clicked += ShowPrereleaseCheckBoxClicked;
packageSourceComboBox.SelectionChanged += PackageSourceChanged;
addPackagesButton.Clicked += AddPackagesButtonClicked;
@@ -147,6 +150,7 @@ void UpdateTabAccessibility ()
protected override void Dispose (bool disposing)
{
closeButton.Clicked -= CloseButtonClicked;
+ packageLicenseLink.NavigateToUrl -= PackageLicenseNavigateToUrl;
currentPackageVersionLabel.BoundsChanged -= PackageVersionLabelBoundsChanged;
showPrereleaseCheckBox.Clicked -= ShowPrereleaseCheckBoxClicked;
@@ -477,6 +481,10 @@ void ShowPackageInformation (ManagePackagesSearchResultViewModel packageViewMode
{
bool consolidate = viewModel.IsConsolidatePageSelected;
+ foreach (Widget child in packageInfoVBox.Children) {
+ child.Visible = !consolidate;
+ }
+
if (consolidate) {
projectsListViewLabel.Text = GettextCatalog.GetString ("Select projects and a version for a consolidation.");
} else {
@@ -491,7 +499,7 @@ void ShowPackageInformation (ManagePackagesSearchResultViewModel packageViewMode
this.packageId.Visible = packageViewModel.HasNoGalleryUrl;
ShowUri (this.packageIdLink, packageViewModel.GalleryUrl, packageViewModel.Id);
ShowUri (this.packageProjectPageLink, packageViewModel.ProjectUrl);
- ShowUri (this.packageLicenseLink, packageViewModel.LicenseUrl);
+ ShowLicense (packageViewModel);
PopulatePackageDependencies (packageViewModel);
}
@@ -511,10 +519,6 @@ void ShowPackageInformation (ManagePackagesSearchResultViewModel packageViewMode
packageVersionsHBox.Visible = true;
}
- foreach (Widget child in packageInfoVBox.Children) {
- child.Visible = !consolidate;
- }
-
if (consolidate) {
PopulateProjectList ();
} else {
@@ -1042,6 +1046,7 @@ void SelectedPackageViewModelChanged (object sender, PropertyChangedEventArgs e)
} else {
if (!viewModel.IsConsolidatePageSelected) {
packagePublishedDate.Text = viewModel.SelectedPackage.GetLastPublishedDisplayText ();
+ ShowLicense (viewModel.SelectedPackage);
PopulatePackageDependencies (viewModel.SelectedPackage);
}
}
@@ -1284,5 +1289,82 @@ void ProjectCheckBoxCellViewToggled (object sender, WidgetEventArgs e)
UpdateAddPackagesButton ();
}
+
+ void ShowLicense (ManagePackagesSearchResultViewModel packageViewModel)
+ {
+ packageLicenseLink.Tag = null;
+ if (packageViewModel.HasLicenseMetadata) {
+ ShowLicenseMetadata (packageViewModel);
+ } else {
+ packageLicenseMetadataLinkLabel.Visible = false;
+ ShowUri (packageLicenseLink, packageViewModel.LicenseUrl, GettextCatalog.GetString ("View License"));
+ }
+ }
+
+ void PackageLicenseNavigateToUrl (object sender, NavigateToUrlEventArgs e)
+ {
+ var licenseFileText = packageLicenseLink.Tag as LicenseFileText;
+ if (licenseFileText != null) {
+ e.SetHandled ();
+ Toolkit.NativeEngine.Invoke (delegate {
+ using (var dialog = new LicenseFileDialog (licenseFileText)) {
+ dialog.Run (this);
+ }
+ });
+ }
+ }
+
+ void ShowLicenseMetadata (ManagePackagesSearchResultViewModel packageViewModel)
+ {
+ packageLicenseLink.Visible = false;
+ packageLicenseMetadataLinkLabel.Visible = false;
+ packageLicenseMetadataWarningInfoPopoverWidget.Visible = false;
+
+ var textLinks = packageViewModel.GetLicenseLinks ();
+ if (textLinks.Count == 0) {
+ return;
+ }
+
+ // Single link - show this on the same line as the License label.
+ if (textLinks.Count == 1) {
+ packageLicenseLink.Visible = true;
+
+ IText textLink = textLinks [0];
+ if (textLink is LicenseText licenseText) {
+ packageLicenseLink.Text = licenseText.Text;
+ packageLicenseLink.Uri = licenseText.Link;
+ return;
+ } else if (textLink is LicenseFileText licenseFileText) {
+ packageLicenseLink.Text = GettextCatalog.GetString ("View License");
+ packageLicenseLink.Uri = licenseFileText.CreateLicenseFileUri ();
+ packageLicenseLink.Tag = licenseFileText;
+ return;
+ } else {
+ // Warning or plain text - handled below.
+ }
+ }
+
+ // Multiple text links. We need to allow these to wrap so show these below the license label.
+ var markupBuilder = new LicenseLinkMarkupBuilder ();
+ packageLicenseMetadataLinkLabel.Markup = markupBuilder.GetMarkup (textLinks);
+ packageLicenseMetadataLinkLabel.Visible = true;
+
+ if (markupBuilder.Warnings.Any ()) {
+ AddWarnings (markupBuilder.Warnings);
+ } else {
+ packageLicenseMetadataWarningInfoPopoverWidget.Visible = false;
+ }
+ }
+
+ void AddWarnings (IEnumerable warnings)
+ {
+ var warningTextBuilder = StringBuilderCache.Allocate ();
+ foreach (WarningText warning in warnings) {
+ warningTextBuilder.Append (warning.Text);
+ warningTextBuilder.Append (' ');
+ }
+ packageLicenseMetadataWarningInfoPopoverWidget.Message = StringBuilderCache.ReturnAndFree (warningTextBuilder).TrimEnd ();
+ packageLicenseMetadataWarningInfoPopoverWidget.Visible = true;
+ }
}
}
\ No newline at end of file
diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.csproj b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.csproj
index 0cee2f7ffa7..50f0d17b01f 100644
--- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.csproj
+++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.csproj
@@ -369,6 +369,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/ManagePackagesSearchResultViewModel.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/ManagePackagesSearchResultViewModel.cs
index 6aa0d2c97a1..8234f3ddf5d 100644
--- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/ManagePackagesSearchResultViewModel.cs
+++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/ManagePackagesSearchResultViewModel.cs
@@ -47,6 +47,7 @@ internal class ManagePackagesSearchResultViewModel : ViewModelBase licenseLinks;
string summary;
bool isChecked;
@@ -383,6 +384,7 @@ void OnPackageMetadataLoaded (Task task)
if (metadata != null) {
viewModel.Published = metadata.Published;
dependencies = GetCompatibleDependencies ().ToArray ();
+ licenseLinks = PackageLicenseUtilities.GenerateLicenseLinks (metadata);
OnPropertyChanged ("Dependencies");
}
}
@@ -467,6 +469,16 @@ public string GetCurrentPackageVersionAdditionalText ()
{
return parent.GetCurrentPackageVersionAdditionalText (Id);
}
+
+ public bool HasLicenseMetadata {
+ get { return packageDetailModel?.PackageMetadata?.LicenseMetadata != null; }
+ }
+
+ public IReadOnlyList GetLicenseLinks ()
+ {
+ licenseLinks ??= new List ();
+ return licenseLinks;
+ }
}
}
diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/NuGetPackageLicense.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/NuGetPackageLicense.cs
index 3bbb43c9e9e..f4db7130cb7 100644
--- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/NuGetPackageLicense.cs
+++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/NuGetPackageLicense.cs
@@ -25,6 +25,8 @@
// THE SOFTWARE.
using System;
+using System.Collections.Generic;
+using NuGet.PackageManagement.UI;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;
@@ -40,6 +42,7 @@ public NuGetPackageLicense (IPackageSearchMetadata metadata)
PackageAuthor = metadata.Authors;
LicenseUrl = metadata.LicenseUrl;
IconUrl = metadata.IconUrl;
+ LicenseLinks = PackageLicenseUtilities.GenerateLicenseLinks (metadata);
}
public PackageIdentity PackageIdentity { get; private set; }
@@ -48,6 +51,7 @@ public NuGetPackageLicense (IPackageSearchMetadata metadata)
public string PackageAuthor { get; private set; }
public Uri LicenseUrl { get; private set; }
public Uri IconUrl { get; private set; }
+ public IReadOnlyList LicenseLinks { get; }
}
}
diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/PackageLicenseViewModel.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/PackageLicenseViewModel.cs
index cad8322ad45..ec72fb70db0 100644
--- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/PackageLicenseViewModel.cs
+++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/PackageLicenseViewModel.cs
@@ -27,6 +27,9 @@
//
using System;
+using System.Collections.Generic;
+using System.Linq;
+using NuGet.PackageManagement.UI;
namespace MonoDevelop.PackageManagement
{
@@ -54,5 +57,9 @@ public string Author {
public Uri IconUrl {
get { return packageLicense.IconUrl; }
}
+
+ public IReadOnlyList LicenseLinks {
+ get { return packageLicense.LicenseLinks; }
+ }
}
}
diff --git a/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/DetailedPackageMetadata.cs b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/DetailedPackageMetadata.cs
index e16eddcd532..91ab22e8090 100644
--- a/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/DetailedPackageMetadata.cs
+++ b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/DetailedPackageMetadata.cs
@@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using NuGet.Packaging;
+using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;
@@ -17,6 +19,7 @@ public DetailedPackageMetadata()
public DetailedPackageMetadata(IPackageSearchMetadata serverData, long? downloadCount)
{
+ Id = serverData.Identity.Id;
Version = serverData.Identity.Version;
Summary = serverData.Summary;
Description = serverData.Description;
@@ -34,9 +37,16 @@ public DetailedPackageMetadata(IPackageSearchMetadata serverData, long? download
?? new PackageDependencySetMetadata[] { };
HasDependencies = DependencySets.Any(
dependencySet => dependencySet.Dependencies != null && dependencySet.Dependencies.Count > 0);
+ LicenseMetadata = serverData.LicenseMetadata;
+ localMetadata = serverData as LocalPackageSearchMetadata;
}
+ readonly LocalPackageSearchMetadata localMetadata;
+
+ public string Id { get; set; }
+
public NuGetVersion Version { get; set; }
+
public string Summary { get; set; }
public string Description { get; set; }
@@ -63,5 +73,17 @@ public DetailedPackageMetadata(IPackageSearchMetadata serverData, long? download
// This property is used by data binding to display text "No dependencies"
public bool HasDependencies { get; set; }
+
+ public LicenseMetadata LicenseMetadata { get; set; }
+
+ public IReadOnlyList LicenseLinks => PackageLicenseUtilities.GenerateLicenseLinks (this);
+
+ public string LoadFileAsText (string path)
+ {
+ if (localMetadata != null) {
+ return localMetadata.LoadFileAsText (path);
+ }
+ return null;
+ }
}
}
\ No newline at end of file
diff --git a/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/FreeText.cs b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/FreeText.cs
new file mode 100644
index 00000000000..80508374086
--- /dev/null
+++ b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/FreeText.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace NuGet.PackageManagement.UI
+{
+ internal class FreeText : IText
+ {
+ public FreeText (string text)
+ {
+ Text = text;
+ }
+
+ public string Text { get; }
+ }
+}
\ No newline at end of file
diff --git a/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/IText.cs b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/IText.cs
new file mode 100644
index 00000000000..afbd4ddc043
--- /dev/null
+++ b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/IText.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace NuGet.PackageManagement.UI
+{
+ public interface IText
+ {
+ string Text { get; }
+ }
+}
\ No newline at end of file
diff --git a/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/LicenseFileText.cs b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/LicenseFileText.cs
new file mode 100644
index 00000000000..43f53cc98d9
--- /dev/null
+++ b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/LicenseFileText.cs
@@ -0,0 +1,78 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel;
+using System.Threading;
+using System.Threading.Tasks;
+using MonoDevelop.Core;
+
+namespace NuGet.PackageManagement.UI
+{
+ internal class LicenseFileText : IText, INotifyPropertyChanged
+ {
+ string _text;
+ string _licenseText;
+ string _licenseHeader;
+ readonly string _licenseFileLocation;
+ private Func _loadFileFromPackage;
+
+ int _initialized;
+
+ internal LicenseFileText (string text, string licenseFileHeader, Func loadFileFromPackage, string licenseFileLocation)
+ {
+ _text = text;
+ _licenseHeader = licenseFileHeader;
+ _licenseText = GettextCatalog.GetString ("Loading license file…");
+ _loadFileFromPackage = loadFileFromPackage;
+ _licenseFileLocation = licenseFileLocation;
+ }
+
+ internal void LoadLicenseFile ()
+ {
+ if (Interlocked.CompareExchange (ref _initialized, 1, 0) == 0) {
+ if (_loadFileFromPackage != null) {
+ Task.Run (async () => {
+ string content = _loadFileFromPackage (_licenseFileLocation);
+ await Runtime.RunInMainThread (() => {
+ LicenseText = content;
+ });
+ }).Ignore ();
+ }
+ }
+ }
+
+ public string LicenseHeader {
+ get => _licenseHeader;
+ set {
+ _licenseHeader = value;
+ OnPropertyChanged ("LicenseHeader");
+ }
+ }
+
+ public string Text {
+ get => _text;
+ set {
+ _text = value;
+ OnPropertyChanged ("Text");
+ }
+ }
+
+ public string LicenseFileLocation => _licenseFileLocation;
+
+ public string LicenseText {
+ get => _licenseText;
+ set {
+ _licenseText = value;
+ OnPropertyChanged ("LicenseText");
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected void OnPropertyChanged (string name)
+ {
+ PropertyChanged?.Invoke (this, new System.ComponentModel.PropertyChangedEventArgs (name));
+ }
+ }
+}
\ No newline at end of file
diff --git a/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/LicenseText.cs b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/LicenseText.cs
new file mode 100644
index 00000000000..41b470657cf
--- /dev/null
+++ b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/LicenseText.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace NuGet.PackageManagement.UI
+{
+ internal class LicenseText : IText
+ {
+ public LicenseText (string text, Uri link)
+ {
+ Text = text;
+ Link = link;
+ }
+
+ public string Text { get; set; }
+ public Uri Link { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/WarningText.cs b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/WarningText.cs
new file mode 100644
index 00000000000..dff77a14aa0
--- /dev/null
+++ b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/Models/WarningText.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace NuGet.PackageManagement.UI
+{
+ internal class WarningText : IText
+ {
+ public WarningText (string text)
+ {
+ Text = text;
+ }
+
+ public string Text { get; }
+ }
+}
\ No newline at end of file
diff --git a/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/PackageLicenseUtilities.cs b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/PackageLicenseUtilities.cs
new file mode 100644
index 00000000000..30a0ad015d4
--- /dev/null
+++ b/main/src/addins/MonoDevelop.PackageManagement/NuGet.PackageManagement.UI/PackageLicenseUtilities.cs
@@ -0,0 +1,134 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using MonoDevelop.Core;
+using NuGet.Packaging;
+using NuGet.Packaging.Licenses;
+using NuGet.Protocol;
+using NuGet.Protocol.Core.Types;
+
+namespace NuGet.PackageManagement.UI
+{
+ internal class PackageLicenseUtilities
+ {
+ internal static IReadOnlyList GenerateLicenseLinks (DetailedPackageMetadata metadata)
+ {
+ return GenerateLicenseLinks (metadata.LicenseMetadata, metadata.LicenseUrl, GettextCatalog.GetString ("{0} License", metadata.Id), metadata.LoadFileAsText);
+ }
+
+ internal static IReadOnlyList GenerateLicenseLinks (IPackageSearchMetadata metadata)
+ {
+ if (metadata is LocalPackageSearchMetadata localMetadata) {
+ return GenerateLicenseLinks (metadata.LicenseMetadata, metadata.LicenseUrl, GettextCatalog.GetString ("{0} License", metadata.Identity.Id), localMetadata.LoadFileAsText);
+ }
+ return GenerateLicenseLinks (metadata.LicenseMetadata, metadata.LicenseUrl, metadata.Identity.Id, null);
+ }
+
+ internal static IReadOnlyList GenerateLicenseLinks (LicenseMetadata licenseMetadata, Uri licenseUrl, string licenseFileHeader, Func loadFile)
+ {
+ if (licenseMetadata != null) {
+ return GenerateLicenseLinks (licenseMetadata, licenseFileHeader, loadFile);
+ } else if (licenseUrl != null) {
+ return new List () { new LicenseText (GettextCatalog.GetString ("View License"), licenseUrl) };
+ }
+ return new List ();
+ }
+
+ //internal static Paragraph[] GenerateParagraphs (string licenseContent)
+ //{
+ // var textParagraphs = licenseContent.Split (
+ // new[] { "\n\n", "\r\n\r\n" }, // Take care of paragraphs regardless of the name ending. It's a best effort, so weird line ending combinations might not work too well.
+ // StringSplitOptions.None);
+
+ // var paragraphs = new Paragraph[textParagraphs.Length];
+ // for (var i = 0; i < textParagraphs.Length; i++) {
+ // paragraphs[i] = new Paragraph (new Run (textParagraphs[i]));
+ // }
+ // return paragraphs;
+ //}
+
+ // Internal for testing purposes.
+ internal static IReadOnlyList GenerateLicenseLinks (LicenseMetadata metadata, string licenseFileHeader, Func loadFile)
+ {
+ var list = new List ();
+
+ if (metadata.WarningsAndErrors != null) {
+ list.Add (new WarningText (string.Join (Environment.NewLine, metadata.WarningsAndErrors)));
+ }
+
+ switch (metadata.Type) {
+ case LicenseType.Expression:
+
+ if (metadata.LicenseExpression != null && !metadata.LicenseExpression.IsUnlicensed ()) {
+ var identifiers = new List ();
+ PopulateLicenseIdentifiers (metadata.LicenseExpression, identifiers);
+
+ var licenseToBeProcessed = metadata.License;
+
+ foreach (var identifier in identifiers) {
+ var licenseStart = licenseToBeProcessed.IndexOf (identifier);
+ if (licenseStart != 0) {
+ list.Add (new FreeText (licenseToBeProcessed.Substring (0, licenseStart)));
+ }
+ var license = licenseToBeProcessed.Substring (licenseStart, identifier.Length);
+ list.Add (new LicenseText (license, new Uri (string.Format (LicenseMetadata.LicenseServiceLinkTemplate, license))));
+ licenseToBeProcessed = licenseToBeProcessed.Substring (licenseStart + identifier.Length);
+ }
+
+ if (licenseToBeProcessed.Length != 0) {
+ list.Add (new FreeText (licenseToBeProcessed));
+ }
+ } else {
+ list.Add (new FreeText (metadata.License));
+ }
+
+ break;
+
+ case LicenseType.File:
+ list.Add (new LicenseFileText (metadata.License, licenseFileHeader, loadFile, metadata.License));
+ break;
+
+ default:
+ break;
+ }
+
+ return list;
+ }
+
+ private static void PopulateLicenseIdentifiers (NuGetLicenseExpression expression, IList identifiers)
+ {
+ switch (expression.Type) {
+ case LicenseExpressionType.License:
+ var license = (NuGetLicense)expression;
+ identifiers.Add (license.Identifier);
+ break;
+
+ case LicenseExpressionType.Operator:
+ var licenseOperator = (LicenseOperator)expression;
+ switch (licenseOperator.OperatorType) {
+ case LicenseOperatorType.LogicalOperator:
+ var logicalOperator = (LogicalOperator)licenseOperator;
+ PopulateLicenseIdentifiers (logicalOperator.Left, identifiers);
+ PopulateLicenseIdentifiers (logicalOperator.Right, identifiers);
+ break;
+
+ case LicenseOperatorType.WithOperator:
+ var withOperator = (WithOperator)licenseOperator;
+ identifiers.Add (withOperator.License.Identifier);
+ identifiers.Add (withOperator.Exception.Identifier);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file