Skip to content
Browse files

add window chrome

  • Loading branch information...
1 parent c41464a commit 85a699b72c96e4a5ca60b42b37882e084f5242bb @yysun committed Apr 5, 2012
Showing with 12,518 additions and 76 deletions.
  1. +32 −0 BasicSccProvider.sln
  2. +9 −1 GitUI/GitUI.csproj
  3. +45 −2 GitUI/MainWindow.xaml
  4. +21 −22 GitUI/MainWindow.xaml.cs
  5. +17 −0 GitUI/Microsoft.Windows.Shell/JumpItem.cs
  6. +1,151 −0 GitUI/Microsoft.Windows.Shell/JumpList.cs
  7. +14 −0 GitUI/Microsoft.Windows.Shell/JumpPath.cs
  8. +26 −0 GitUI/Microsoft.Windows.Shell/JumpTask.cs
  9. +161 −0 GitUI/Microsoft.Windows.Shell/Microsoft.Windows.Shell.csproj
  10. +31 −0 GitUI/Microsoft.Windows.Shell/Properties/AssemblyInfo.cs
  11. +95 −0 GitUI/Microsoft.Windows.Shell/Standard/ComGuids.cs
  12. +373 −0 GitUI/Microsoft.Windows.Shell/Standard/Debug.cs
  13. +132 −0 GitUI/Microsoft.Windows.Shell/Standard/DoubleUtil.cs
  14. +86 −0 GitUI/Microsoft.Windows.Shell/Standard/DpiHelper.cs
  15. +508 −0 GitUI/Microsoft.Windows.Shell/Standard/ErrorCodes.cs
  16. +169 −0 GitUI/Microsoft.Windows.Shell/Standard/MessageWindow.cs
  17. +3,458 −0 GitUI/Microsoft.Windows.Shell/Standard/NativeMethods.cs
  18. +967 −0 GitUI/Microsoft.Windows.Shell/Standard/ShellProvider.cs
  19. +341 −0 GitUI/Microsoft.Windows.Shell/Standard/StreamHelper.cs
  20. +1,037 −0 GitUI/Microsoft.Windows.Shell/Standard/Utilities.cs
  21. +312 −0 GitUI/Microsoft.Windows.Shell/Standard/Verify.cs
  22. +91 −0 GitUI/Microsoft.Windows.Shell/SystemCommands.cs
  23. +557 −0 GitUI/Microsoft.Windows.Shell/SystemParameters2.cs
  24. +856 −0 GitUI/Microsoft.Windows.Shell/TaskbarItemInfo.cs
  25. +347 −0 GitUI/Microsoft.Windows.Shell/ThumbButtonInfo.cs
  26. +36 −0 GitUI/Microsoft.Windows.Shell/ThumbButtonInfoCollection.cs
  27. +235 −0 GitUI/Microsoft.Windows.Shell/WindowChrome.cs
  28. +1,195 −0 GitUI/Microsoft.Windows.Shell/WindowChromeWorker.cs
  29. +3 −0 GitUI/Microsoft.Windows.Shell/app.config
  30. +65 −0 GitUI/Resources/Microsoft.Windows.Shell.dll.CodeAnalysisLog.xml
  31. +3 −0 GitUI/Resources/Microsoft.Windows.Shell.dll.config
  32. 0 GitUI/Resources/Microsoft.Windows.Shell.dll.lastcodeanalysissucceeded
  33. +9 −9 GitUI/UI/CommitDetails.xaml
  34. +47 −17 GitUI/UI/MainToolBar.xaml
  35. +11 −5 GitUI/UI/MainToolBar.xaml.cs
  36. +9 −10 GitUI/UI/PendingChanges.xaml
  37. +20 −0 GitUI/UI/PendingChanges.xaml.cs
  38. +46 −9 GitUI/UI/ResourceDictionary.xaml
  39. +3 −1 GitUI/ZoomAndPan/ZoomAndPan.csproj
View
32 BasicSccProvider.sln
@@ -33,55 +33,87 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.AvalonEdit", "G
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZoomAndPan", "GitUI\ZoomAndPan\ZoomAndPan.csproj", "{468ADEDE-6F5E-40C0-99C9-C6DAC4FAC934}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Windows.Shell", "GitUI\Microsoft.Windows.Shell\Microsoft.Windows.Shell.csproj", "{55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}"
+EndProject
Global
GlobalSection(TestCaseManagementSettings) = postSolution
CategoryFile = BasicSccProvider.vsmdi
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{ADECE07A-5D80-4950-9DA5-A681CE2F5106}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ADECE07A-5D80-4950-9DA5-A681CE2F5106}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ADECE07A-5D80-4950-9DA5-A681CE2F5106}.Debug|x86.ActiveCfg = Debug|Any CPU
{ADECE07A-5D80-4950-9DA5-A681CE2F5106}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADECE07A-5D80-4950-9DA5-A681CE2F5106}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ADECE07A-5D80-4950-9DA5-A681CE2F5106}.Release|x86.ActiveCfg = Release|Any CPU
{AE3D1ACA-680D-4CDE-B71E-B7B135F32899}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE3D1ACA-680D-4CDE-B71E-B7B135F32899}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AE3D1ACA-680D-4CDE-B71E-B7B135F32899}.Debug|x86.ActiveCfg = Debug|Any CPU
{AE3D1ACA-680D-4CDE-B71E-B7B135F32899}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE3D1ACA-680D-4CDE-B71E-B7B135F32899}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AE3D1ACA-680D-4CDE-B71E-B7B135F32899}.Release|x86.ActiveCfg = Release|Any CPU
{E3BFF8B4-189C-496A-A817-7E8B31E22B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3BFF8B4-189C-496A-A817-7E8B31E22B91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E3BFF8B4-189C-496A-A817-7E8B31E22B91}.Debug|x86.ActiveCfg = Debug|Any CPU
{E3BFF8B4-189C-496A-A817-7E8B31E22B91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3BFF8B4-189C-496A-A817-7E8B31E22B91}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E3BFF8B4-189C-496A-A817-7E8B31E22B91}.Release|x86.ActiveCfg = Release|Any CPU
{72944A6C-45FF-4EF8-B349-8C9CABF519D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72944A6C-45FF-4EF8-B349-8C9CABF519D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72944A6C-45FF-4EF8-B349-8C9CABF519D4}.Debug|x86.ActiveCfg = Debug|Any CPU
{72944A6C-45FF-4EF8-B349-8C9CABF519D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72944A6C-45FF-4EF8-B349-8C9CABF519D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72944A6C-45FF-4EF8-B349-8C9CABF519D4}.Release|x86.ActiveCfg = Release|Any CPU
{A19E6F3F-A25B-4B01-8922-CF0CC35C781D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A19E6F3F-A25B-4B01-8922-CF0CC35C781D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A19E6F3F-A25B-4B01-8922-CF0CC35C781D}.Debug|x86.ActiveCfg = Debug|Any CPU
{A19E6F3F-A25B-4B01-8922-CF0CC35C781D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A19E6F3F-A25B-4B01-8922-CF0CC35C781D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A19E6F3F-A25B-4B01-8922-CF0CC35C781D}.Release|x86.ActiveCfg = Release|Any CPU
{E1FA8F29-5BAB-4FB7-8362-B8CE67ECF7C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1FA8F29-5BAB-4FB7-8362-B8CE67ECF7C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E1FA8F29-5BAB-4FB7-8362-B8CE67ECF7C2}.Debug|x86.ActiveCfg = Debug|Any CPU
{E1FA8F29-5BAB-4FB7-8362-B8CE67ECF7C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1FA8F29-5BAB-4FB7-8362-B8CE67ECF7C2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E1FA8F29-5BAB-4FB7-8362-B8CE67ECF7C2}.Release|x86.ActiveCfg = Release|Any CPU
{161E81E2-25E5-4134-8D2D-595419B1265B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{161E81E2-25E5-4134-8D2D-595419B1265B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {161E81E2-25E5-4134-8D2D-595419B1265B}.Debug|x86.ActiveCfg = Debug|Any CPU
{161E81E2-25E5-4134-8D2D-595419B1265B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{161E81E2-25E5-4134-8D2D-595419B1265B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {161E81E2-25E5-4134-8D2D-595419B1265B}.Release|x86.ActiveCfg = Release|Any CPU
{C756204F-28E1-473A-8160-E697E3303F1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C756204F-28E1-473A-8160-E697E3303F1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C756204F-28E1-473A-8160-E697E3303F1B}.Debug|x86.ActiveCfg = Debug|Any CPU
{C756204F-28E1-473A-8160-E697E3303F1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C756204F-28E1-473A-8160-E697E3303F1B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C756204F-28E1-473A-8160-E697E3303F1B}.Release|x86.ActiveCfg = Release|Any CPU
{6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}.Debug|x86.ActiveCfg = Debug|Any CPU
{6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}.Release|x86.ActiveCfg = Release|Any CPU
{468ADEDE-6F5E-40C0-99C9-C6DAC4FAC934}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{468ADEDE-6F5E-40C0-99C9-C6DAC4FAC934}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {468ADEDE-6F5E-40C0-99C9-C6DAC4FAC934}.Debug|x86.ActiveCfg = Debug|Any CPU
{468ADEDE-6F5E-40C0-99C9-C6DAC4FAC934}.Release|Any CPU.ActiveCfg = Release|Any CPU
{468ADEDE-6F5E-40C0-99C9-C6DAC4FAC934}.Release|Any CPU.Build.0 = Release|Any CPU
+ {468ADEDE-6F5E-40C0-99C9-C6DAC4FAC934}.Release|x86.ActiveCfg = Release|Any CPU
+ {55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}.Debug|x86.ActiveCfg = Debug|x86
+ {55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}.Debug|x86.Build.0 = Debug|x86
+ {55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}.Release|x86.ActiveCfg = Release|x86
+ {55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
View
10 GitUI/GitUI.csproj
@@ -186,7 +186,7 @@
<ProjectReference Include="..\GitApi\GitApi.csproj">
<Project>{161E81E2-25E5-4134-8D2D-595419B1265B}</Project>
<Name>GitApi</Name>
- <Private>True</Private>
+ <Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\ngit\NGit\NGit.csproj">
<Project>{E3BFF8B4-189C-496A-A817-7E8B31E22B91}</Project>
@@ -208,6 +208,11 @@
<Name>ICSharpCode.AvalonEdit</Name>
<Private>False</Private>
</ProjectReference>
+ <ProjectReference Include="Microsoft.Windows.Shell\Microsoft.Windows.Shell.csproj">
+ <Project>{55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}</Project>
+ <Name>Microsoft.Windows.Shell</Name>
+ <Private>False</Private>
+ </ProjectReference>
<ProjectReference Include="ZoomAndPan\ZoomAndPan.csproj">
<Project>{468ADEDE-6F5E-40C0-99C9-C6DAC4FAC934}</Project>
<Name>ZoomAndPan</Name>
@@ -230,6 +235,9 @@
<ItemGroup>
<EmbeddedResource Include="Resources\ZoomAndPan.dll" />
</ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Resources\Microsoft.Windows.Shell.dll" />
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>copy GitUI.exe "$(SolutionDir)Dragon.pkg"</PostBuildEvent>
View
47 GitUI/MainWindow.xaml
@@ -1,12 +1,15 @@
<Window xmlns:my="clr-namespace:GitUI.UI" xmlns:cmd="clr-namespace:GitUI"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:GitScc_UI="clr-namespace:GitScc.UI" x:Class="GitUI.MainWindow" Height="350" Width="525"
+ xmlns:shell="http://schemas.microsoft.com/winfx/2006/xaml/presentation/shell"
+ xmlns:GitScc_UI="clr-namespace:GitScc.UI" x:Class="GitUI.MainWindow" Height="460" Width="800"
Loaded="Window_Loaded" PreviewKeyDown="Window_PreviewKeyDown"
- Icon="/GitUI;component/Resources/dragon.png"
+ Icon="/GitUI;component/Resources/dragon.png"
AllowDrop="True" Drop="Window_Drop">
<Window.CommandBindings>
+ <CommandBinding Command="{x:Static shell:SystemCommands.CloseWindowCommand}" Executed="_OnSystemCommandCloseWindow"/>
+
<CommandBinding Command="cmd:HistoryViewCommands.OpenCommitDetails" Executed="OpenCommitDetails_Executed" />
<CommandBinding Command="cmd:HistoryViewCommands.CloseCommitDetails" Executed="CloseCommitDetails_Executed" />
<CommandBinding Command="cmd:HistoryViewCommands.SelectCommit" Executed="SelectCommit_Executed" />
@@ -18,6 +21,46 @@
<CommandBinding Command="cmd:HistoryViewCommands.PendingChanges" Executed="PendingChanges_Executed" />
</Window.CommandBindings>
+ <Window.Resources>
+
+ <Style x:Key="GradientStyle" TargetType="{x:Type cmd:MainWindow}">
+ <Setter Property="shell:WindowChrome.WindowChrome">
+ <Setter.Value>
+ <shell:WindowChrome ResizeBorderThickness="4" CaptionHeight="6" CornerRadius="6,6,6,6" GlassFrameThickness="0"/>
+ </Setter.Value>
+ </Setter>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type cmd:MainWindow}">
+ <Grid Background="#FFDBDBDB">
+ <!--<Border>
+ <Border.Background>
+ <LinearGradientBrush StartPoint="0,0" EndPoint="0,50" MappingMode="Absolute">
+ <GradientStop Offset="0" Color="#AAF2F2F2"/>
+ <GradientStop Offset="1" Color="#AAFFFFFF"/>
+ </LinearGradientBrush>
+ </Border.Background>
+ <ContentPresenter Margin="2" Content="{TemplateBinding Content}"/>
+ </Border>-->
+ <ContentPresenter Margin="0" Content="{TemplateBinding Content}"/>
+ <!--<Button shell:WindowChrome.IsHitTestVisibleInChrome="True"
+ VerticalAlignment="Top" HorizontalAlignment="Right" Content="x"
+ Command="{x:Static shell:SystemCommands.CloseWindowCommand}">
+
+ </Button>-->
+ <!--
+ Width="{Binding Source={x:Static shell:SystemParameters2.Current}, Path=WindowCaptionButtonsLocation.Width}"
+ Height="{Binding Source={x:Static shell:SystemParameters2.Current}, Path=WindowCaptionButtonsLocation.Height}"
+ Margin="{Binding Source={x:Static shell:SystemParameters2.Current}, Path=WindowCaptionButtonsLocation, Converter={StaticResource CaptionButtonMarginConverter}}"
+ -->
+
+ </Grid>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+ </Window.Resources>
+
<Grid x:Name="rootGrid">
<!--<Grid.RowDefinitions>
<RowDefinition Height="*"/>
View
43 GitUI/MainWindow.xaml.cs
@@ -6,6 +6,7 @@
using System.Windows.Media.Animation;
using System.Windows.Threading;
using GitScc;
+using Microsoft.Windows.Shell;
namespace GitUI
{
@@ -32,6 +33,8 @@ public MainWindow()
private void Window_Loaded(object sender, RoutedEventArgs e)
{
+ this.Style = (Style)Resources["GradientStyle"];
+
GitBash.GitExePath = GitSccOptions.Current.GitBashPath;
if (!GitBash.Exists) GitBash.GitExePath = TryFindFile(new string[] {
@@ -51,20 +54,15 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
this.gitViewModel.GraphChanged += (o, reload) =>
{
// show loading sign immediately
- Action a = () => loading.Visibility = Visibility.Visible;
- this.Dispatcher.BeginInvoke(a, DispatcherPriority.Render);
+ ////Action a = () => loading.Visibility = Visibility.Visible;
+ ////this.Dispatcher.BeginInvoke(a, DispatcherPriority.Render);
+ loading.Visibility = Visibility.Visible;
Action act = () =>
{
- loading.Visibility = Visibility.Visible;
-
if (gitViewModel.Tracker.HasGitRepository)
this.Title = gitViewModel.Tracker.GitWorkingDirectory;
this.graph.Show(gitViewModel.Tracker, reload != null);
-
- //this.gitConsole.WorkingDirectory = gitViewModel.Tracker.HasGitRepository ?
- // gitViewModel.Tracker.GitWorkingDirectory :
- // gitViewModel.WorkingDirectory;
};
this.Dispatcher.BeginInvoke(act, DispatcherPriority.ApplicationIdle);
};
@@ -105,7 +103,9 @@ private void ShowCommitDetails(string id)
var animationDuration = TimeSpan.FromSeconds(.5);
var animation = new DoubleAnimation(0, new Duration(animationDuration));
animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
+
loading.Visibility = Visibility.Visible;
+
animation.Completed += (_, e) =>
{
this.details.Show(this.gitViewModel.Tracker, id);
@@ -168,20 +168,11 @@ private void ScrollToCommit_Executed(object sender, ExecutedRoutedEventArgs e)
private void GraphLoaded_Executed(object sender, ExecutedRoutedEventArgs e)
{
- //var animationDuration = TimeSpan.FromSeconds(5);
- //var animation = new DoubleAnimation(0.8, new Duration(animationDuration));
- ////animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseIn };
- //animation.Completed += (o, _) =>
- //{
- // this.loading.Visibility = Visibility.Collapsed;
- // this.loading.Opacity = 1;
- //};
- //this.loading.BeginAnimation(UIElement.OpacityProperty, animation);
-
gitViewModel.DisableAutoRefresh();
this.loading.Visibility = Visibility.Collapsed;
- this.topToolBar.GitViewModel = gitViewModel;
+ this.topToolBar.GitViewModel = gitViewModel;
+
this.Title = gitViewModel.Tracker.HasGitRepository ?
string.Format("{0} ({1})", gitViewModel.Tracker.GitWorkingDirectory, gitViewModel.Tracker.CurrentBranch) :
string.Format("{0} (No Repository)", gitViewModel.WorkingDirectory);
@@ -240,10 +231,13 @@ private void PendingChanges_Executed(object sender, ExecutedRoutedEventArgs e)
animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
loading.Visibility = Visibility.Visible;
- animation.Completed += (_, x) => loading.Visibility = Visibility.Collapsed;
-
+ animation.Completed += (_, x) =>
+ {
+ this.pendingChanges.Refresh();
+ loading.Visibility = Visibility.Collapsed;
+ };
this.pendingChanges.RenderTransform.BeginAnimation(TranslateTransform.XProperty, animation);
- this.pendingChanges.Refresh();
+
}
catch (Exception ex)
{
@@ -271,5 +265,10 @@ private void Window_Drop(object sender, DragEventArgs e)
}
}
}
+
+ private void _OnSystemCommandCloseWindow(object sender, ExecutedRoutedEventArgs e)
+ {
+ SystemCommands.CloseWindow(this);
+ }
}
}
View
17 GitUI/Microsoft.Windows.Shell/JumpItem.cs
@@ -0,0 +1,17 @@
+/**************************************************************************\
+ Copyright Microsoft Corporation. All Rights Reserved.
+\**************************************************************************/
+
+namespace Microsoft.Windows.Shell
+{
+ public abstract class JumpItem
+ {
+ // This class is just provided to strongly type the JumpList's contents.
+ // It's not externally extendable.
+ internal JumpItem()
+ {
+ }
+
+ public string CustomCategory { get; set; }
+ }
+}
View
1,151 GitUI/Microsoft.Windows.Shell/JumpList.cs
@@ -0,0 +1,1151 @@
+/**************************************************************************\
+ Copyright Microsoft Corporation. All Rights Reserved.
+\**************************************************************************/
+
+namespace Microsoft.Windows.Shell
+{
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.IO;
+ using System.Reflection;
+ using System.Text;
+ using System.Threading;
+ using System.Windows;
+ using System.Windows.Markup;
+ using Standard;
+
+ /// <summary>
+ /// The list of possible reasons why a JumpItem would be rejected from a JumpList when applied.
+ /// </summary>
+ public enum JumpItemRejectionReason
+ {
+ /// <summary>
+ /// Unknown reason. This should not be used.
+ /// </summary>
+ None,
+ /// <summary>
+ /// The item was rejected because it was invalid for a jump list. E.g. the file path didn't exist.
+ /// </summary>
+ /// <remarks>
+ /// If the application is running on a system where jump lists are not available (like XP or Vista)
+ /// items will get rejected with this reason.
+ /// </remarks>
+ InvalidItem,
+ /// <summary>
+ /// The item was rejected because the program was not registered to handle the file extension.
+ /// </summary>
+ NoRegisteredHandler,
+ /// <summary>
+ /// The item was rejected because the user had explicitly removed it since the last time a JumpList was applied.
+ /// </summary>
+ RemovedByUser,
+ }
+
+ /// <summary>
+ /// EventArgs for JumpList.JumpItemsRejected event.
+ /// </summary>
+ public sealed class JumpItemsRejectedEventArgs : EventArgs
+ {
+ public JumpItemsRejectedEventArgs()
+ : this(null, null)
+ { }
+
+ public JumpItemsRejectedEventArgs(IList<JumpItem> rejectedItems, IList<JumpItemRejectionReason> reasons)
+ {
+ // If one of the collections is null then the other has to be, too.
+ if ((rejectedItems == null && reasons != null)
+ || (reasons == null && rejectedItems != null)
+ || (rejectedItems != null && reasons != null && rejectedItems.Count != reasons.Count))
+ {
+ throw new ArgumentException("The counts of rejected items doesn't match the count of reasons.");
+ }
+
+ // We don't want the contents of the list getting modified in the event handler,
+ // so use a read-only copy
+ if (rejectedItems != null)
+ {
+ RejectedItems = new List<JumpItem>(rejectedItems).AsReadOnly();
+ RejectionReasons = new List<JumpItemRejectionReason>(reasons).AsReadOnly();
+ }
+ else
+ {
+ RejectedItems = new List<JumpItem>().AsReadOnly();
+ RejectionReasons = new List<JumpItemRejectionReason>().AsReadOnly();
+ }
+ }
+
+ public IList<JumpItem> RejectedItems { get; private set; }
+ public IList<JumpItemRejectionReason> RejectionReasons { get; private set; }
+ }
+
+ /// <summary>
+ /// EventArgs for JumpList.JumpItemsRemovedByUser event.
+ /// </summary>
+ public sealed class JumpItemsRemovedEventArgs : EventArgs
+ {
+ public JumpItemsRemovedEventArgs()
+ : this(null)
+ { }
+
+ public JumpItemsRemovedEventArgs(IList<JumpItem> removedItems)
+ {
+ if (removedItems != null)
+ {
+ RemovedItems = new List<JumpItem>(removedItems).AsReadOnly();
+ }
+ else
+ {
+ RemovedItems = new List<JumpItem>().AsReadOnly();
+ }
+ }
+
+ public IList<JumpItem> RemovedItems { get; private set; }
+ }
+
+ /// <summary>
+ /// Manage the tasks and files that Shell associates with this application.
+ /// This allows modification of the Jump List UI in Windows 7 that appears on the Taskbar and Start Menu.
+ /// </summary>
+ [ContentProperty("JumpItems")]
+ public sealed class JumpList : ISupportInitialize
+ {
+ [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
+ static JumpList()
+ {
+ // Passing NULL for the HMODULE returns the running executable path.
+ _FullName = NativeMethods.GetModuleFileName(IntPtr.Zero);
+ }
+
+ /// <summary>
+ /// Add the item at the specified file path to the application's JumpList's recent items.
+ /// </summary>
+ /// <remarks>
+ /// This makes the item eligible for inclusion in the special Recent and Frequent categories.
+ /// </remarks>
+ public static void AddToRecentCategory(string itemPath)
+ {
+ Verify.FileExists(itemPath, "itemPath");
+ itemPath = Path.GetFullPath(itemPath);
+ NativeMethods.SHAddToRecentDocs(itemPath);
+ }
+
+ /// <summary>
+ /// Add the item to the application's JumpList's recent items.
+ /// </summary>
+ /// <remarks>
+ /// This makes the item eligible for inclusion in the special Recent and Frequent categories.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")]
+ public static void AddToRecentCategory(JumpPath jumpPath)
+ {
+ Verify.IsNotNull(jumpPath, "jumpPath");
+ AddToRecentCategory(jumpPath.Path);
+ }
+
+ /// <summary>
+ /// Add the task at the specified file path to the application's JumpList's recent items.
+ /// </summary>
+ /// <remarks>
+ /// This makes the item eligible for inclusion in the special Recent and Frequent categories.
+ /// </remarks>
+ public static void AddToRecentCategory(JumpTask jumpTask)
+ {
+ Verify.IsNotNull(jumpTask, "jumpTask");
+
+ // SHAddToRecentDocs only allows IShellLinks in Windows 7 and later.
+ // Silently fail this if that's not the case.
+ // We don't give feedback on success here, so this is okay.
+ if (Utility.IsOSWindows7OrNewer)
+ {
+ IShellLinkW shellLink = CreateLinkFromJumpTask(jumpTask, false);
+ try
+ {
+ if (shellLink != null)
+ {
+ NativeMethods.SHAddToRecentDocs(shellLink);
+ }
+ }
+ finally
+ {
+ Utility.SafeRelease(ref shellLink);
+ }
+ }
+ }
+
+ private class _RejectedJumpItemPair
+ {
+ public JumpItem JumpItem { get; set; }
+ public JumpItemRejectionReason Reason { get; set; }
+ }
+
+ private class _ShellObjectPair
+ {
+ // JumpPath/JumpTask
+ public JumpItem JumpItem { get; set; }
+ // IShellItem/IShellLink
+ public object ShellObject { get; set; }
+
+ /// <summary>
+ /// Releases all native references in a list of _ShellObjectPairs.
+ /// </summary>
+ /// <param name="list">The list from which to release the resources.</param>
+ public static void ReleaseShellObjects(List<_ShellObjectPair> list)
+ {
+ if (list != null)
+ {
+ foreach (_ShellObjectPair shellMap in list)
+ {
+ object o = shellMap.ShellObject;
+ shellMap.ShellObject = null;
+ Utility.SafeRelease(ref o);
+ }
+ }
+ }
+ }
+
+ #region Attached Property Methods
+
+ /// <summary>
+ /// Set the JumpList attached property on an Application.
+ /// </summary>
+ public static void SetJumpList(Application application, JumpList value)
+ {
+ Verify.IsNotNull(application, "application");
+
+ lock (s_lock)
+ {
+ // If this was associated with a different application, remove the association.
+ JumpList oldValue;
+ if (s_applicationMap.TryGetValue(application, out oldValue) && oldValue != null)
+ {
+ oldValue._application = null;
+ }
+
+ // Associate the jumplist with the application so we can retrieve it later.
+ s_applicationMap[application] = value;
+
+ if (value != null)
+ {
+ value._application = application;
+ }
+ }
+
+ if (value != null)
+ {
+ // Changes will only get applied if the list isn't in an ISupportInitialize block.
+ value.ApplyFromApplication();
+ }
+ }
+
+ /// <summary>
+ /// Get the JumpList attached property for an Application.
+ /// </summary>
+ public static JumpList GetJumpList(Application application)
+ {
+ Verify.IsNotNull(application, "application");
+
+ JumpList value;
+ s_applicationMap.TryGetValue(application, out value);
+ return value;
+ }
+
+ #endregion
+
+ // static lock to ensure integrity when modifying instances as attached properties on Application.
+ private static readonly object s_lock = new object();
+ private static readonly Dictionary<Application, JumpList> s_applicationMap = new Dictionary<Application, JumpList>();
+
+ private Application _application;
+
+ // Used to enforce the ISupportInitialize contract. It's not required to BeginInit to use this class,
+ // but it is useful for XAML scenarios so we can apply the changes when both EndInit has been called
+ // and the Application attached property has been set.
+ private bool? _initializing;
+
+ // The internal list of JumpItems in this JumpList
+ private List<JumpItem> _jumpItems;
+
+ public JumpList()
+ : this(null, false, false)
+ {
+ // Restore the ability to use ISupportInitialize.
+ _initializing = null;
+ }
+
+ public JumpList(IEnumerable<JumpItem> items, bool showFrequent, bool showRecent)
+ {
+ if (items != null)
+ {
+ _jumpItems = new List<JumpItem>(items);
+ }
+ else
+ {
+ _jumpItems = new List<JumpItem>();
+ }
+
+ ShowFrequentCategory = showFrequent;
+ ShowRecentCategory = showRecent;
+
+ // Using this constructor precludes using ISupportInitialize.
+ _initializing = false;
+ }
+
+ /// <summary>
+ /// Whether to show the special "Frequent" category.
+ /// </summary>
+ /// <remarks>
+ /// This category is managed by the Shell and keeps track of items that are frequently accessed by this program.
+ /// Applications can request that specific items are included here by calling JumpList.AddToRecentCategory.
+ /// Because of duplication, applications generally should not have both ShowRecentCategory and ShowFrequentCategory set at the same time.
+ /// </remarks>
+ public bool ShowFrequentCategory { get; set; }
+
+ /// <summary>
+ /// Whether to show the special "Recent" category.
+ /// </summary>
+ /// <remarks>
+ /// This category is managed by the Shell and keeps track of items that have been recently accessed by this program.
+ /// Applications can request that specific items are included here by calling JumpList.AddToRecentCategory
+ /// Because of duplication, applications generally should not have both ShowRecentCategory and ShowFrequentCategory set at the same time.
+ /// </remarks>
+ public bool ShowRecentCategory { get; set; }
+
+ /// <summary>
+ /// The list of JumpItems to be in the JumpList. After a call to Apply this list will contain only those items that were successfully added.
+ /// </summary>
+ /// <remarks>
+ /// This object is not guaranteed to retain its identity after a call to Apply or other implicit setting of the JumpList.
+ /// It should be requeried at such times.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
+ public List<JumpItem> JumpItems
+ {
+ get { return _jumpItems; }
+ }
+
+ private bool _IsUnmodified
+ {
+ get
+ {
+ return _initializing == null
+ && JumpItems.Count == 0
+ && !ShowRecentCategory
+ && !ShowFrequentCategory;
+ }
+ }
+
+ #region ISupportInitialize Members
+
+ /// <summary>
+ /// Prepare the JumpList for modification.
+ /// </summary>
+ /// <remarks>
+ /// This works in concert with the Application.JumpList attached property. The JumpList will automatically be applied
+ /// to the current application when attached and a corresponding call to EndInit is made.
+ /// Nested calls to BeginInit are not allowed.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "BeginInit")]
+ public void BeginInit()
+ {
+ if (!_IsUnmodified)
+ {
+ throw new InvalidOperationException("Calls to BeginInit cannot be nested.");
+ }
+
+ _initializing = true;
+ }
+
+ /// <summary>
+ /// Signal the end of initialization of this JumpList. If it is attached to the current Application, apply the contents of the jump list.
+ /// </summary>
+ /// <remarks>
+ /// Calls to EndInit must be paired with calls to BeginInit.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EndInit")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "BeginInit")]
+ public void EndInit()
+ {
+ if (_initializing != true)
+ {
+ throw new NotSupportedException("Can't call EndInit without first calling BeginInit.");
+ }
+
+ _initializing = false;
+
+ // EndInit only implicitly applies the list if the current Application has been set as an attached property.
+ ApplyFromApplication();
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Get the AppUserModelId for the running process.
+ /// </summary>
+ /// <remarks>
+ /// This is a Shell property that currently is only used as part of a heuristic
+ /// for what taskbar item an HWND should be associated with, e.g. you can put
+ /// windows from multiple processes into the same group, or you can prevent glomming
+ /// of HWNDs that would otherwise be shown together.
+ ///
+ /// Even though this property isn't exposed on the public WPF OM
+ /// we still want to make sure that the jump list gets associated with
+ /// the current running app even if the client has explicitly changed the id.
+ ///
+ /// It's straightforward to p/invoke to set these for the running application or
+ /// the HWND. Not so much for this object.
+ /// </remarks>
+ private static string _RuntimeId
+ {
+ get
+ {
+ string appId;
+ HRESULT hr = NativeMethods.GetCurrentProcessExplicitAppUserModelID(out appId);
+ if (hr == HRESULT.E_FAIL)
+ {
+ // This is how Shell signals that the app id hasn't been set.
+ hr = HRESULT.S_OK;
+ appId = null;
+ }
+ hr.ThrowIfFailed();
+ return appId;
+ }
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "JumpList")]
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EndInit")]
+ public void Apply()
+ {
+ if (_initializing == true)
+ {
+ throw new InvalidOperationException("The JumpList can't be applied until EndInit has been called.");
+ }
+
+ // After this attempting to use ISupportInitialize is invalid.
+ _initializing = false;
+
+ _ApplyList();
+ }
+
+ private void ApplyFromApplication()
+ {
+ // If we're here and the caller has modified the JumpList without using ISupportInitialize
+ // we still want to apply the changes.
+ if (_initializing != true && !_IsUnmodified)
+ {
+ _initializing = false;
+ }
+
+ if (_application == Application.Current && _initializing == false)
+ {
+ // If we're applying due to being an attached property, then don't apply
+ // unless we're really on the current application and wait until EndInit
+ // has been called, or the list has otherwise been modified.
+ _ApplyList();
+ }
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "JumpLists")]
+ [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Standard.Verify.IsApartmentState(System.Threading.ApartmentState,System.String)")]
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private void _ApplyList()
+ {
+ Debug.Assert(_initializing == false);
+ Verify.IsApartmentState(ApartmentState.STA, "JumpLists can only be effected on STA threads.");
+
+ // We don't want to force applications to conditionally check this before constructing a JumpList,
+ // but if we're not on 7 then this isn't going to work. Fail fast.
+ if (!Utility.IsOSWindows7OrNewer)
+ {
+ RejectEverything();
+ return;
+ }
+
+ List<JumpItem> successList;
+ List<_RejectedJumpItemPair> rejectedList;
+ List<_ShellObjectPair> removedList;
+
+ try
+ {
+ _BuildShellLists(out successList, out rejectedList, out removedList);
+ }
+ catch (Exception)
+ {
+ // It's not okay to throw an exception here. If Shell is rejecting the JumpList for some reason
+ // we don't want to be responsible for the app throwing an exception in its startup path.
+ // For common use patterns there isn't really any user code on the stack, so there isn't
+ // an opportunity to catch this in the app.
+ // We can instead handle this.
+ Assert.Fail();
+ RejectEverything();
+ return;
+ }
+
+ _jumpItems = successList;
+
+ // Raise the events for rejected and removed.
+ EventHandler<JumpItemsRejectedEventArgs> rejectedHandler = JumpItemsRejected;
+ EventHandler<JumpItemsRemovedEventArgs> removedHandler = JumpItemsRemovedByUser;
+
+ if (rejectedList.Count > 0 && rejectedHandler != null)
+ {
+ var items = new List<JumpItem>(rejectedList.Count);
+ var reasons = new List<JumpItemRejectionReason>(rejectedList.Count);
+
+ foreach (_RejectedJumpItemPair rejectionPair in rejectedList)
+ {
+ items.Add(rejectionPair.JumpItem);
+ reasons.Add(rejectionPair.Reason);
+ }
+
+ rejectedHandler(this, new JumpItemsRejectedEventArgs(items, reasons));
+ }
+
+ if (removedList.Count > 0 && removedHandler != null)
+ {
+ var items = new List<JumpItem>(removedList.Count);
+ foreach (_ShellObjectPair shellMap in removedList)
+ {
+ // It's possible that not every shell object could be converted to a JumpItem.
+ if (shellMap.JumpItem != null)
+ {
+ items.Add(shellMap.JumpItem);
+ }
+ }
+
+ if (items.Count > 0)
+ {
+ removedHandler(this, new JumpItemsRemovedEventArgs(items));
+ }
+ }
+ }
+
+ private void _BuildShellLists(out List<JumpItem> successList, out List<_RejectedJumpItemPair> rejectedList, out List<_ShellObjectPair> removedList)
+ {
+ // Declare these outside the try block so we can cleanup native resources in the _ShellObjectPairs.
+ List<List<_ShellObjectPair>> categories = null;
+ removedList = null;
+ ICustomDestinationList destinationList = CLSID.CoCreateInstance<ICustomDestinationList>(CLSID.DestinationList);
+ try
+ {
+ // Even though we're not exposing Shell's AppModelId concept, we'll still respect it
+ // since it's easy to clients to p/invoke to set it for Application and Window, but not for JumpLists
+ string appId = _RuntimeId;
+ if (!string.IsNullOrEmpty(appId))
+ {
+ destinationList.SetAppID(appId);
+ }
+
+ // The number ot items visible on a jump list is a user setting. Shell doesn't reject items based on overflow.
+ // We don't bother checking against it because the app can query the setting and manage overflow based on it
+ // if they really care. We'll happily add too many items with the hope that if the user changes the setting
+ // items will be recovered from the overflow.
+ uint slotsVisible;
+ Guid removedIid = new Guid(IID.ObjectArray);
+ var objectsRemoved = (IObjectArray)destinationList.BeginList(out slotsVisible, ref removedIid);
+
+ // Keep track of the items that were previously removed by the user.
+ // We don't want to pend any items that are contained in this list.
+ // It's possible that this contains null JumpItems when we were unable to do the conversion.
+ removedList = GenerateJumpItems(objectsRemoved);
+
+ // Keep track of the items that have been successfully pended.
+ // IMPORTANT: Ensure at the end of this that if the list is applied again that it would
+ // result in items being added to the JumpList in the same order.
+ // This doesn't mean that they'll be ordered the same as how the user added them
+ // (e.g. categories will be coalesced), but the categories should appear in the same order.
+ // Since when we call AddCategory we're doing it in reverse order, AddCategory augments
+ // the items in the list in reverse as well. At the end the final list is reversed.
+ successList = new List<JumpItem>(JumpItems.Count);
+
+ // Keep track of the items that we couldn't pend, and why.
+ rejectedList = new List<_RejectedJumpItemPair>(JumpItems.Count);
+
+ // Need to group the JumpItems based on their categories.
+ // The special "Tasks" category doesn't actually have a name and should be first so it's unconditionally added.
+ categories = new List<List<_ShellObjectPair>>() { new List<_ShellObjectPair>() };
+
+ // This is not thread-safe.
+ // We're traversing the original list so we're vulnerable to another thread modifying it during the enumeration.
+ foreach (var jumpItem in JumpItems)
+ {
+ if (jumpItem == null)
+ {
+ // App added a null jump item? Just go through the normal failure mechanisms.
+ rejectedList.Add(new _RejectedJumpItemPair { JumpItem = jumpItem, Reason = JumpItemRejectionReason.InvalidItem });
+ continue;
+ }
+
+ object shellObject = null;
+ try
+ {
+ shellObject = GetShellObjectForJumpItem(jumpItem);
+ // If for some reason we couldn't create the item add it to the rejected list.
+ if (shellObject == null)
+ {
+ rejectedList.Add(new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.InvalidItem, JumpItem = jumpItem });
+ continue;
+ }
+
+ // Don't add this item if it's in the list of items previously removed by the user.
+ if (ListContainsShellObject(removedList, shellObject))
+ {
+ rejectedList.Add(new _RejectedJumpItemPair { Reason = JumpItemRejectionReason.RemovedByUser, JumpItem = jumpItem });
+ continue;
+ }
+
+ var shellMap = new _ShellObjectPair { JumpItem = jumpItem, ShellObject = shellObject };
+ if (string.IsNullOrEmpty(jumpItem.CustomCategory))
+ {
+ // No custom category, so add to the Tasks list.
+ categories[0].Add(shellMap);
+ }
+ else
+ {
+ // Find the appropriate category and add to that list.
+ // If it doesn't exist, add a new category for it.
+ bool categoryExists = false;
+ foreach (var list in categories)
+ {
+ // The first item in the category list can be used to check the name.
+ if (list.Count > 0 && list[0].JumpItem.CustomCategory == jumpItem.CustomCategory)
+ {
+ list.Add(shellMap);
+ categoryExists = true;
+ break;
+ }
+ }
+ if (!categoryExists)
+ {
+ categories.Add(new List<_ShellObjectPair>() { shellMap });
+ }
+ }
+
+ // Shell interface is now owned by the category list.
+ shellObject = null;
+ }
+ finally
+ {
+ Utility.SafeRelease(ref shellObject);
+ }
+ }
+
+ // Jump List categories get added top-down, except for "Tasks" which is special and always at the bottom.
+ // We want the Recent/Frequent to always be at the top so they get added first.
+ // Logically the categories are added bottom-up, but their contents are top-down,
+ // so we reverse the order we add the categories to the destinationList.
+ // To preserve the item ordering AddCategory also adds items in reverse.
+ // We need to reverse the final list when everything is done.
+ categories.Reverse();
+
+ if (ShowFrequentCategory)
+ {
+ destinationList.AppendKnownCategory(KDC.FREQUENT);
+ }
+
+ if (ShowRecentCategory)
+ {
+ destinationList.AppendKnownCategory(KDC.RECENT);
+ }
+
+ // Now that all the JumpItems are grouped add them to the custom destinations list.
+ foreach (List<_ShellObjectPair> categoryList in categories)
+ {
+ if (categoryList.Count > 0)
+ {
+ string categoryHeader = categoryList[0].JumpItem.CustomCategory;
+ AddCategory(destinationList, categoryHeader, categoryList, successList, rejectedList);
+ }
+ }
+
+ destinationList.CommitList();
+
+ // Swap the current list with what we were able to successfully place into the JumpList.
+ // Reverse it first to ensure that the items are in a repeatable order.
+ successList.Reverse();
+ }
+ finally
+ {
+ // Deterministically release native resources.
+
+ Utility.SafeRelease(ref destinationList);
+
+ if (categories != null)
+ {
+ foreach (List<_ShellObjectPair> list in categories)
+ {
+ _ShellObjectPair.ReleaseShellObjects(list);
+ }
+ }
+
+ // Note that this only clears the ShellObjects, not the JumpItems.
+ // We still need the JumpItems out of this list for the JumpItemsRemovedByUser event.
+ _ShellObjectPair.ReleaseShellObjects(removedList);
+ }
+ }
+
+ private static bool ListContainsShellObject(List<_ShellObjectPair> removedList, object shellObject)
+ {
+ Debug.Assert(removedList != null);
+ Debug.Assert(shellObject != null);
+
+ if (removedList.Count == 0)
+ {
+ return false;
+ }
+
+ // Casts in .Net don't AddRef. Don't need to release these.
+ var shellItem = shellObject as IShellItem;
+ if (shellItem != null)
+ {
+ foreach (var shellMap in removedList)
+ {
+ var removedItem = shellMap.ShellObject as IShellItem;
+ if (removedItem != null)
+ {
+ if (0 == shellItem.Compare(removedItem, SICHINT.CANONICAL | SICHINT.TEST_FILESYSPATH_IF_NOT_EQUAL))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ var shellLink = shellObject as IShellLinkW;
+ if (shellLink != null)
+ {
+ foreach (var shellMap in removedList)
+ {
+ var removedLink = shellMap.ShellObject as IShellLinkW;
+ if (removedLink != null)
+ {
+ // There's no intrinsic comparison function for ShellLinks.
+ // Talking to the Shell guys, the way they compare these is to catenate a string with
+ // a normalized version of the app path and the unmodified args.
+ // If the two strings ordinally compare, they're the same.
+ string removedLinkString = ShellLinkToString(removedLink);
+ string linkString = ShellLinkToString(shellLink);
+
+ if (removedLinkString == linkString)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // Unlikely. It's not a supported shell interface?
+ return false;
+ }
+
+ /// <summary>
+ /// </summary>
+ /// <remarks>
+ /// This returns a native COM object that should be deterministically released by the caller, when possible.
+ /// </remarks>
+ private static object GetShellObjectForJumpItem(JumpItem jumpItem)
+ {
+ var jumpPath = jumpItem as JumpPath;
+ var jumpTask = jumpItem as JumpTask;
+
+ // Either of these create functions could return null if the item is invalid but they shouldn't throw.
+ if (jumpPath != null)
+ {
+ return CreateItemFromJumpPath(jumpPath);
+ }
+ else if (jumpTask != null)
+ {
+ return CreateLinkFromJumpTask(jumpTask, true);
+ }
+
+ // Unsupported type?
+ Debug.Assert(false);
+ return null;
+ }
+
+ private static List<_ShellObjectPair> GenerateJumpItems(IObjectArray shellObjects)
+ {
+ Debug.Assert(shellObjects != null);
+
+ var retList = new List<_ShellObjectPair>();
+
+ Guid unknownIid = new Guid(IID.Unknown);
+ uint count = shellObjects.GetCount();
+ for (uint i = 0; i < count; ++i)
+ {
+ // This is potentially a heterogenous list, so get as an IUnknown and QI afterwards.
+ object unk = shellObjects.GetAt(i, ref unknownIid);
+ JumpItem item = null;
+ try
+ {
+ item = GetJumpItemForShellObject(unk);
+ }
+ catch (Exception e)
+ {
+ if (e is NullReferenceException || e is System.Runtime.InteropServices.SEHException)
+ {
+ throw;
+ }
+ // If we failed the conversion we still want to keep the shell interface for comparision.
+ // Just leave the JumpItem property as null.
+ }
+ retList.Add(new _ShellObjectPair { ShellObject = unk, JumpItem = item });
+ }
+
+ return retList;
+ }
+
+ private static void AddCategory(ICustomDestinationList cdl, string category, List<_ShellObjectPair> jumpItems, List<JumpItem> successList, List<_RejectedJumpItemPair> rejectionList)
+ {
+ AddCategory(cdl, category, jumpItems, successList, rejectionList, true);
+ }
+
+ private static void AddCategory(ICustomDestinationList cdl, string category, List<_ShellObjectPair> jumpItems, List<JumpItem> successList, List<_RejectedJumpItemPair> rejectionList, bool isHeterogenous)
+ {
+ Debug.Assert(jumpItems.Count != 0);
+ Debug.Assert(cdl != null);
+
+ HRESULT hr;
+ var shellObjectCollection = (IObjectCollection)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid(CLSID.EnumerableObjectCollection)));
+
+ foreach (var itemMap in jumpItems)
+ {
+ shellObjectCollection.AddObject(itemMap.ShellObject);
+ }
+
+ if (string.IsNullOrEmpty(category))
+ {
+ hr = cdl.AddUserTasks((IObjectArray)shellObjectCollection);
+ }
+ else
+ {
+ hr = cdl.AppendCategory(category, (IObjectArray)shellObjectCollection);
+ }
+
+ if (hr.Succeeded)
+ {
+ // Woot! Add these items to the list.
+ // Do it in reverse order so Apply has the items in the correct order.
+ for (int i = jumpItems.Count; --i >= 0;)
+ {
+ successList.Add(jumpItems[i].JumpItem);
+ }
+ }
+ else
+ {
+ // If the list contained items that could not be added because this object isn't a handler
+ // then drop all ShellItems and retry without them.
+ if (isHeterogenous && hr == HRESULT.DESTS_E_NO_MATCHING_ASSOC_HANDLER)
+ {
+ Utility.SafeRelease(ref shellObjectCollection);
+ var linksOnlyList = new List<_ShellObjectPair>();
+ foreach (var itemMap in jumpItems)
+ {
+ if (itemMap.JumpItem is JumpPath)
+ {
+ rejectionList.Add(new _RejectedJumpItemPair { JumpItem = itemMap.JumpItem, Reason = JumpItemRejectionReason.NoRegisteredHandler });
+ }
+ else
+ {
+ linksOnlyList.Add(itemMap);
+ }
+ }
+ if (linksOnlyList.Count > 0)
+ {
+ // There's not a reason I know of that we should reject a list of only links...
+ Debug.Assert(jumpItems.Count != linksOnlyList.Count);
+ AddCategory(cdl, category, linksOnlyList, successList, rejectionList, false);
+ }
+ }
+ else
+ {
+ Debug.Assert(HRESULT.DESTS_E_NO_MATCHING_ASSOC_HANDLER != hr);
+ // If we failed for some other reason, just reject everything.
+ foreach (var item in jumpItems)
+ {
+ rejectionList.Add(new _RejectedJumpItemPair { JumpItem = item.JumpItem, Reason = JumpItemRejectionReason.InvalidItem });
+ }
+ }
+ }
+ }
+
+ private static readonly string _FullName;
+
+ #region Converter methods
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private static IShellLinkW CreateLinkFromJumpTask(JumpTask jumpTask, bool allowSeparators)
+ {
+ Debug.Assert(jumpTask != null);
+
+ // Title is generally required. If it's missing we need to treat this like a separator.
+ // Everything else can still appear on separator elements,
+ // but separators can only exist in the Tasks category.
+ if (string.IsNullOrEmpty(jumpTask.Title))
+ {
+ if (!allowSeparators || !string.IsNullOrEmpty(jumpTask.CustomCategory))
+ {
+ // Just treat this situation as an InvalidItem.
+ return null;
+ }
+ }
+
+ var link = (IShellLinkW)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid(CLSID.ShellLink)));
+ try
+ {
+ string appPath = _FullName;
+ if (!string.IsNullOrEmpty(jumpTask.ApplicationPath))
+ {
+ appPath = jumpTask.ApplicationPath;
+ }
+
+ link.SetPath(appPath);
+
+ // This is optional. Don't set it if the app hasn't explicitly requested it.
+ if (!string.IsNullOrEmpty(jumpTask.WorkingDirectory))
+ {
+ // Don't verify this. It's possible that the directory doesn't exist now, but it will later.
+ // Shell handles this fine when we try to set an improperly formatted path.
+ link.SetWorkingDirectory(jumpTask.WorkingDirectory);
+ }
+
+ if (!string.IsNullOrEmpty(jumpTask.Arguments))
+ {
+ link.SetArguments(jumpTask.Arguments);
+ }
+
+ // -1 is a sentinel value indicating not to use the icon.
+ if (jumpTask.IconResourceIndex != -1)
+ {
+ string resourcePath = _FullName;
+ if (!string.IsNullOrEmpty(jumpTask.IconResourcePath))
+ {
+ // Shell bug (Windows 7 595770): IShellLink doesn't correctly limit icon location path to MAX_PATH.
+ // It's really too bad we have to enforce this here. When the shortcut gets
+ // serialized it streams the full string. On deserialization it only retrieves
+ // MAX_PATH for this field leaving junk behind for subsequent gets, leading to data corruption.
+ // Because we don't want to allow the app to do create something that we know may
+ // be corrupt we have to enforce this ourselves. If Shell fixes this later then
+ // we need to remove this check to let them handle this as they see fit.
+ // If they fix it by supporting longer paths, then we're artificially constraining this value...
+ if (jumpTask.IconResourcePath.Length >= Win32Value.MAX_PATH)
+ {
+ // we could throw the exception here, but we're already globally catching everything.
+ return null;
+ }
+ resourcePath = jumpTask.IconResourcePath;
+ }
+ link.SetIconLocation(resourcePath, jumpTask.IconResourceIndex);
+ }
+
+ if (!string.IsNullOrEmpty(jumpTask.Description))
+ {
+ link.SetDescription(jumpTask.Description);
+ }
+
+ IPropertyStore propStore = (IPropertyStore)link;
+ using (var pv = new PROPVARIANT())
+ {
+ PKEY pkey = default(PKEY);
+
+ if (!string.IsNullOrEmpty(jumpTask.Title))
+ {
+ pv.SetValue(jumpTask.Title);
+ pkey = PKEY.Title;
+ }
+ else
+ {
+ pv.SetValue(true);
+ pkey = PKEY.AppUserModel_IsDestListSeparator;
+ }
+
+ propStore.SetValue(ref pkey, pv);
+ }
+
+ propStore.Commit();
+
+ IShellLinkW retLink = link;
+ link = null;
+ return retLink;
+ }
+ catch (Exception)
+ {
+ // IShellLinkW::Set* methods tend to return E_FAIL when trying to set invalid data.
+ // The create methods don't explicitly check for these kinds of errors.
+
+ // If we aren't able to create the item for any reason just return null to indicate an invalid item.
+ return null;
+ }
+ finally
+ {
+ Utility.SafeRelease(ref link);
+ }
+ }
+
+ private static IShellItem2 GetShellItemForPath(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ // Internal function. Should have verified this before calling if we cared.
+ return null;
+ }
+
+ Guid iidShellItem2 = new Guid(IID.ShellItem2);
+ object unk;
+ HRESULT hr = NativeMethods.SHCreateItemFromParsingName(path, null, ref iidShellItem2, out unk);
+
+ // Silently absorb errors such as ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND.
+ // Let others pass through
+ if (hr == (HRESULT)Win32Error.ERROR_FILE_NOT_FOUND || hr == (HRESULT)Win32Error.ERROR_PATH_NOT_FOUND)
+ {
+ hr = HRESULT.S_OK;
+ unk = null;
+ }
+
+ hr.ThrowIfFailed();
+
+ return (IShellItem2)unk;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
+ private static IShellItem2 CreateItemFromJumpPath(JumpPath jumpPath)
+ {
+ Debug.Assert(jumpPath != null);
+
+ try
+ {
+ // This will return null if the path doesn't exist.
+ return GetShellItemForPath(Path.GetFullPath(jumpPath.Path));
+ }
+ catch (Exception)
+ {
+ // Don't propagate exceptions here. If we couldn't create the item, it's just invalid.
+ }
+
+ return null;
+ }
+
+ private static JumpItem GetJumpItemForShellObject(object shellObject)
+ {
+ var shellItem = shellObject as IShellItem2;
+ var shellLink = shellObject as IShellLinkW;
+
+ if (shellItem != null)
+ {
+ JumpPath path = new JumpPath
+ {
+ Path = shellItem.GetDisplayName(SIGDN.DESKTOPABSOLUTEPARSING),
+ };
+ return path;
+ }
+
+ if (shellLink != null)
+ {
+ var pathBuilder = new StringBuilder((int)Win32Value.MAX_PATH);
+ shellLink.GetPath(pathBuilder, pathBuilder.Capacity, null, SLGP.RAWPATH);
+ var argsBuilder = new StringBuilder((int)Win32Value.INFOTIPSIZE);
+ shellLink.GetArguments(argsBuilder, argsBuilder.Capacity);
+ var descBuilder = new StringBuilder((int)Win32Value.INFOTIPSIZE);
+ shellLink.GetDescription(descBuilder, descBuilder.Capacity);
+ var iconBuilder = new StringBuilder((int)Win32Value.MAX_PATH);
+ int iconIndex;
+ shellLink.GetIconLocation(iconBuilder, iconBuilder.Capacity, out iconIndex);
+ var dirBuilder = new StringBuilder((int)Win32Value.MAX_PATH);
+ shellLink.GetWorkingDirectory(dirBuilder, dirBuilder.Capacity);
+
+ JumpTask task = new JumpTask
+ {
+ // Set ApplicationPath and IconResources, even if they're from the current application.
+ // This means that equivalent JumpTasks won't necessarily compare property-for-property.
+ ApplicationPath = pathBuilder.ToString(),
+ Arguments = argsBuilder.ToString(),
+ Description = descBuilder.ToString(),
+ IconResourceIndex = iconIndex,
+ IconResourcePath = iconBuilder.ToString(),
+ WorkingDirectory = dirBuilder.ToString(),
+ };
+
+ using (PROPVARIANT pv = new PROPVARIANT())
+ {
+ var propStore = (IPropertyStore)shellLink;
+ PKEY pkeyTitle = PKEY.Title;
+
+ propStore.GetValue(ref pkeyTitle, pv);
+
+ // PKEY_Title should be an LPWSTR if it's not empty.
+ task.Title = pv.GetValue() ?? "";
+ }
+
+ return task;
+ }
+
+ // Unsupported type?
+ Debug.Assert(false);
+ return null;
+ }
+
+ /// <summary>
+ /// Generate a unique string for the ShellLink that can be used for equality checks.
+ /// </summary>
+ private static string ShellLinkToString(IShellLinkW shellLink)
+ {
+ var pathBuilder = new StringBuilder((int)Win32Value.MAX_PATH);
+ shellLink.GetPath(pathBuilder, pathBuilder.Capacity, null, SLGP.RAWPATH);
+
+ string title = null;
+ // Need to use the property store to get the title for the link.
+ using (PROPVARIANT pv = new PROPVARIANT())
+ {
+ var propStore = (IPropertyStore)shellLink;
+ PKEY pkeyTitle = PKEY.Title;
+
+ propStore.GetValue(ref pkeyTitle, pv);
+
+ // PKEY_Title should be an LPWSTR if it's not empty.
+ title = pv.GetValue() ?? "";
+ }
+
+ var argsBuilder = new StringBuilder((int)Win32Value.INFOTIPSIZE);
+ shellLink.GetArguments(argsBuilder, argsBuilder.Capacity);
+
+ // Path and title should be case insensitive.
+ // Shell treats arguments as case sensitive because apps can handle those differently.
+ return pathBuilder.ToString().ToUpperInvariant() + title.ToUpperInvariant() + argsBuilder.ToString();
+ }
+
+ #endregion
+
+ private void RejectEverything()
+ {
+ EventHandler<JumpItemsRejectedEventArgs> handler = JumpItemsRejected;
+ if (handler == null)
+ {
+ _jumpItems.Clear();
+ return;
+ }
+
+ if (_jumpItems.Count > 0)
+ {
+ var reasons = new List<JumpItemRejectionReason>(JumpItems.Count);
+ for (int i = 0; i < JumpItems.Count; ++i)
+ {
+ reasons.Add(JumpItemRejectionReason.InvalidItem);
+ }
+ // We're rejecting everything,
+ // so create an event args with the original list and then clear it.
+ var args = new JumpItemsRejectedEventArgs(JumpItems, reasons);
+ _jumpItems.Clear();
+
+ handler(this, args);
+ }
+ }
+
+ public event EventHandler<JumpItemsRejectedEventArgs> JumpItemsRejected;
+
+ public event EventHandler<JumpItemsRemovedEventArgs> JumpItemsRemovedByUser;
+ }
+}
View
14 GitUI/Microsoft.Windows.Shell/JumpPath.cs
@@ -0,0 +1,14 @@
+/**************************************************************************\
+ Copyright Microsoft Corporation. All Rights Reserved.
+\**************************************************************************/
+
+namespace Microsoft.Windows.Shell
+{
+ public class JumpPath : JumpItem
+ {
+ public JumpPath()
+ {}
+
+ public string Path { get; set; }
+ }
+}
View
26 GitUI/Microsoft.Windows.Shell/JumpTask.cs
@@ -0,0 +1,26 @@
+/**************************************************************************\
+ Copyright Microsoft Corporation. All Rights Reserved.
+\**************************************************************************/
+
+namespace Microsoft.Windows.Shell
+{
+ public class JumpTask : JumpItem
+ {
+ public JumpTask()
+ {}
+
+ public string Title { get; set; }
+
+ public string Description { get; set; }
+
+ public string ApplicationPath { get; set; }
+
+ public string Arguments { get; set; }
+
+ public string WorkingDirectory { get; set; }
+
+ public string IconResourcePath { get; set; }
+
+ public int IconResourceIndex { get; set; }
+ }
+}
View
161 GitUI/Microsoft.Windows.Shell/Microsoft.Windows.Shell.csproj
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{55D5297C-F1DF-4B76-A3C1-D82CC294EEBB}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Microsoft.Windows.Shell</RootNamespace>
+ <AssemblyName>Microsoft.Windows.Shell</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <SccProjectName>
+ </SccProjectName>
+ <SccLocalPath>
+ </SccLocalPath>
+ <SccAuxPath>
+ </SccAuxPath>
+ <SccProvider>
+ </SccProvider>
+ <SignAssembly>false</SignAssembly>
+ <AssemblyOriginatorKeyFile>
+ </AssemblyOriginatorKeyFile>
+ <DelaySign>true</DelaySign>
+ <FileUpgradeFlags>
+ </FileUpgradeFlags>
+ <OldToolsVersion>3.5</OldToolsVersion>
+ <UpgradeBackupLocation />
+ <PublishUrl>publish\</PublishUrl>
+ <Install>true</Install>
+ <InstallFrom>Disk</InstallFrom>
+ <UpdateEnabled>false</UpdateEnabled>
+ <UpdateMode>Foreground</UpdateMode>
+ <UpdateInterval>7</UpdateInterval>
+ <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+ <UpdatePeriodically>false</UpdatePeriodically>
+ <UpdateRequired>false</UpdateRequired>
+ <MapFileExtensions>true</MapFileExtensions>
+ <ApplicationRevision>0</ApplicationRevision>
+ <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+ <IsWebBootstrapper>false</IsWebBootstrapper>
+ <UseApplicationTrust>false</UseApplicationTrust>
+ <BootstrapperEnabled>true</BootstrapperEnabled>
+ <TargetFrameworkProfile />
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\Resources\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <RunCodeAnalysis>true</RunCodeAnalysis>
+ <CodeAnalysisRules>
+ </CodeAnalysisRules>
+ <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>..\Resources\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <CodeAnalysisRules>
+ </CodeAnalysisRules>
+ <RunCodeAnalysis>true</RunCodeAnalysis>
+ <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\x86\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <DebugType>full</DebugType>
+ <PlatformTarget>x86</PlatformTarget>
+ <CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression>
+ <CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.cs</CodeAnalysisModuleSuppressionsFile>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+ <OutputPath>bin\x86\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <Optimize>true</Optimize>
+ <DebugType>pdbonly</DebugType>
+ <PlatformTarget>x86</PlatformTarget>
+ <CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression>
+ <CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.cs</CodeAnalysisModuleSuppressionsFile>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="PresentationCore">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="PresentationFramework">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Xaml" />
+ <Reference Include="WindowsBase">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="JumpItem.cs" />
+ <Compile Include="JumpList.cs" />
+ <Compile Include="JumpPath.cs" />
+ <Compile Include="JumpTask.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Standard\ComGuids.cs" />
+ <Compile Include="Standard\Debug.cs" />
+ <Compile Include="Standard\DoubleUtil.cs" />
+ <Compile Include="Standard\DpiHelper.cs" />
+ <Compile Include="Standard\ErrorCodes.cs" />
+ <Compile Include="Standard\MessageWindow.cs" />
+ <Compile Include="Standard\NativeMethods.cs" />
+ <Compile Include="Standard\ShellProvider.cs" />
+ <Compile Include="Standard\StreamHelper.cs" />
+ <Compile Include="Standard\Utilities.cs" />
+ <Compile Include="Standard\Verify.cs" />
+ <Compile Include="SystemCommands.cs" />
+ <Compile Include="SystemParameters2.cs" />
+ <Compile Include="TaskbarItemInfo.cs" />
+ <Compile Include="ThumbButtonInfo.cs" />
+ <Compile Include="ThumbButtonInfoCollection.cs" />
+ <Compile Include="WindowChrome.cs" />
+ <Compile Include="WindowChromeWorker.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="app.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+ <Visible>False</Visible>
+ <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+ <Install>false</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+ <Visible>False</Visible>
+ <ProductName>.NET Framework 3.5 SP1</ProductName>
+ <Install>true</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
+ <Visible>False</Visible>
+ <ProductName>Windows Installer 3.1</ProductName>
+ <Install>true</Install>
+ </BootstrapperPackage>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
View
31 GitUI/Microsoft.Windows.Shell/Properties/AssemblyInfo.cs
@@ -0,0 +1,31 @@
+/**************************************************************************\
+ Copyright Microsoft Corporation. All Rights Reserved.
+\**************************************************************************/
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Windows.Markup;
+
+[assembly: AssemblyTitle("Microsoft.Windows.Shell")]
+[assembly: AssemblyDescription("Windows 7 shell integration library for WPF")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("Microsoft.Windows.Shell")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2010")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+[assembly: Guid("573618e1-4f3f-4395-a3bf-ffebfb342917")]
+[assembly: CLSCompliant(true)]
+
+[assembly: AssemblyVersion("3.0.1.0")]
+[assembly: AssemblyFileVersion("3.0.1.0")]
+
+[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation/shell", "Microsoft.Windows.Shell")]
+
+// Code analysis suppressions:
+[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Standard", Justification="Internal-only namespace")]
+[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification="Assembly has strong name when published")]
View
95 GitUI/Microsoft.Windows.Shell/Standard/ComGuids.cs
@@ -0,0 +1,95 @@
+/**************************************************************************\
+ Copyright Microsoft Corporation. All Rights Reserved.
+\**************************************************************************/
+
+namespace Standard
+{
+ internal static partial class IID
+ {
+ /// <summary>IID_IEnumIDList</summary>
+ public const string EnumIdList = "000214F2-0000-0000-C000-000000000046";
+ /// <summary>IID_IEnumObjects</summary>
+ public const string EnumObjects = "2c1c7e2e-2d0e-4059-831e-1e6f82335c2e";
+ /// <summary>IID_IHTMLDocument2</summary>
+ public const string HtmlDocument2 = "332C4425-26CB-11D0-B483-00C04FD90119";
+ /// <summary>IID_IModalWindow</summary>
+ public const string ModalWindow = "b4db1657-70d7-485e-8e3e-6fcb5a5c1802";
+ /// <summary>IID_IObjectArray</summary>
+ public const string ObjectArray = "92CA9DCD-5622-4bba-A805-5E9F541BD8C9";
+ /// <summary>IID_IObjectCollection</summary>
+ public const string ObjectCollection = "5632b1a4-e38a-400a-928a-d4cd63230295";
+ /// <summary>IID_IPropertyNotifySink</summary>
+ public const string PropertyNotifySink = "9BFBBC02-EFF1-101A-84ED-00AA00341D07";
+ /// <summary>IID_IPropertyStore</summary>
+ public const string PropertyStore = "886d8eeb-8cf2-4446-8d02-cdba1dbdcf99";
+ /// <summary>IID_IServiceProvider</summary>
+ public const string ServiceProvider = "6d5140c1-7436-11ce-8034-00aa006009fa";
+ /// <summary>IID_IShellFolder</summary>
+ public const string ShellFolder = "000214E6-0000-0000-C000-000000000046";
+ /// <summary>IID_IShellLink</summary>
+ public const string ShellLink = "000214F9-0000-0000-C000-000000000046";
+ /// <summary>IID_IShellItem</summary>
+ public const string ShellItem = "43826d1e-e718-42ee-bc55-a1e261c37bfe";
+ /// <summary>IID_IShellItem2</summary>
+ public const string ShellItem2 = "7e9fb0d3-919f-4307-ab2e-9b1860310c93";
+ /// <summary>IID_IShellItemArray</summary>
+ public const string ShellItemArray = "B63EA76D-1F85-456F-A19C-48159EFA858B";
+ /// <summary>IID_ITaskbarList</summary>
+ public const string TaskbarList = "56FDF342-FD6D-11d0-958A-006097C9A090";
+ /// <summary>IID_ITaskbarList2</summary>
+ public const string TaskbarList2 = "602D4995-B13A-429b-A66E-1935E44F4317";
+ /// <summary>IID_IUnknown</summary>
+ public const string Unknown = "00000000-0000-0000-C000-000000000046";
+
+ #region Win7 IIDs
+
+ /// <summary>IID_IApplicationDestinations</summary>
+ public const string ApplicationDestinations = "12337d35-94c6-48a0-bce7-6a9c69d4d600";
+ /// <summary>IID_IApplicationDocumentLists</summary>
+ public const string ApplicationDocumentLists = "3c594f9f-9f30-47a1-979a-c9e83d3d0a06";
+ /// <summary>IID_ICustomDestinationList</summary>
+ public const string CustomDestinationList = "6332debf-87b5-4670-90c0-5e57b408a49e";
+ /// <summary>IID_IObjectWithAppUserModelID</summary>
+ public const string ObjectWithAppUserModelId = "36db0196-9665-46d1-9ba7-d3709eecf9ed";
+ /// <summary>IID_IObjectWithProgID</summary>
+ public const string ObjectWithProgId = "71e806fb-8dee-46fc-bf8c-7748a8a1ae13";
+ /// <summary>IID_ITaskbarList3</summary>
+ public const string TaskbarList3 = "ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf";
+ /// <summary>IID_ITaskbarList4</summary>
+ public const string TaskbarList4 = "c43dc798-95d1-4bea-9030-bb99e2983a1a";
+
+ #endregion
+ }
+
+ internal static partial class CLSID
+ {
+ public static T CoCreateInstance<T>(string clsid)
+ {
+ return (T)System.Activator.CreateInstance(System.Type.GetTypeFromCLSID(new System.Guid(clsid)));
+ }
+
+ /// <summary>CLSID_TaskbarList</summary>
+ /// <remarks>IID_ITaskbarList</remarks>
+ public const string TaskbarList = "56FDF344-FD6D-11d0-958A-006097C9A090";
+ /// <summary>CLSID_EnumerableObjectCollection</summary>
+ /// <remarks>IID_IEnumObjects.</remarks>
+ public const string EnumerableObjectCollection = "2d3468c1-36a7-43b6-ac24-d3f02fd9607a";
+ /// <summary>CLSID_ShellLink</summary>
+ /// <remarks>IID_IShellLink</remarks>
+ public const string ShellLink = "00021401-0000-0000-C000-000000000046";
+
+ #region Win7 CLSIDs
+
+ /// <summary>CLSID_DestinationList</summary>
+ /// <remarks>IID_ICustomDestinationList</remarks>
+ public const string DestinationList = "77f10cf0-3db5-4966-b520-b7c54fd35ed6";
+ /// <summary>CLSID_ApplicationDestinations</summary>
+ /// <remarks>IID_IApplicationDestinations</remarks>
+ public const string ApplicationDestinations = "86c14003-4d6b-4ef3-a7b4-0506663b2e68";
+ /// <summary>CLSID_ApplicationDocumentLists</summary>
+ /// <remarks>IID_IApplicationDocumentLists</remarks>
+ public const string ApplicationDocumentLists = "86bec222-30f2-47e0-9f25-60d11cd75c28";
+
+ #endregion
+ }
+}
View
373 GitUI/Microsoft.Windows.Shell/Standard/Debug.cs
@@ -0,0 +1,373 @@
+/**************************************************************************\
+ Copyright Microsoft Corporation. All Rights Reserved.
+\**************************************************************************/
+
+// Conditional to use more aggressive fail-fast behaviors when debugging.
+#define DEV_DEBUG
+
+// This file contains general utilities to aid in development.
+// It is distinct from unit test Assert classes.
+// Classes here generally shouldn't be exposed publicly since
+// they're not particular to any library functionality.
+// Because the classes here are internal, it's likely this file
+// might be included in multiple assemblies.
+namespace Standard
+{
+ using System;
+ using System.Diagnostics;
+ using System.Threading;
+
+ /// <summary>A static class for verifying assumptions.</summary>
+ internal static class Assert
+ {
+ private static void _Break()
+ {
+#if DEV_DEBUG
+ Debugger.Break();
+#else
+ Debug.Assert(false);
+#endif
+ }
+
+ /// <summary>A function signature for Assert.Evaluate.</summary>
+ public delegate void EvaluateFunction();
+
+ /// <summary>A function signature for Assert.Implies.</summary>
+ /// <returns>Returns the truth of a predicate.</returns>
+ public delegate bool ImplicationFunction();
+
+ /// <summary>
+ /// Executes the specified argument.
+ /// </summary>
+ /// <param name="argument">The function to execute.</param>
+ [Conditional("DEBUG")]
+ public static void Evaluate(EvaluateFunction argument)
+ {
+ IsNotNull(argument);
+ argument();
+ }
+
+ /// <summary>Obsolete: Use Standard.Assert.AreEqual instead of Assert.Equals</summary>
+ /// <typeparam name="T">The generic type to compare for equality.</typeparam>
+ /// <param name="expected">The first generic type data to compare. This is is the expected value.</param>
+ /// <param name="actual">The second generic type data to compare. This is the actual value.</param>
+ [
+ Obsolete("Use Assert.AreEqual instead of Assert.Equals", false),
+ Conditional("DEBUG")
+ ]
+ public static void Equals<T>(T expected, T actual)
+ {
+ AreEqual(expected, actual);
+ }
+
+ /// <summary>
+ /// Verifies that two generic type data are equal. The assertion fails if they are not.
+ /// </summary>
+ /// <typeparam name="T">The generic type to compare for equality.</typeparam>
+ /// <param name="expected">The first generic type data to compare. This is is the expected value.</param>
+ /// <param name="actual">The second generic type data to compare. This is the actual value.</param>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void AreEqual<T>(T expected, T actual)
+ {
+ if (null == expected)
+ {
+ // Two nulls are considered equal, regardless of type semantics.
+ if (null != actual && !actual.Equals(expected))
+ {
+ _Break();
+ }
+ }
+ else if (!expected.Equals(actual))
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Verifies that two generic type data are not equal. The assertion fails if they are.
+ /// </summary>
+ /// <typeparam name="T">The generic type to compare for inequality.</typeparam>
+ /// <param name="notExpected">The first generic type data to compare. This is is the value that's not expected.</param>
+ /// <param name="actual">The second generic type data to compare. This is the actual value.</param>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void AreNotEqual<T>(T notExpected, T actual)
+ {
+ if (null == notExpected)
+ {
+ // Two nulls are considered equal, regardless of type semantics.
+ if (null == actual || actual.Equals(notExpected))
+ {
+ _Break();
+ }
+ }
+ else if (notExpected.Equals(actual))
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Verifies that if the specified condition is true, then so is the result.
+ /// The assertion fails if the condition is true but the result is false.
+ /// </summary>
+ /// <param name="condition">if set to <c>true</c> [condition].</param>
+ /// <param name="result">
+ /// A second Boolean statement. If the first was true then so must this be.
+ /// If the first statement was false then the value of this is ignored.
+ /// </param>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void Implies(bool condition, bool result)
+ {
+ if (condition && !result)
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Lazy evaluation overload. Verifies that if a condition is true, then so is a secondary value.
+ /// </summary>
+ /// <param name="condition">The conditional value.</param>
+ /// <param name="result">A function to be evaluated for truth if the condition argument is true.</param>
+ /// <remarks>
+ /// This overload only evaluates the result if the first condition is true.
+ /// </remarks>
+ [Conditional("DEBUG")]
+ public static void Implies(bool condition, ImplicationFunction result)
+ {
+ if (condition && !result())
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Verifies that a string has content. I.e. it is not null and it is not empty.
+ /// </summary>
+ /// <param name="value">The string to verify.</param>
+ [Conditional("DEBUG")]
+ public static void IsNeitherNullNorEmpty(string value)
+ {
+ IsFalse(string.IsNullOrEmpty(value));
+ }
+
+ /// <summary>
+ /// Verifies that a string has content. I.e. it is not null and it is not purely whitespace.
+ /// </summary>
+ /// <param name="value">The string to verify.</param>
+ [Conditional("DEBUG")]
+ public static void IsNeitherNullNorWhitespace(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ _Break();
+ }
+
+ if (value.Trim().Length == 0)
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Verifies the specified value is not null. The assertion fails if it is.
+ /// </summary>
+ /// <typeparam name="T">The generic reference type.</typeparam>
+ /// <param name="value">The value to check for nullness.</param>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void IsNotNull<T>(T value) where T : class
+ {
+ if (null == value)
+ {
+ _Break();
+ }
+ }
+
+ [Conditional("DEBUG")]
+ public static void IsDefault<T>(T value) where T : struct
+ {
+ if (!value.Equals(default(T)))
+ {
+ Assert.Fail();
+ }
+ }
+
+ [Conditional("DEBUG")]
+ public static void IsNotDefault<T>(T value) where T : struct
+ {
+ if (value.Equals(default(T)))
+ {
+ Assert.Fail();
+ }
+ }
+
+ /// <summary>
+ /// Verifies that the specified condition is false. The assertion fails if it is true.
+ /// </summary>
+ /// <param name="condition">The expression that should be <c>false</c>.</param>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void IsFalse(bool condition)
+ {
+ if (condition)
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Verifies that the specified condition is false. The assertion fails if it is true.
+ /// </summary>
+ /// <param name="condition">The expression that should be <c>false</c>.</param>
+ /// <param name="message">The message to display if the condition is <c>true</c>.</param>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void IsFalse(bool condition, string message)
+ {
+ if (condition)
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Verifies that the specified condition is true. The assertion fails if it is not.
+ /// </summary>
+ /// <param name="condition">A condition that is expected to be <c>true</c>.</param>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void IsTrue(bool condition)
+ {
+ if (!condition)
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Verifies that the specified condition is true. The assertion fails if it is not.
+ /// </summary>
+ /// <param name="condition">A condition that is expected to be <c>true</c>.</param>
+ /// <param name="message">The message to write in case the condition is <c>false</c>.</param>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void IsTrue(bool condition, string message)
+ {
+ if (!condition)
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// This line should never be executed. The assertion always fails.
+ /// </summary>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void Fail()
+ {
+ _Break();
+ }
+
+ /// <summary>
+ /// This line should never be executed. The assertion always fails.
+ /// </summary>
+ /// <param name="message">The message to display if this function is executed.</param>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void Fail(string message)
+ {
+ _Break();
+ }
+
+ /// <summary>
+ /// Verifies that the specified object is null. The assertion fails if it is not.
+ /// </summary>
+ /// <param name="item">The item to verify is null.</param>
+ [Conditional("DEBUG")]
+ public static void IsNull<T>(T item) where T : class
+ {
+ if (null != item)
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Verifies that the specified value is within the expected range. The assertion fails if it isn't.
+ /// </summary>
+ /// <param name="lowerBoundInclusive">The lower bound inclusive value.</param>
+ /// <param name="value">The value to verify.</param>
+ /// <param name="upperBoundInclusive">The upper bound inclusive value.</param>
+ [Conditional("DEBUG")]
+ public static void BoundedDoubleInc(double lowerBoundInclusive, double value, double upperBoundInclusive)
+ {
+ if (value < lowerBoundInclusive || value > upperBoundInclusive)
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Verifies that the specified value is within the expected range. The assertion fails if it isn't.
+ /// </summary>
+ /// <param name="lowerBoundInclusive">The lower bound inclusive value.</param>
+ /// <param name="value">The value to verify.</param>
+ /// <param name="upperBoundExclusive">The upper bound exclusive value.</param>
+ [Conditional("DEBUG")]
+ public static void BoundedInteger(int lowerBoundInclusive, int value, int upperBoundExclusive)
+ {
+ if (value < lowerBoundInclusive || value >= upperBoundExclusive)
+ {
+ _Break();
+ }
+ }
+
+ /// <summary>
+ /// Verify the current thread's apartment state is what's expected. The assertion fails if it isn't
+ /// </summary>
+ /// <param name="expectedState">
+ /// The expected apartment state for the current thread.
+ /// </param>
+ /// <remarks>This breaks into the debugger in the case of a failed assertion.</remarks>
+ [Conditional("DEBUG")]
+ public static void IsApartmentState(ApartmentState expectedState)
+ {
+ if (Thread.CurrentThread.GetApartmentState() != expectedState)
+ {
+ _Break();
+ }
+ }
+
+ [Conditional("DEBUG")]
+ public static void NullableIsNotNull<T>(T? value) where T : struct
+ {
+ if (null == value)
+ {
+ _Break();
+ }
+ }
+
+ [Conditional("DEBUG")]
+ public static void NullableIsNull<T>(T? value) where T : struct
+ {
+ if (null != value)
+ {
+ _Break();
+ }
+ }
+
+ [Conditional("DEBUG")]
+ public static void IsNotOnMainThread()
+ {
+ if (System.Windows.Application.Current.Dispatcher.CheckAccess())
+ {
+ _Break();
+ }
+ }
+ }
+}
View
132 GitUI/Microsoft.Windows.Shell/Standard/DoubleUtil.cs
@@ -0,0 +1,132 @@
+
+namespace Standard
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+
+ /// <summary>
+ /// DoubleUtil uses fixed eps to provide fuzzy comparison functionality for doubles.
+ /// Note that FP noise is a big problem and using any of these compare
+ /// methods is not a complete solution, but rather the way to reduce
+ /// the probability of repeating unnecessary work.
+ /// </summary>
+ internal static class DoubleUtilities
+ {
+ /// <summary>
+ /// Epsilon - more or less random, more or less small number.
+ /// </summary>
+ private const double Epsilon = 0.00000153;
+
+ /// <summary>
+ /// AreClose returns whether or not two doubles are "close". That is, whether or
+ /// not they are within epsilon of each other.
+ /// There are plenty of ways for this to return false even for numbers which
+ /// are theoretically identical, so no code calling this should fail to work if this
+ /// returns false.
+ /// </summary>
+ /// <param name="value1">The first double to compare.</param>
+ /// <param name="value2">The second double to compare.</param>
+ /// <returns>The result of the AreClose comparision.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ public static bool AreClose(double value1, double value2)
+ {
+ if (value1 == value2)
+ {
+ return true;
+ }
+
+ double delta = value1 - value2;
+ return (delta < Epsilon) && (delta > -Epsilon);
+ }
+
+ /// <summary>
+ /// LessThan returns whether or not the first double is less than the second double.
+ /// That is, whether or not the first is strictly less than *and* not within epsilon of
+ /// the other number.
+ /// There are plenty of ways for this to return false even for numbers which
+ /// are theoretically identical, so no code calling this should fail to work if this
+ /// returns false.
+ /// </summary>
+ /// <param name="value1">The first double to compare.</param>
+ /// <param name="value2">The second double to compare.</param>
+ /// <returns>The result of the LessThan comparision.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ public static bool LessThan(double value1, double value2)
+ {
+ return (value1 < value2) && !AreClose(value1, value2);
+ }
+
+ /// <summary>
+ /// GreaterThan returns whether or not the first double is greater than the second double.
+ /// That is, whether or not the first is strictly greater than *and* not within epsilon of
+ /// the other number.
+ /// There are plenty of ways for this to return false even for numbers which
+ /// are theoretically identical, so no code calling this should fail to work if this
+ /// returns false.
+ /// </summary>
+ /// <param name="value1">The first double to compare.</param>
+ /// <param name="value2">The second double to compare.</param>
+ /// <returns>The result of the GreaterThan comparision.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ public static bool GreaterThan(double value1, double value2)
+ {
+ return (value1 > value2) && !AreClose(value1, value2);
+ }
+
+ /// <summary>
+ /// LessThanOrClose returns whether or not the first double is less than or close to
+ /// the second double. That is, whether or not the first is strictly less than or within
+ /// epsilon of the other number.
+ /// There are plenty of ways for this to return false even for numbers which
+ /// are theoretically identical, so no code calling this should fail to work if this
+ /// returns false.
+ /// </summary>
+ /// <param name="value1">The first double to compare.</param>
+ /// <param name="value2">The second double to compare.</param>
+ /// <returns>The result of the LessThanOrClose comparision.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ public static bool LessThanOrClose(double value1, double value2)
+ {
+ return (value1 < value2) || AreClose(value1, value2);
+ }
+
+ /// <summary>
+ /// GreaterThanOrClose returns whether or not the first double is greater than or close to
+ /// the second double. That is, whether or not the first is strictly greater than or within
+ /// epsilon of the other number.
+ /// There are plenty of ways for this to return false even for numbers which
+ /// are theoretically identical, so no code calling this should fail to work if this
+ /// returns false.
+ /// </summary>
+ /// <param name="value1">The first double to compare.</param>
+ /// <param name="value2">The second double to compare.</param>
+ /// <returns>The result of the GreaterThanOrClose comparision.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ public static bool GreaterThanOrClose(double value1, double value2)
+ {
+ return (value1 > value2) || AreClose(value1, value2);
+ }
+
+ /// <summary>
+ /// Test to see if a double is a finite number (is not NaN or Infinity).
+ /// </summary>
+ /// <param name='value'>The value to test.</param>
+ /// <returns>Whether or not the value is a finite number.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ public static bool IsFinite(double value)
+ {
+ return !double.IsNaN(value) && !double.IsInfinity(value);
+ }