Skip to content
This repository was archived by the owner on Oct 4, 2021. It is now read-only.

[NuGet] Show license metadata #9085

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -126,21 +127,20 @@ protected override void OnShown ()

void AddPackage (PackageLicenseViewModel package)
{
var label = new Label {
Markup = string.Format ("<span weight='bold'>{0}</span> – {1}", package.Id, package.Author),
};
var titleBox = new VBox ();
titleBox.Spacing = 0;
titleBox.MarginBottom = 4;
titleBox.PackStart (new Label {
Markup = string.Format ("<span weight='bold'>{0}</span> – {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);
Expand All @@ -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<WarningText> warnings = null;
foreach (IText textLink in package.LicenseLinks) {
if (textLink is WarningText warning) {
warnings ??= new List<WarningText> ();
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;
Expand All @@ -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);
}
});
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// LicenseFileDialog.cs
//
// Author:
// Matt Ward <[email protected]>
//
// 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"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is Command.Ok already

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, originally that was used. However in the UX review there were complaints about showing 'Ok' instead of 'OK' as the button text. I guess I could change the Command.Ok but did not want to make that change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting. So this means all "OK" in MD are "Ok"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only dialogs that use Xwt and use the Command.Ok will show 'Ok'.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense to reword this globally to "OK". @hbons?

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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// LicenseFileTextExtensions.cs
//
// Author:
// Matt Ward <[email protected]>
//
// 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}");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// LicenseLinkMarkupBuilder.cs
//
// Author:
// Matt Ward <[email protected]>
//
// 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<WarningText> warnings;

public string GetMarkup (IReadOnlyList<IText> 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<WarningText> ();
warnings.Add (warning);
} else {
markupBuilder.Append (textLink.Text);
}
}

return StringBuilderCache.ReturnAndFree (markupBuilder);
}

public IEnumerable<WarningText> Warnings {
get { return warnings ?? Enumerable.Empty<WarningText> (); }
}

static string GetUriMarkup (Uri uri, string text)
{
return string.Format (
"<a href=\"{0}\">{1}</a>",
uri != null ? SecurityElement.Escape (uri.ToString ()) : string.Empty,
SecurityElement.Escape (text));
}
}
}
Loading