diff --git a/src/NuGet.Clients/PackageManagement.UI/PackageLoader.cs b/src/NuGet.Clients/PackageManagement.UI/PackageLoader.cs index 2a3f4ff73e0..3f44b6bae6b 100644 --- a/src/NuGet.Clients/PackageManagement.UI/PackageLoader.cs +++ b/src/NuGet.Clients/PackageManagement.UI/PackageLoader.cs @@ -446,15 +446,16 @@ private async Task SearchUpdatesAsync(int startIndex, Cancellation }; } - // Creates the list of installed packages that have updates available - private async Task CreatePackagesWithUpdatesAsync(CancellationToken ct) + // Creates the list of installed packages that have updates available. + // Returns the number of packages that have updates available. + public async Task CreatePackagesWithUpdatesAsync(CancellationToken ct) { _packagesWithUpdates = new List(); var metadataResource = await _sourceRepository.GetResourceAsync(); if (metadataResource == null) { - return; + return 0; } var installedPackages = (await GetInstalledPackagesAsync(latest: false, token: ct)) @@ -487,6 +488,8 @@ private async Task CreatePackagesWithUpdatesAsync(CancellationToken ct) } } } + + return _packagesWithUpdates.Count; } public async Task LoadItemsAsync(int startIndex, CancellationToken cancellationToken) diff --git a/src/NuGet.Clients/PackageManagement.UI/PackageManagement.UI.csproj b/src/NuGet.Clients/PackageManagement.UI/PackageManagement.UI.csproj index 2613e5a2d3b..634b2738b23 100644 --- a/src/NuGet.Clients/PackageManagement.UI/PackageManagement.UI.csproj +++ b/src/NuGet.Clients/PackageManagement.UI/PackageManagement.UI.csproj @@ -157,6 +157,9 @@ FileConflictDialog.xaml + + FilterLabel.xaml + InstalledIndicator.xaml @@ -174,6 +177,9 @@ PackageManagerControl.xaml + + PackageManagerTopPanel.xaml + PackageMetadataControl.xaml @@ -218,6 +224,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -238,6 +248,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/NuGet.Clients/PackageManagement.UI/Resources.Designer.cs b/src/NuGet.Clients/PackageManagement.UI/Resources.Designer.cs index 7d0b63d82c9..74b3b2fcabc 100644 --- a/src/NuGet.Clients/PackageManagement.UI/Resources.Designer.cs +++ b/src/NuGet.Clients/PackageManagement.UI/Resources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.0 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -366,33 +366,6 @@ public static string FileConflictAction_Prompt { } } - /// - /// Looks up a localized string similar to All. - /// - public static string Filter_All { - get { - return ResourceManager.GetString("Filter_All", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Installed. - /// - public static string Filter_Installed { - get { - return ResourceManager.GetString("Filter_Installed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Upgrade available. - /// - public static string Filter_UpgradeAvailable { - get { - return ResourceManager.GetString("Filter_UpgradeAvailable", resourceCulture); - } - } - /// /// Looks up a localized string similar to Ignore for now. /// @@ -420,6 +393,15 @@ public static string Label_Authors { } } + /// + /// Looks up a localized string similar to Browse. + /// + public static string Label_Browse { + get { + return ResourceManager.GetString("Label_Browse", resourceCulture); + } + } + /// /// Looks up a localized string similar to Date published:. /// @@ -492,6 +474,15 @@ public static string Label_InstallationOptions { } } + /// + /// Looks up a localized string similar to Installed. + /// + public static string Label_Installed { + get { + return ResourceManager.GetString("Label_Installed", resourceCulture); + } + } + /// /// Looks up a localized string similar to Installing:. /// @@ -627,6 +618,15 @@ public static string Label_UpdatedPackages { } } + /// + /// Looks up a localized string similar to Updates. + /// + public static string Label_Updates { + get { + return ResourceManager.GetString("Label_Updates", resourceCulture); + } + } + /// /// Looks up a localized string similar to Version:. /// diff --git a/src/NuGet.Clients/PackageManagement.UI/Resources.resx b/src/NuGet.Clients/PackageManagement.UI/Resources.resx index 9eb37bd48e9..1db835c154c 100644 --- a/src/NuGet.Clients/PackageManagement.UI/Resources.resx +++ b/src/NuGet.Clients/PackageManagement.UI/Resources.resx @@ -1,17 +1,17 @@  - @@ -243,10 +243,10 @@ Preview - - All + + Browse - + Installed @@ -297,8 +297,8 @@ Include prerelease - - Upgrade available + + Updates Installed diff --git a/src/NuGet.Clients/PackageManagement.UI/Resources/Brushes.cs b/src/NuGet.Clients/PackageManagement.UI/Resources/Brushes.cs index 640d6900fa9..0d159adf014 100644 --- a/src/NuGet.Clients/PackageManagement.UI/Resources/Brushes.cs +++ b/src/NuGet.Clients/PackageManagement.UI/Resources/Brushes.cs @@ -76,6 +76,7 @@ public static void Initialize() // when the keys are used in an xaml file. ContentBrushKey = SystemColors.WindowBrush; BackgroundBrushKey = SystemColors.WindowBrush; + ContentSelectedBrushKey = SystemColors.ActiveCaptionBrushKey; } else { diff --git a/src/NuGet.Clients/PackageManagement.UI/Xamls/FilterLabel.xaml b/src/NuGet.Clients/PackageManagement.UI/Xamls/FilterLabel.xaml new file mode 100644 index 00000000000..81c05669a88 --- /dev/null +++ b/src/NuGet.Clients/PackageManagement.UI/Xamls/FilterLabel.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NuGet.Clients/PackageManagement.UI/Xamls/FilterLabel.xaml.cs b/src/NuGet.Clients/PackageManagement.UI/Xamls/FilterLabel.xaml.cs new file mode 100644 index 00000000000..dcfe328d1a2 --- /dev/null +++ b/src/NuGet.Clients/PackageManagement.UI/Xamls/FilterLabel.xaml.cs @@ -0,0 +1,131 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace NuGet.PackageManagement.UI +{ + /// + /// Represents the filter label. E.g. Browse, Installed, Update Available. + /// + public partial class FilterLabel : UserControl + { + public FilterLabel() + { + InitializeComponent(); + } + + public event EventHandler ControlSelected; + + public Filter Filter + { + get; + set; + } + + public string Text + { + get + { + return _labelText.Text; + } + set + { + _labelText.Text = value; + } + } + + private bool _selected; + + public bool Selected + { + get + { + return _selected; + } + set + { + _selected = value; + if (_selected) + { + _labelText.SetResourceReference( + TextBlock.ForegroundProperty, + Brushes.ContentSelectedBrushKey); + _underline.Visibility = Visibility.Visible; + + if (ControlSelected != null) + { + ControlSelected(this, EventArgs.Empty); + } + } + else + { + _labelText.SetResourceReference( + TextBlock.ForegroundProperty, + Brushes.UIText); + _underline.Visibility = Visibility.Hidden; + } + } + } + + private void ButtonClicked(object sender, RoutedEventArgs e) + { + if (_selected) + { + // already selected. Do nothing + return; + } + else + { + Selected = true; + } + } + + private int _count; + + public int Count + { + get + { + return _count; + } + set + { + _count = value; + if (_count > 0) + { + _textBlockCount.Text = _count.ToString(CultureInfo.CurrentCulture); + _textBlockCountContainer.Visibility = Visibility.Visible; + } + else + { + _textBlockCountContainer.Visibility = Visibility.Collapsed; + } + } + } + + private void _labelText_MouseEnter(object sender, MouseEventArgs e) + { + _labelText.SetResourceReference( + TextBlock.ForegroundProperty, + Brushes.ContentSelectedBrushKey); + } + + private void _labelText_MouseLeave(object sender, MouseEventArgs e) + { + if (_selected) + { + _labelText.SetResourceReference( + TextBlock.ForegroundProperty, + Brushes.ContentSelectedBrushKey); + } + else + { + _labelText.SetResourceReference( + TextBlock.ForegroundProperty, + Brushes.UIText); + } + } + } +} \ No newline at end of file diff --git a/src/NuGet.Clients/PackageManagement.UI/Xamls/PackageManagerControl.xaml b/src/NuGet.Clients/PackageManagement.UI/Xamls/PackageManagerControl.xaml index fee5eb1ac01..b99873cb450 100644 --- a/src/NuGet.Clients/PackageManagement.UI/Xamls/PackageManagerControl.xaml +++ b/src/NuGet.Clients/PackageManagement.UI/Xamls/PackageManagerControl.xaml @@ -53,119 +53,16 @@ FontSize="{Binding ElementName=_self,Path=FontSize,Converter={StaticResource Font155PercentSizeConverter}}" Margin="8,8">Package Manager - - - - - - - - - - - - - - - - - - - - - - - - - + Margin="24,0,24,17" + SettingsButtonClicked="SettingsButtonClicked" + FilterChanged="Filter_SelectionChanged" + PrereleaseCheckChanged="CheckboxPrerelease_CheckChanged" + SourceRepoListSelectionChanged="SourceRepoList_SelectionChanged" + /> + item.Filter == settings.SelectedFilter); - } - else - { - _filter.SelectedItem = items[0]; + _topPanel.SelectFilter(settings.SelectedFilter); } } @@ -193,7 +178,7 @@ private void ApplySettings( _detailModel.Options.ShowPreviewWindow = settings.ShowPreviewWindow; _detailModel.Options.RemoveDependencies = settings.RemoveDependencies; _detailModel.Options.ForceRemove = settings.ForceRemove; - _checkboxPrerelease.IsChecked = settings.IncludePrerelease; + _topPanel.CheckboxPrerelease.IsChecked = settings.IncludePrerelease; SetSelectedDepencyBehavior(settings.DependencyBehavior); @@ -218,14 +203,14 @@ private void Sources_PackageSourcesChanged(object sender, EventArgs e) _dontStartNewSearch = true; try { - var oldActiveSource = _sourceRepoList.SelectedItem as SourceRepository; + var oldActiveSource = _topPanel.SourceRepoList.SelectedItem as SourceRepository; var newSources = GetEnabledSources(); // Update the source repo list with the new value. - _sourceRepoList.Items.Clear(); + _topPanel.SourceRepoList.Items.Clear(); foreach (var source in newSources) { - _sourceRepoList.Items.Add(source); + _topPanel.SourceRepoList.Items.Add(source); } SetNewActiveSource(newSources, oldActiveSource); @@ -240,6 +225,7 @@ private void Sources_PackageSourcesChanged(object sender, EventArgs e) { SaveSettings(); SearchPackageInActivePackageSource(_windowSearchHost.SearchQuery.SearchString); + RefreshAvailableUpdatesCount(); } } finally @@ -285,13 +271,8 @@ public void SaveSettings() settings.ForceRemove = _detailModel.Options.ForceRemove; settings.DependencyBehavior = _detailModel.Options.SelectedDependencyBehavior.Behavior; settings.FileConflictAction = _detailModel.Options.SelectedFileConflictAction.Action; - settings.IncludePrerelease = _checkboxPrerelease.IsChecked == true; - - var filterItem = _filter.SelectedItem as FilterItem; - if (filterItem != null) - { - settings.SelectedFilter = filterItem.Filter; - } + settings.IncludePrerelease = _topPanel.CheckboxPrerelease.IsChecked == true; + settings.SelectedFilter = _topPanel.Filter; Model.Context.AddSettings(GetSettingsKey(), settings); } @@ -343,7 +324,7 @@ private void SetNewActiveSource(IEnumerable newSources, Source } } - _sourceRepoList.SelectedItem = ActiveSource; + _topPanel.SourceRepoList.SelectedItem = ActiveSource; if (ActiveSource != null) { Model.Context.SourceProvider.PackageSourceProvider.SaveActivePackageSource(ActiveSource.PackageSource); @@ -458,11 +439,11 @@ private void SetTitle() private void InitSourceRepoList(UserSettings settings) { // init source repo list - _sourceRepoList.Items.Clear(); + _topPanel.SourceRepoList.Items.Clear(); var enabledSources = GetEnabledSources(); foreach (var source in enabledSources) { - _sourceRepoList.Items.Add(source); + _topPanel.SourceRepoList.Items.Add(source); } // get active source name. @@ -493,7 +474,7 @@ private void InitSourceRepoList(UserSettings settings) if (ActiveSource != null) { - _sourceRepoList.SelectedItem = ActiveSource; + _topPanel.SourceRepoList.SelectedItem = ActiveSource; } } @@ -501,8 +482,7 @@ private bool ShowInstalled { get { - var filterItem = _filter.SelectedItem as FilterItem; - return filterItem != null && filterItem.Filter == Filter.Installed; + return _topPanel.Filter == Filter.Installed; } } @@ -510,14 +490,13 @@ private bool ShowUpdatesAvailable { get { - var filterItem = _filter.SelectedItem as FilterItem; - return filterItem != null && filterItem.Filter == Filter.UpdatesAvailable; + return _topPanel.Filter == Filter.UpdatesAvailable; } } public bool IncludePrerelease { - get { return _checkboxPrerelease.IsChecked == true; } + get { return _topPanel.CheckboxPrerelease.IsChecked == true; } } internal SourceRepository ActiveSource { get; private set; } @@ -529,12 +508,7 @@ private void SearchPackageInActivePackageSource(string searchText) { NuGetUIThreadHelper.JoinableTaskFactory.RunAsync(async delegate { - var filterItem = _filter.SelectedItem as FilterItem; - var filter = filterItem != null ? - filterItem.Filter : - Filter.All; - - var option = new PackageLoaderOption(filter, IncludePrerelease); + var option = new PackageLoaderOption(_topPanel.Filter, IncludePrerelease); var loader = new PackageLoader( option, Model.Context.PackageManager, @@ -546,7 +520,24 @@ private void SearchPackageInActivePackageSource(string searchText) }); } - private void SettingsButtonClick(object sender, RoutedEventArgs e) + private void RefreshAvailableUpdatesCount() + { + NuGetUIThreadHelper.JoinableTaskFactory.RunAsync(async delegate + { + _topPanel._labelUpgradeAvailable.Count = 0; + var updatesLoader = new PackageLoader( + new PackageLoaderOption(Filter.UpdatesAvailable, IncludePrerelease), + Model.Context.PackageManager, + Model.Context.Projects, + ActiveSource, + String.Empty); + await updatesLoader.InitializeAsync(); + int updatesCount = await updatesLoader.CreatePackagesWithUpdatesAsync(CancellationToken.None); + _topPanel._labelUpgradeAvailable.Count = updatesCount; + }); + } + + private void SettingsButtonClicked(object sender, EventArgs e) { Model.UIController.LaunchNuGetOptionsDialog(); } @@ -570,9 +561,9 @@ private async Task UpdateDetailPaneAsync() else { _packageDetail.Visibility = Visibility.Visible; - var selectedFilter = _filter.SelectedItem as FilterItem; - await _detailModel.SetCurrentPackage(selectedPackage, - selectedFilter == null ? Filter.All : selectedFilter.Filter); + await _detailModel.SetCurrentPackage( + selectedPackage, + _topPanel.Filter); _packageDetail.DataContext = _detailModel; _packageDetail.ScrollToHome(); @@ -601,26 +592,27 @@ private static string GetPackageSourceTooltip(Configuration.PackageSource packag packageSource.Source); } - private void SourceRepoList_SelectionChanged(object sender, SelectionChangedEventArgs e) + private void SourceRepoList_SelectionChanged(object sender, EventArgs e) { if (_dontStartNewSearch || !_initialized) { return; } - ActiveSource = _sourceRepoList.SelectedItem as SourceRepository; + ActiveSource = _topPanel.SourceRepoList.SelectedItem as SourceRepository; if (ActiveSource != null) { - _sourceTooltip.Visibility = Visibility.Visible; - _sourceTooltip.DataContext = GetPackageSourceTooltip(ActiveSource.PackageSource); + _topPanel.SourceToolTip.Visibility = Visibility.Visible; + _topPanel.SourceToolTip.DataContext = GetPackageSourceTooltip(ActiveSource.PackageSource); Model.Context.SourceProvider.PackageSourceProvider.SaveActivePackageSource(ActiveSource.PackageSource); SaveSettings(); SearchPackageInActivePackageSource(_windowSearchHost.SearchQuery.SearchString); + RefreshAvailableUpdatesCount(); } } - private void Filter_SelectionChanged(object sender, SelectionChangedEventArgs e) + private void Filter_SelectionChanged(object sender, EventArgs e) { if (_initialized) { @@ -649,12 +641,14 @@ internal void UpdatePackageStatus() continue; } - package.StatusProvider = new Lazy>(async () => await GetPackageStatus( + package.StatusProvider = new Lazy>(async () => await GetPackageInfo( package.Id, installedPackages, package.Versions)); } } + + RefreshAvailableUpdatesCount(); } private static IReadOnlyList GetInstalledPackages(IEnumerable projects) @@ -681,7 +675,7 @@ internal void UpdatePackageStatus() /// All installed pacakges. /// List of all versions of the package. /// The status of the package in the installation target. - private static async Task GetPackageStatus( + private static async Task GetPackageInfo( string packageId, IReadOnlyList installedPackages, Lazy>> allVersions) @@ -699,24 +693,24 @@ private static async Task GetPackageStatus( .OrderBy(r => r.PackageIdentity.Version) .FirstOrDefault(); - PackageStatus status; + PackageStatus result; if (minimumInstalledPackage != null) { if (minimumInstalledPackage.PackageIdentity.Version < latestStableVersion) { - status = PackageStatus.UpdateAvailable; + result = PackageStatus.UpdateAvailable; } else { - status = PackageStatus.Installed; + result = PackageStatus.Installed; } } else { - status = PackageStatus.NotInstalled; + result = PackageStatus.NotInstalled; } - return status; + return result; } private void SearchControl_SearchStart(object sender, EventArgs e) @@ -729,15 +723,18 @@ private void SearchControl_SearchStart(object sender, EventArgs e) SearchPackageInActivePackageSource(_windowSearchHost.SearchQuery.SearchString); } - private void CheckboxPrerelease_CheckChanged(object sender, RoutedEventArgs e) + private void CheckboxPrerelease_CheckChanged(object sender, EventArgs e) { if (!_initialized) { return; } - RegistrySettingUtility.SetBooleanSetting(Constants.IncludePrereleaseRegistryName, _checkboxPrerelease.IsChecked == true); + RegistrySettingUtility.SetBooleanSetting( + Constants.IncludePrereleaseRegistryName, + _topPanel.CheckboxPrerelease.IsChecked == true); SearchPackageInActivePackageSource(_windowSearchHost.SearchQuery.SearchString); + RefreshAvailableUpdatesCount(); } internal class SearchQuery : IVsSearchQuery @@ -783,7 +780,7 @@ public void ProvideSearchSettings(IVsUIDataSource pSearchSettings) // so that the code can be run on both dev12 & dev14. If we use the type directly, // there will be type mismatch error. dynamic settings = pSearchSettings; - settings.ControlMinWidth = (uint)_searchControlParent.MinWidth; + settings.ControlMinWidth = (uint)_topPanel.SearchControlParent.MinWidth; settings.ControlMaxWidth = uint.MaxValue; settings.SearchWatermark = GetSearchText(); } diff --git a/src/NuGet.Clients/PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml b/src/NuGet.Clients/PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml new file mode 100644 index 00000000000..8f5bfb7f9a7 --- /dev/null +++ b/src/NuGet.Clients/PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NuGet.Clients/PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml.cs b/src/NuGet.Clients/PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml.cs new file mode 100644 index 00000000000..e2cd81286c5 --- /dev/null +++ b/src/NuGet.Clients/PackageManagement.UI/Xamls/PackageManagerTopPanel.xaml.cs @@ -0,0 +1,151 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; + +namespace NuGet.PackageManagement.UI +{ + /// + /// The panel which is located at the top of the package manager window. + /// + public partial class PackageManagerTopPanel : UserControl + { + private FilterLabel _selectedFilter; + + public PackageManagerTopPanel() + { + InitializeComponent(); + + _labelBrowse.Selected = true; + _selectedFilter = _labelBrowse; + } + + // the control that is used as container for the search box. + public Border SearchControlParent + { + get + { + return _searchControlParent; + } + } + + public CheckBox CheckboxPrerelease + { + get + { + return _checkboxPrerelease; + } + } + + public ComboBox SourceRepoList + { + get + { + return _sourceRepoList; + } + } + + public ToolTip SourceToolTip + { + get + { + return _sourceTooltip; + } + } + + public Filter Filter + { + get + { + return _selectedFilter.Filter; + } + } + + private void _checkboxPrerelease_Checked(object sender, RoutedEventArgs e) + { + if (PrereleaseCheckChanged != null) + { + PrereleaseCheckChanged(this, EventArgs.Empty); + } + } + + private void _checkboxPrerelease_Unchecked(object sender, RoutedEventArgs e) + { + if (PrereleaseCheckChanged != null) + { + PrereleaseCheckChanged(this, EventArgs.Empty); + } + } + + private void _sourceRepoList_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (SourceRepoListSelectionChanged != null) + { + SourceRepoListSelectionChanged(this, EventArgs.Empty); + } + } + + private void _settingsButton_Click(object sender, RoutedEventArgs e) + { + if (SettingsButtonClicked != null) + { + SettingsButtonClicked(this, EventArgs.Empty); + } + } + + [SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", + Justification = "It is referenced by the xaml file")] + private void FilterLabel_ControlSelected(object sender, EventArgs e) + { + var selectedFilter = (FilterLabel)sender; + if (selectedFilter == _selectedFilter) + { + return; + } + + if (_selectedFilter != null) + { + _selectedFilter.Selected = false; + } + + _selectedFilter = selectedFilter; + if (FilterChanged != null) + { + FilterChanged(this, EventArgs.Empty); + } + } + + public event EventHandler FilterChanged; + + public event EventHandler SettingsButtonClicked; + + public event EventHandler PrereleaseCheckChanged; + + public event EventHandler SourceRepoListSelectionChanged; + + public void SelectFilter(Filter selectedFilter) + { + if (_selectedFilter != null) + { + _selectedFilter.Selected = false; + } + + switch (selectedFilter) + { + case Filter.All: + _selectedFilter = _labelBrowse; + break; + + case Filter.Installed: + _selectedFilter = _labelInstalled; + break; + + case Filter.UpdatesAvailable: + _selectedFilter = _labelUpgradeAvailable; + break; + } + + _selectedFilter.Selected = true; + } + } +} \ No newline at end of file