diff --git a/QuickLook.Plugin.LastResort/Extensions.cs b/QuickLook.Plugin.LastResort/Extensions.cs new file mode 100644 index 0000000..3d76182 --- /dev/null +++ b/QuickLook.Plugin.LastResort/Extensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace QuickLook.Plugin.LastResort +{ + public static class Extensions + { + [DllImport("gdi32")] + private static extern int DeleteObject(IntPtr o); + + public static BitmapSource ToBitmapSource(this Bitmap source) + { + var ip = source.GetHbitmap(); + BitmapSource bs = null; + try + { + var data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), + ImageLockMode.ReadOnly, source.PixelFormat); + + bs = BitmapSource.Create(source.Width, source.Height, source.HorizontalResolution, + source.VerticalResolution, PixelFormats.Bgra32, null, + data.Scan0, data.Stride * source.Height, data.Stride); + + source.UnlockBits(data); + + bs.Freeze(); + } + finally + { + DeleteObject(ip); + } + + return bs; + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.LastResort/IconHelper.cs b/QuickLook.Plugin.LastResort/IconHelper.cs new file mode 100644 index 0000000..184e374 --- /dev/null +++ b/QuickLook.Plugin.LastResort/IconHelper.cs @@ -0,0 +1,255 @@ +using System; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +// ReSharper disable InconsistentNaming + +namespace QuickLook.Plugin.LastResort +{ + public class IconHelper + { + public enum IconSizeEnum + { + SmallIcon16 = SHGFI_SMALLICON, + MediumIcon32 = SHGFI_LARGEICON, + LargeIcon48 = SHIL_EXTRALARGE, + ExtraLargeIcon = SHIL_JUMBO + } + + private const int SHGFI_SMALLICON = 0x1; + private const int SHGFI_LARGEICON = 0x0; + private const int SHIL_JUMBO = 0x4; + private const int SHIL_EXTRALARGE = 0x2; + private const int WM_CLOSE = 0x0010; + + [DllImport("user32")] + private static extern + IntPtr SendMessage( + IntPtr handle, + int Msg, + IntPtr wParam, + IntPtr lParam); + + + [DllImport("shell32.dll")] + private static extern int SHGetImageList( + int iImageList, + ref Guid riid, + out IImageList ppv); + + [DllImport("Shell32.dll")] + private static extern int SHGetFileInfo( + string pszPath, + int dwFileAttributes, + ref SHFILEINFO psfi, + int cbFileInfo, + uint uFlags); + + [DllImport("user32")] + private static extern int DestroyIcon( + IntPtr hIcon); + + public static Bitmap GetBitmapFromFolderPath( + string filepath, IconSizeEnum iconsize) + { + var hIcon = GetIconHandleFromFolderPath(filepath, iconsize); + return getBitmapFromIconHandle(hIcon); + } + + public static Bitmap GetBitmapFromFilePath( + string filepath, IconSizeEnum iconsize) + { + var hIcon = GetIconHandleFromFilePath(filepath, iconsize); + return getBitmapFromIconHandle(hIcon); + } + + public static Bitmap GetBitmapFromPath( + string filepath, IconSizeEnum iconsize) + { + var hIcon = IntPtr.Zero; + if (Directory.Exists(filepath)) + { + hIcon = GetIconHandleFromFolderPath(filepath, iconsize); + } + else + { + if (File.Exists(filepath)) + hIcon = GetIconHandleFromFilePath(filepath, iconsize); + } + return getBitmapFromIconHandle(hIcon); + } + + private static Bitmap getBitmapFromIconHandle(IntPtr hIcon) + { + if (hIcon == IntPtr.Zero) throw new FileNotFoundException(); + var myIcon = Icon.FromHandle(hIcon); + var bitmap = myIcon.ToBitmap(); + myIcon.Dispose(); + DestroyIcon(hIcon); + SendMessage(hIcon, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + return bitmap; + } + + private static IntPtr GetIconHandleFromFilePath(string filepath, IconSizeEnum iconsize) + { + var shinfo = new SHFILEINFO(); + const uint SHGFI_SYSICONINDEX = 0x4000; + const int FILE_ATTRIBUTE_NORMAL = 0x80; + var flags = SHGFI_SYSICONINDEX; + return getIconHandleFromFilePathWithFlags(filepath, iconsize, ref shinfo, FILE_ATTRIBUTE_NORMAL, flags); + } + + private static IntPtr GetIconHandleFromFolderPath(string folderpath, IconSizeEnum iconsize) + { + var shinfo = new SHFILEINFO(); + + const uint SHGFI_ICON = 0x000000100; + const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; + const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + var flags = SHGFI_ICON | SHGFI_USEFILEATTRIBUTES; + return getIconHandleFromFilePathWithFlags(folderpath, iconsize, ref shinfo, FILE_ATTRIBUTE_DIRECTORY, + flags); + } + + private static IntPtr getIconHandleFromFilePathWithFlags( + string filepath, IconSizeEnum iconsize, + ref SHFILEINFO shinfo, int fileAttributeFlag, uint flags) + { + const int ILD_TRANSPARENT = 1; + var retval = SHGetFileInfo(filepath, fileAttributeFlag, ref shinfo, Marshal.SizeOf(shinfo), flags); + if (retval == 0) throw new FileNotFoundException(); + var iconIndex = shinfo.iIcon; + var iImageListGuid = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950"); + IImageList iml; + var hres = SHGetImageList((int) iconsize, ref iImageListGuid, out iml); + var hIcon = IntPtr.Zero; + hres = iml.GetIcon(iconIndex, ILD_TRANSPARENT, ref hIcon); + return hIcon; + } + } + + [ComImport] + [Guid("46EB5926-582E-4017-9FDF-E8998DAA0950")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IImageList + { + [PreserveSig] + int Add( + IntPtr hbmImage, + IntPtr hbmMask, + ref int pi); + + [PreserveSig] + int ReplaceIcon( + int i, + IntPtr hicon, + ref int pi); + + [PreserveSig] + int SetOverlayImage( + int iImage, + int iOverlay); + + [PreserveSig] + int Replace( + int i, + IntPtr hbmImage, + IntPtr hbmMask); + + [PreserveSig] + int AddMasked( + IntPtr hbmImage, + int crMask, + ref int pi); + + [PreserveSig] + int Draw( + ref IMAGELISTDRAWPARAMS pimldp); + + [PreserveSig] + int Remove( + int i); + + [PreserveSig] + int GetIcon( + int i, + int flags, + ref IntPtr picon); + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGEINFO + { + public IntPtr hbmImage; + public IntPtr hbmMask; + public int Unused1; + public int Unused2; + public RECT rcImage; + } + + public struct IMAGELISTDRAWPARAMS + { + public int cbSize; + public IntPtr himl; + public int i; + public IntPtr hdcDst; + public int x; + public int y; + public int cx; + public int cy; + public int xBitmap; + public int yBitmap; + public int rgbBk; + public int rgbFg; + public int fStyle; + public int dwRop; + public int fState; + public int Frame; + public int crEffect; + } + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + private int X; + private int Y; + + public POINT(int x, int y) + { + X = x; + Y = y; + } + + public POINT(Point pt) : this(pt.X, pt.Y) + { + } + + public static implicit operator Point(POINT p) + { + return new Point(p.X, p.Y); + } + + public static implicit operator POINT(Point p) + { + return new POINT(p.X, p.Y); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + + public struct SHFILEINFO + { + public IntPtr hIcon; + public int iIcon; + public uint dwAttributes; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 254)] public string szDisplayName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szTypeName; + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.LastResort/InfoPanel.xaml b/QuickLook.Plugin.LastResort/InfoPanel.xaml new file mode 100644 index 0000000..18e246f --- /dev/null +++ b/QuickLook.Plugin.LastResort/InfoPanel.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/QuickLook.Plugin.LastResort/InfoPanel.xaml.cs b/QuickLook.Plugin.LastResort/InfoPanel.xaml.cs new file mode 100644 index 0000000..35fdc2d --- /dev/null +++ b/QuickLook.Plugin.LastResort/InfoPanel.xaml.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace QuickLook.Plugin.LastResort +{ + /// + /// Interaction logic for InfoPanel.xaml + /// + public partial class InfoPanel : UserControl + { + public InfoPanel() + { + InitializeComponent(); + + var i = 0; + } + } +} diff --git a/QuickLook.Plugin.LastResort/Plugin.cs b/QuickLook.Plugin.LastResort/Plugin.cs new file mode 100644 index 0000000..2d5a6a7 --- /dev/null +++ b/QuickLook.Plugin.LastResort/Plugin.cs @@ -0,0 +1,33 @@ +using System.Drawing; +using System.Windows; +using Size = System.Windows.Size; + +namespace QuickLook.Plugin.LastResort +{ + public class Plugin : IViewer + { + private InfoPanel ip; + public int Priority => -9999; + + public bool CanHandle(string sample) + { + return true; + } + + public void View(string path, ViewContentContainer container) + { + var s = IconHelper.GetBitmapFromPath(path, IconHelper.IconSizeEnum.ExtraLargeIcon).ToBitmapSource(); + + ip = new InfoPanel(); + ip.image.Source = s; + + container.SetContent(ip); + container.PreferedSize = new Size {Width = ip.Width, Height = ip.Height}; + } + + public void Close() + { + //ip.Dispose(); + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.LastResort/Properties/AssemblyInfo.cs b/QuickLook.Plugin.LastResort/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a1c3232 --- /dev/null +++ b/QuickLook.Plugin.LastResort/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("QuickLook.Plugin.LastResort")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("QuickLook.Plugin.LastResort")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b9a5a4f6-813e-40ce-ad32-dc5c1356215d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/QuickLook.Plugin.LastResort/QuickLook.Plugin.LastResort.csproj b/QuickLook.Plugin.LastResort/QuickLook.Plugin.LastResort.csproj new file mode 100644 index 0000000..8090154 --- /dev/null +++ b/QuickLook.Plugin.LastResort/QuickLook.Plugin.LastResort.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {B9A5A4F6-813E-40CE-AD32-DC5C1356215D} + Library + Properties + QuickLook.Plugin.LastResort + QuickLook.Plugin.LastResort + v4.5.2 + 512 + + + true + full + false + ..\Build\Debug\Plugins\ + DEBUG;TRACE + prompt + 4 + x86 + + + pdbonly + true + ..\Build\Release\Plugins\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + InfoPanel.xaml + + + + + + + + {8B4A9CE5-67B5-4A94-81CB-3771F688FDEB} + QuickLook + False + + + + + Designer + MSBuild:Compile + + + + \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/Class1.cs b/QuickLook.Plugin.PDFViewer/Class1.cs deleted file mode 100644 index 134efe6..0000000 --- a/QuickLook.Plugin.PDFViewer/Class1.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Linq; -using System.Text; -using System.Windows; -using System.Windows.Media; -using MoonPdfLib; -using QuickLook.ExtensionMethods; - -namespace QuickLook.Plugin.PDFViewer -{ - public class Class1 : IViewer - { - public PluginType Type => PluginType.ByExtension | PluginType.ByContent; - - public string[] SupportExtensions => new[] {".pdf"}; - - public bool CheckSupportByContent(byte[] sample) - { - return Encoding.ASCII.GetString(sample.Take(4).ToArray()) == "%PDF"; - } - - public void View(string path, ViewContentContainer container) - { - var pdfPanel = new MoonPdfPanel - { - ViewType = ViewType.SinglePage, - PageRowDisplay = PageRowDisplayType.ContinuousPageRows, - PageMargin = new Thickness(0, 2, 4, 2), - Background = new SolidColorBrush(Colors.LightGray) - }; - container.SetContent(pdfPanel); - - container.Dispatcher.Delay(100, o => pdfPanel.OpenFile(path)); - //container.Dispatcher.Delay(200, o => pdfPanel.ZoomToWidth()); - } - - public void Close() - { - } - } -} \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/DpiHelpers.cs b/QuickLook.Plugin.PDFViewer/DpiHelpers.cs new file mode 100644 index 0000000..8c7ade0 --- /dev/null +++ b/QuickLook.Plugin.PDFViewer/DpiHelpers.cs @@ -0,0 +1,46 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace QuickLook.Plugin.PDFViewer +{ + internal static class DpiHelper + { + public const float DEFAULT_DPI = 96; + + public static Dpi GetCurrentDpi() + { + Graphics g = Graphics.FromHwnd(IntPtr.Zero); + IntPtr desktop = g.GetHdc(); + + var dpi = new Dpi + { + HorizontalDpi = GetDeviceCaps(desktop, (int) DeviceCap.LOGPIXELSX), + VerticalDpi = GetDeviceCaps(desktop, (int) DeviceCap.LOGPIXELSY) + }; + + return dpi; + } + + [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)] + public static extern int GetDeviceCaps(IntPtr hDC, int nIndex); + + public enum DeviceCap + { + /// + /// Logical pixels inch in X + /// + LOGPIXELSX = 88, + /// + /// Logical pixels inch in Y + /// + LOGPIXELSY = 90 + } + } + + internal class Dpi + { + public float HorizontalDpi { get; set; } + public float VerticalDpi { get; set; } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/Extensions.cs b/QuickLook.Plugin.PDFViewer/Extensions.cs new file mode 100644 index 0000000..3c6e21b --- /dev/null +++ b/QuickLook.Plugin.PDFViewer/Extensions.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace QuickLook.Plugin.PDFViewer +{ + public static class Extensions + { + public static void ForEach(this IEnumerable enumeration, Action action) + { + foreach (var item in enumeration) + action(item); + } + + public static T GetDescendantByType(this Visual element) where T : class + { + if (element == null) + return default(T); + if (element.GetType() == typeof(T)) + return element as T; + + T foundElement = null; + (element as FrameworkElement)?.ApplyTemplate(); + + for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) + { + var visual = VisualTreeHelper.GetChild(element, i) as Visual; + foundElement = visual.GetDescendantByType(); + if (foundElement != null) + break; + } + return foundElement; + } + + [DllImport("gdi32")] + private static extern int DeleteObject(IntPtr o); + + public static BitmapSource ToBitmapSource(this Bitmap source) + { + var ip = source.GetHbitmap(); + BitmapSource bs = null; + try + { + var data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), + ImageLockMode.ReadOnly, source.PixelFormat); + + bs = BitmapSource.Create(source.Width, source.Height, source.HorizontalResolution, + source.VerticalResolution, PixelFormats.Bgr24, null, + data.Scan0, data.Stride * source.Height, data.Stride); + + source.UnlockBits(data); + + bs.Freeze(); + } + finally + { + DeleteObject(ip); + } + + return bs; + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/LibMuPdf.cs b/QuickLook.Plugin.PDFViewer/LibMuPdf.cs new file mode 100644 index 0000000..f3d084f --- /dev/null +++ b/QuickLook.Plugin.PDFViewer/LibMuPdf.cs @@ -0,0 +1,181 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; + +namespace QuickLook.Plugin.PDFViewer +{ + internal class LibMuPdf + { + public static Bitmap RenderPage(IntPtr context, IntPtr document, IntPtr page, double zoomFactor) + { + var pageBound = new Rectangle(); + + var ctm = new Matrix(); + var pix = IntPtr.Zero; + var dev = IntPtr.Zero; + + NativeMethods.BoundPage(document, page, ref pageBound); + + var currentDpi = DpiHelper.GetCurrentDpi(); + var zoomX = zoomFactor * (currentDpi.HorizontalDpi / DpiHelper.DEFAULT_DPI); + var zoomY = zoomFactor * (currentDpi.VerticalDpi / DpiHelper.DEFAULT_DPI); + + // gets the size of the page and multiplies it with zoom factors + var width = (int) (pageBound.Width * zoomX); + var height = (int) (pageBound.Height * zoomY); + + // sets the matrix as a scaling matrix (zoomX,0,0,zoomY,0,0) + ctm.A = (float)zoomX; + ctm.D = (float)zoomY; + + // creates a pixmap the same size as the width and height of the page + pix = NativeMethods.NewPixmap(context, NativeMethods.LookupDeviceColorSpace(context, "DeviceRGB"), width, + height); + // sets white color as the background color of the pixmap + NativeMethods.ClearPixmap(context, pix, 0xFF); + + // creates a drawing device + dev = NativeMethods.NewDrawDevice(context, pix); + // draws the page on the device created from the pixmap + NativeMethods.RunPage(document, page, dev, ref ctm, IntPtr.Zero); + + NativeMethods.FreeDevice(dev); // frees the resources consumed by the device + dev = IntPtr.Zero; + + // creates a colorful bitmap of the same size of the pixmap + var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb); + var imageData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, + bmp.PixelFormat); + unsafe + { + // converts the pixmap data to Bitmap data + var ptrSrc = (byte*) NativeMethods.GetSamples(context, pix); // gets the rendered data from the pixmap + var ptrDest = (byte*) imageData.Scan0; + for (var y = 0; y < height; y++) + { + var pl = ptrDest; + var sl = ptrSrc; + for (var x = 0; x < width; x++) + { + //Swap these here instead of in MuPDF because most pdf images will be rgb or cmyk. + //Since we are going through the pixels one by one anyway swap here to save a conversion from rgb to bgr. + pl[2] = sl[0]; //b-r + pl[1] = sl[1]; //g-g + pl[0] = sl[2]; //r-b + //sl[3] is the alpha channel, we will skip it here + pl += 3; + sl += 4; + } + ptrDest += imageData.Stride; + ptrSrc += width * 4; + } + } + bmp.UnlockBits(imageData); + NativeMethods.DropPixmap(context, pix); + + bmp.SetResolution(currentDpi.HorizontalDpi, currentDpi.VerticalDpi); + + return bmp; + } + + public struct Rectangle + { + public float Left, Top, Right, Bottom; + + public float Width => Right - Left; + public float Height => Bottom - Top; + } + + public struct Matrix + { + public float A, B, C, D, E, F; + + public Matrix(float a, float b, float c, float d, float e, float f) + { + A = a; + B = b; + C = c; + D = d; + E = e; + F = f; + } + } + + internal struct NativePage + { + public Matrix Ctm; + public Rectangle MediaBox; + public int Rotate; + } + + + internal class NativeMethods + { + private const uint FZ_STORE_DEFAULT = 256 << 20; + private const string DLL = "libmupdf.dll"; + // please modify the version number to match the FZ_VERSION definition in "fitz\version.h" file + private const string MuPDFVersion = "1.6"; + + [DllImport(DLL, EntryPoint = "fz_new_context_imp", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr NewContext(IntPtr alloc, IntPtr locks, uint max_store, string version); + + public static IntPtr NewContext() + { + return NewContext(IntPtr.Zero, IntPtr.Zero, FZ_STORE_DEFAULT, MuPDFVersion); + } + + [DllImport(DLL, EntryPoint = "fz_free_context", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr FreeContext(IntPtr ctx); + + [DllImport(DLL, EntryPoint = "fz_open_file_w", CharSet = CharSet.Unicode, + CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr OpenFile(IntPtr ctx, string fileName); + + [DllImport(DLL, EntryPoint = "pdf_open_document_with_stream", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr OpenDocumentStream(IntPtr ctx, IntPtr stm); + + [DllImport(DLL, EntryPoint = "fz_close", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CloseStream(IntPtr stm); + + [DllImport(DLL, EntryPoint = "pdf_close_document", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CloseDocument(IntPtr doc); + + [DllImport(DLL, EntryPoint = "pdf_count_pages", CallingConvention = CallingConvention.Cdecl)] + public static extern int CountPages(IntPtr doc); + + [DllImport(DLL, EntryPoint = "pdf_bound_page", CallingConvention = CallingConvention.Cdecl)] + public static extern void BoundPage(IntPtr doc, IntPtr page, ref Rectangle bound); + + [DllImport(DLL, EntryPoint = "fz_clear_pixmap_with_value", CallingConvention = CallingConvention.Cdecl)] + public static extern void ClearPixmap(IntPtr ctx, IntPtr pix, int byteValue); + + [DllImport(DLL, EntryPoint = "fz_lookup_device_colorspace", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr LookupDeviceColorSpace(IntPtr ctx, string colorspace); + + [DllImport(DLL, EntryPoint = "fz_free_device", CallingConvention = CallingConvention.Cdecl)] + public static extern void FreeDevice(IntPtr dev); + + [DllImport(DLL, EntryPoint = "pdf_free_page", CallingConvention = CallingConvention.Cdecl)] + public static extern void FreePage(IntPtr doc, IntPtr page); + + [DllImport(DLL, EntryPoint = "pdf_load_page", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr LoadPage(IntPtr doc, int pageNumber); + + [DllImport(DLL, EntryPoint = "fz_new_draw_device", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr NewDrawDevice(IntPtr ctx, IntPtr pix); + + [DllImport(DLL, EntryPoint = "fz_new_pixmap", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr NewPixmap(IntPtr ctx, IntPtr colorspace, int width, int height); + + [DllImport(DLL, EntryPoint = "pdf_run_page", CallingConvention = CallingConvention.Cdecl)] + public static extern void RunPage(IntPtr doc, IntPtr page, IntPtr dev, ref Matrix transform, IntPtr cookie); + + [DllImport(DLL, EntryPoint = "fz_drop_pixmap", CallingConvention = CallingConvention.Cdecl)] + public static extern void DropPixmap(IntPtr ctx, IntPtr pix); + + [DllImport(DLL, EntryPoint = "fz_pixmap_samples", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr GetSamples(IntPtr ctx, IntPtr pix); + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/MouseWheelMonitor.cs b/QuickLook.Plugin.PDFViewer/MouseWheelMonitor.cs new file mode 100644 index 0000000..f5be1f4 --- /dev/null +++ b/QuickLook.Plugin.PDFViewer/MouseWheelMonitor.cs @@ -0,0 +1,113 @@ +using System; +using System.Linq; +using System.Threading; +using System.Windows; +using System.Windows.Input; +using System.Windows.Threading; + +namespace QuickLook.Plugin.PDFViewer +{ + public sealed class PreviewMouseWheelMonitor : IDisposable + { + private readonly UIElement _canvas; + private readonly Dispatcher _dispatcher; + private readonly int _sensitivity; + + private bool _disposed; + private volatile bool _inactive; + private AutoResetEvent _resetMonitorEvent; + private volatile bool _stopped; + + public PreviewMouseWheelMonitor(UIElement canvas, int sensitivity) + { + _canvas = canvas; + _canvas.PreviewMouseWheel += (s, e) => RaisePreviewMouseWheel(e); + + _sensitivity = sensitivity; + _dispatcher = Dispatcher.CurrentDispatcher; + _resetMonitorEvent = new AutoResetEvent(false); + + _disposed = false; + _inactive = true; + _stopped = true; + + var monitor = new Thread(Monitor) {IsBackground = true}; + monitor.Start(); + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + DetachEventHandlers(); + if (_resetMonitorEvent != null) + { + _resetMonitorEvent.Close(); + _resetMonitorEvent = null; + } + } + } + + public event EventHandler PreviewMouseWheel; + public event EventHandler PreviewMouseWheelStarted; + public event EventHandler PreviewMouseWheelStopped; + + private void Monitor() + { + while (!_disposed) + { + if (_inactive) // if wheel is still inactive... + { + _resetMonitorEvent.WaitOne(_sensitivity / 10); // ...wait negligibly small quantity of time... + continue; // ...and check again + } + // otherwise, if wheel is active... + _inactive = true; // ...purposely change the state to inactive + _resetMonitorEvent.WaitOne(_sensitivity); // wait... + if (_inactive + ) // ...and after specified time check if the state is still not re-activated inside mouse wheel event + RaiseMouseWheelStopped(); + } + } + + private void RaisePreviewMouseWheel(MouseWheelEventArgs args) + { + if (_stopped) + RaiseMouseWheelStarted(); + + _inactive = false; + if (PreviewMouseWheel != null) + PreviewMouseWheel(_canvas, args); + } + + private void RaiseMouseWheelStarted() + { + _stopped = false; + if (PreviewMouseWheelStarted != null) + PreviewMouseWheelStarted(_canvas, new EventArgs()); + } + + private void RaiseMouseWheelStopped() + { + _stopped = true; + if (PreviewMouseWheelStopped != null) + _dispatcher.Invoke(() => PreviewMouseWheelStopped(_canvas, + new + EventArgs())); // invoked on cached dispatcher for convenience (because fired from non-UI thread) + } + + private void DetachEventHandlers() + { + if (PreviewMouseWheel != null) + foreach (var handler in PreviewMouseWheel.GetInvocationList().Cast>()) + PreviewMouseWheel -= handler; + if (PreviewMouseWheelStarted != null) + foreach (var handler in PreviewMouseWheelStarted.GetInvocationList().Cast>()) + PreviewMouseWheelStarted -= handler; + if (PreviewMouseWheelStopped != null) + foreach (var handler in PreviewMouseWheelStopped.GetInvocationList().Cast>()) + PreviewMouseWheelStopped -= handler; + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/PageIdToImageConverter.cs b/QuickLook.Plugin.PDFViewer/PageIdToImageConverter.cs new file mode 100644 index 0000000..8d4bbb2 --- /dev/null +++ b/QuickLook.Plugin.PDFViewer/PageIdToImageConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace QuickLook.Plugin.PDFViewer +{ + public sealed class PageIdToImageConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.Length < 2) + throw new Exception("PageIdToImageConverter"); + + var zoom = 0.5f; + if (parameter != null) + float.TryParse((string) parameter, out zoom); + + var handle = values[0] as PdfFile; + if (handle == null) return null; + + var pageId = (int) values[1]; + if (pageId < 0) return null; + + return handle.GetPage(pageId, zoom).ToBitmapSource(); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/PdfFile.cs b/QuickLook.Plugin.PDFViewer/PdfFile.cs new file mode 100644 index 0000000..77ee53e --- /dev/null +++ b/QuickLook.Plugin.PDFViewer/PdfFile.cs @@ -0,0 +1,73 @@ +using System; +using System.Drawing; +using Size = System.Windows.Size; + +namespace QuickLook.Plugin.PDFViewer +{ + public class PdfFile : IDisposable + { + private readonly IntPtr _ctx; + private readonly IntPtr _doc; + private readonly IntPtr _stm; + + public PdfFile(string path) + { + _ctx = LibMuPdf.NativeMethods.NewContext(); + _stm = LibMuPdf.NativeMethods.OpenFile(_ctx, path); + _doc = LibMuPdf.NativeMethods.OpenDocumentStream(_ctx, _stm); + + TotalPages = LibMuPdf.NativeMethods.CountPages(_doc); + } + + public int TotalPages { get; } + + public void Dispose() + { + LibMuPdf.NativeMethods.CloseDocument(_doc); + LibMuPdf.NativeMethods.CloseStream(_stm); + LibMuPdf.NativeMethods.FreeContext(_ctx); + } + + public bool IsLastPage(int pageId) + { + return pageId >= TotalPages; + } + + public Size GetPageSize(int pageId, double zoomFactor) + { + if (pageId < 0 || pageId >= TotalPages) + throw new OverflowException( + $"Page id {pageId} should greater or equal than 0 and less than total page count {TotalPages}."); + + var p = LibMuPdf.NativeMethods.LoadPage(_doc, pageId); + + var realSize = new LibMuPdf.Rectangle(); + LibMuPdf.NativeMethods.BoundPage(_doc, p, ref realSize); + + var size = new Size + { + Width = realSize.Right * zoomFactor, + Height = realSize.Bottom * zoomFactor + }; + + LibMuPdf.NativeMethods.FreePage(_doc, p); + + return size; + } + + public Bitmap GetPage(int pageId, double zoomFactor) + { + if (pageId < 0 || pageId >= TotalPages) + throw new OverflowException( + $"Page id {pageId} should greater or equal than 0 and less than total page count {TotalPages}."); + + var p = LibMuPdf.NativeMethods.LoadPage(_doc, pageId); + + var bmp = LibMuPdf.RenderPage(_ctx, _doc, p, zoomFactor); + + LibMuPdf.NativeMethods.FreePage(_doc, p); + + return bmp; + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml b/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml new file mode 100644 index 0000000..dd027b0 --- /dev/null +++ b/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml.cs b/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml.cs new file mode 100644 index 0000000..db6d518 --- /dev/null +++ b/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace QuickLook.Plugin.PDFViewer +{ + /// + /// Interaction logic for PdfViewer.xaml + /// + public partial class PdfViewerControl : UserControl, INotifyPropertyChanged, IDisposable + { + private PreviewMouseWheelMonitor _whellMonitor; + + public PdfViewerControl() + { + InitializeComponent(); + + DataContext = this; + + PageIds = new ObservableCollection(); + } + + public ObservableCollection PageIds { get; set; } + + public PdfFile PdfHandleForThumbnails { get; private set; } + + public PdfFile PdfHandle { get; private set; } + + public bool PdfLoaded { get; private set; } + + public double ZoomFactor { get; set; } + + public int TotalPages => PdfHandle.TotalPages; + + public int CurrectPage + { + get => listThumbnails.SelectedIndex; + set + { + listThumbnails.SelectedIndex = value; + listThumbnails.ScrollIntoView(listThumbnails.SelectedItem); + + CurrentPageChanged?.Invoke(this, new EventArgs()); + } + } + + public void Dispose() + { + _whellMonitor.Dispose(); + PdfHandleForThumbnails?.Dispose(); + PdfHandle?.Dispose(); + } + + public event PropertyChangedEventHandler PropertyChanged; + + public event EventHandler CurrentPageChanged; + + private void NavigatePage(object sender, MouseWheelEventArgs e) + { + if (!PdfLoaded) + return; + + if (Keyboard.Modifiers != ModifierKeys.None) + return; + + if (e.Delta > 0) // up + { + if (pageViewPanel.VerticalOffset != 0) return; + + PrevPage(); + e.Handled = true; + } + else // down + { + if (pageViewPanel.VerticalOffset != pageViewPanel.ScrollableHeight) return; + + NextPage(); + e.Handled = true; + } + } + + private void NextPage() + { + if (CurrectPage < PdfHandle.TotalPages - 1) + { + CurrectPage++; + pageViewPanel.ScrollToTop(); + } + } + + private void PrevPage() + { + if (CurrectPage > 0) + { + CurrectPage--; + pageViewPanel.ScrollToBottom(); + } + } + + private void ReRenderCurrentPageLowQuality(double viewZoom, bool fromCenter) + { + if (pageViewPanelImage.Source == null) + return; + + var position = fromCenter + ? new Point(pageViewPanelImage.Source.Width / 2, pageViewPanelImage.Source.Height / 2) + : Mouse.GetPosition(pageViewPanelImage); + + pageViewPanelImage.LayoutTransform = new ScaleTransform(viewZoom, viewZoom); + + // critical for calcuating offset + pageViewPanel.ScrollToHorizontalOffset(0); + pageViewPanel.ScrollToVerticalOffset(0); + UpdateLayout(); + + var offset = pageViewPanelImage.TranslatePoint(position, pageViewPanel) - Mouse.GetPosition(pageViewPanel); + pageViewPanel.ScrollToHorizontalOffset(offset.X); + pageViewPanel.ScrollToVerticalOffset(offset.Y); + UpdateLayout(); + } + + + private void ReRenderCurrentPage() + { + if (!PdfLoaded) + return; + + var image = PdfHandle.GetPage(CurrectPage, ZoomFactor).ToBitmapSource(); + + pageViewPanelImage.Source = image; + pageViewPanelImage.Width = pageViewPanelImage.Source.Width; + pageViewPanelImage.Height = pageViewPanelImage.Source.Height; + + // reset view zoom factor + pageViewPanelImage.LayoutTransform = new ScaleTransform(); + + GC.Collect(); + } + + private void UpdatePageViewWhenSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!PdfLoaded) + return; + + if (CurrectPage == -1) + return; + + ReRenderCurrentPage(); + } + + private void ZoomToFit() + { + if (!PdfLoaded) + return; + + var size = PdfHandle.GetPageSize(CurrectPage, 1d); + + var factor = Math.Min(pageViewPanel.ActualWidth / size.Width, pageViewPanel.ActualHeight / size.Height); + + ZoomFactor = factor; + + ReRenderCurrentPage(); + } + + public Size GetDesiredControlSizeByFirstPage(string path) + { + var tempHandle = new PdfFile(path); + + var size = tempHandle.GetPageSize(0, 1d); + tempHandle.Dispose(); + + size.Width += /*listThumbnails.ActualWidth*/ 150 + 1; + + return size; + } + + public void LoadPdf(string path) + { + PageIds.Clear(); + _whellMonitor?.Dispose(); + + PdfHandleForThumbnails = new PdfFile(path); + PdfHandle = new PdfFile(path); + PdfLoaded = true; + + // fill thumbnails list + Enumerable.Range(0, PdfHandle.TotalPages).ForEach(PageIds.Add); + OnPropertyChanged(nameof(PageIds)); + + CurrectPage = 0; + + // calculate zoom factor for first page + ZoomToFit(); + + // register events + listThumbnails.SelectionChanged += UpdatePageViewWhenSelectionChanged; + //pageViewPanel.SizeChanged += ReRenderCurrentPageWhenSizeChanged; + pageViewPanel.PreviewMouseWheel += NavigatePage; + StartMouseWhellDelayedZoomMonitor(pageViewPanel); + } + + private void StartMouseWhellDelayedZoomMonitor(UIElement ui) + { + if (_whellMonitor == null) + _whellMonitor = new PreviewMouseWheelMonitor(ui, 100); + + var newZoom = 1d; + var scrolling = false; + + _whellMonitor.PreviewMouseWheelStarted += (sender, e) => + { + if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) + return; + + newZoom = ZoomFactor; + scrolling = true; + }; + _whellMonitor.PreviewMouseWheel += (sender, e) => + { + if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) + return; + + e.Handled = true; + + newZoom = newZoom + e.Delta / 120 * 0.1; + + newZoom = Math.Max(newZoom, 0.2); + newZoom = Math.Min(newZoom, 3); + + ReRenderCurrentPageLowQuality(newZoom / ZoomFactor, false); + }; + _whellMonitor.PreviewMouseWheelStopped += (sender, e) => + { + if (!scrolling) + return; + + ZoomFactor = newZoom; + ReRenderCurrentPage(); + }; + } + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/Plugin.cs b/QuickLook.Plugin.PDFViewer/Plugin.cs new file mode 100644 index 0000000..25f14a5 --- /dev/null +++ b/QuickLook.Plugin.PDFViewer/Plugin.cs @@ -0,0 +1,53 @@ +using System.IO; +using System.Text; + +namespace QuickLook.Plugin.PDFViewer +{ + public class Plugin : IViewer + { + private PdfViewerControl _pdfControl; + public int Priority => 9999; + + public bool CanHandle(string path) + { + if (Directory.Exists(path)) + return false; + + if (File.Exists(path) && Path.GetExtension(path).ToLower() == ".pdf") + return true; + + using (var br = new BinaryReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) + { + return Encoding.ASCII.GetString(br.ReadBytes(4)) == "%PDF"; + } + } + + public void View(string path, ViewContentContainer container) + { + _pdfControl = new PdfViewerControl(); + + var desiredSize = _pdfControl.GetDesiredControlSizeByFirstPage(path); + + desiredSize.Width += 150; // add thumbnails column + + container.SetPreferedSizeFit(desiredSize, 0.7); + + container.SetContent(_pdfControl); + + _pdfControl.Loaded += (sender, e) => + { + _pdfControl.LoadPdf(path); + + container.Title = $"{Path.GetFileName(path)} (1 / {_pdfControl.TotalPages})"; + _pdfControl.CurrentPageChanged += (sender2, e2) => container.Title = + $"{Path.GetFileName(path)} ({_pdfControl.CurrectPage + 1} / {_pdfControl.TotalPages})"; + }; + } + + public void Close() + { + _pdfControl.Dispose(); + _pdfControl = null; + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/QuickLook.Plugin.PDFViewer.csproj b/QuickLook.Plugin.PDFViewer/QuickLook.Plugin.PDFViewer.csproj index 7e3b1eb..01bbf3f 100644 --- a/QuickLook.Plugin.PDFViewer/QuickLook.Plugin.PDFViewer.csproj +++ b/QuickLook.Plugin.PDFViewer/QuickLook.Plugin.PDFViewer.csproj @@ -16,31 +16,29 @@ true full false - ..\Build\Debug\Plugins\PDFViewer\ + ..\Build\Debug\Plugins\QuickLook.Plugin.PDFViewer\ DEBUG;TRACE prompt 4 x86 + true pdbonly true - ..\Build\Release\Plugins\PDFViewer\ + ..\Build\Release\Plugins\QuickLook.Plugin.PDFViewer\ TRACE prompt 4 + x86 + true - - ..\packages\MoonPdfLib-x86.0.3.0\lib\MoonPdfLib.dll - - - ..\packages\MoonPdfLib-x86.0.3.0\lib\MouseKeyboardActivityMonitor.dll - + @@ -51,7 +49,16 @@ - + + PdfViewerControl.xaml + + + + + + + + @@ -62,13 +69,15 @@ - - - - - + Always + + + MSBuild:Compile + Designer + + \ No newline at end of file diff --git a/QuickLook.Plugin.PDFViewer/libmupdf.dll b/QuickLook.Plugin.PDFViewer/libmupdf.dll index e28ff73..375d73f 100644 Binary files a/QuickLook.Plugin.PDFViewer/libmupdf.dll and b/QuickLook.Plugin.PDFViewer/libmupdf.dll differ diff --git a/QuickLook.Plugin.PDFViewer/packages.config b/QuickLook.Plugin.PDFViewer/packages.config deleted file mode 100644 index a24d94b..0000000 --- a/QuickLook.Plugin.PDFViewer/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/QuickLook.sln b/QuickLook.sln index f0a5e6a..e550b20 100644 --- a/QuickLook.sln +++ b/QuickLook.sln @@ -10,26 +10,48 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook", "QuickLook\Quic EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QuickLook.Native.Shell32", "QuickLook.Native.Shell32\QuickLook.Native.Shell32.vcxproj", "{D31EE321-C2B0-4984-B749-736F7DE509F1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.PDFViewer", "QuickLook.Plugin.PDFViewer\QuickLook.Plugin.PDFViewer.csproj", "{A82AC69C-EDF5-4F0D-8CBD-8E5E3C06E64D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.PdfViewer", "QuickLook.Plugin.PDFViewer\QuickLook.Plugin.PdfViewer.csproj", "{A82AC69C-EDF5-4F0D-8CBD-8E5E3C06E64D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.LastResort", "QuickLook.Plugin.LastResort\QuickLook.Plugin.LastResort.csproj", "{B9A5A4F6-813E-40CE-AD32-DC5C1356215D}" EndProject Global 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 + {8B4A9CE5-67B5-4A94-81CB-3771F688FDEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B4A9CE5-67B5-4A94-81CB-3771F688FDEB}.Debug|Any CPU.Build.0 = Debug|Any CPU {8B4A9CE5-67B5-4A94-81CB-3771F688FDEB}.Debug|x86.ActiveCfg = Debug|Any CPU {8B4A9CE5-67B5-4A94-81CB-3771F688FDEB}.Debug|x86.Build.0 = Debug|Any CPU + {8B4A9CE5-67B5-4A94-81CB-3771F688FDEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B4A9CE5-67B5-4A94-81CB-3771F688FDEB}.Release|Any CPU.Build.0 = Release|Any CPU {8B4A9CE5-67B5-4A94-81CB-3771F688FDEB}.Release|x86.ActiveCfg = Release|Any CPU {8B4A9CE5-67B5-4A94-81CB-3771F688FDEB}.Release|x86.Build.0 = Release|Any CPU + {D31EE321-C2B0-4984-B749-736F7DE509F1}.Debug|Any CPU.ActiveCfg = Debug|Win32 {D31EE321-C2B0-4984-B749-736F7DE509F1}.Debug|x86.ActiveCfg = Debug|Win32 {D31EE321-C2B0-4984-B749-736F7DE509F1}.Debug|x86.Build.0 = Debug|Win32 + {D31EE321-C2B0-4984-B749-736F7DE509F1}.Release|Any CPU.ActiveCfg = Release|Win32 {D31EE321-C2B0-4984-B749-736F7DE509F1}.Release|x86.ActiveCfg = Release|Win32 {D31EE321-C2B0-4984-B749-736F7DE509F1}.Release|x86.Build.0 = Release|Win32 + {A82AC69C-EDF5-4F0D-8CBD-8E5E3C06E64D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A82AC69C-EDF5-4F0D-8CBD-8E5E3C06E64D}.Debug|Any CPU.Build.0 = Debug|Any CPU {A82AC69C-EDF5-4F0D-8CBD-8E5E3C06E64D}.Debug|x86.ActiveCfg = Debug|Any CPU {A82AC69C-EDF5-4F0D-8CBD-8E5E3C06E64D}.Debug|x86.Build.0 = Debug|Any CPU + {A82AC69C-EDF5-4F0D-8CBD-8E5E3C06E64D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A82AC69C-EDF5-4F0D-8CBD-8E5E3C06E64D}.Release|Any CPU.Build.0 = Release|Any CPU {A82AC69C-EDF5-4F0D-8CBD-8E5E3C06E64D}.Release|x86.ActiveCfg = Release|Any CPU {A82AC69C-EDF5-4F0D-8CBD-8E5E3C06E64D}.Release|x86.Build.0 = Release|Any CPU + {B9A5A4F6-813E-40CE-AD32-DC5C1356215D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9A5A4F6-813E-40CE-AD32-DC5C1356215D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9A5A4F6-813E-40CE-AD32-DC5C1356215D}.Debug|x86.ActiveCfg = Debug|Any CPU + {B9A5A4F6-813E-40CE-AD32-DC5C1356215D}.Debug|x86.Build.0 = Debug|Any CPU + {B9A5A4F6-813E-40CE-AD32-DC5C1356215D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9A5A4F6-813E-40CE-AD32-DC5C1356215D}.Release|Any CPU.Build.0 = Release|Any CPU + {B9A5A4F6-813E-40CE-AD32-DC5C1356215D}.Release|x86.ActiveCfg = Release|Any CPU + {B9A5A4F6-813E-40CE-AD32-DC5C1356215D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/QuickLook.sln.DotSettings b/QuickLook.sln.DotSettings index 0b7d2d1..ab20a03 100644 --- a/QuickLook.sln.DotSettings +++ b/QuickLook.sln.DotSettings @@ -1,5 +1,6 @@  True + True Default: Reformat Code 0 0 diff --git a/QuickLook/App.xaml b/QuickLook/App.xaml index 025d877..286264b 100644 --- a/QuickLook/App.xaml +++ b/QuickLook/App.xaml @@ -5,6 +5,10 @@ Startup="Application_Startup" ShutdownMode="OnExplicitShutdown"> - + + + + + \ No newline at end of file diff --git a/QuickLook/BackgroundListener.cs b/QuickLook/BackgroundListener.cs index 44f3147..ab654b3 100644 --- a/QuickLook/BackgroundListener.cs +++ b/QuickLook/BackgroundListener.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; @@ -12,6 +13,8 @@ namespace QuickLook private GlobalKeyboardHook _hook; + private MainWindow _showingWindow; + protected BackgroundListener() { InstallHook(HotkeyEventHandler); @@ -19,20 +22,46 @@ namespace QuickLook private void HotkeyEventHandler(object sender, KeyEventArgs e) { - var paths = new string[0]; + if (_showingWindow != null) + { + _showingWindow.Close(); + _showingWindow = null; + + GC.Collect(); + + return; + } + + var path = String.Empty; // communicate with COM in a separate thread - Task.Run(() => paths = GetCurrentSelection()).Wait(); + Task.Run(() => + { + var paths = GetCurrentSelection(); - var ddd = PathToPluginMatcher.FindMatch(paths); + if (paths.Any()) + path = paths.First(); - var mw = new MainWindow(); + }).Wait(); - ddd.View(paths[0], mw.ViewContentContainer); + if (String.IsNullOrEmpty(path)) + return; - mw.Show(); + var matched = PluginManager.FindMatch(path); - mw.ShowFinishLoadingAnimation(TimeSpan.FromMilliseconds(200)); + if (matched == null) + return; + + _showingWindow = new MainWindow(); + + _showingWindow.Closed += (sender2, e2) => { _showingWindow = null; }; + + _showingWindow.viewContentContainer.ViewerPlugin = matched; + matched.View(path, _showingWindow.viewContentContainer); + + _showingWindow.Show(); + + _showingWindow.ShowFinishLoadingAnimation(); } private void InstallHook(KeyEventHandler handler) diff --git a/QuickLook/MainWindow.xaml b/QuickLook/MainWindow.xaml index 566be3a..76a112d 100644 --- a/QuickLook/MainWindow.xaml +++ b/QuickLook/MainWindow.xaml @@ -11,23 +11,19 @@ UseLayoutRounding="True" Topmost="True" d:DesignWidth="624" d:DesignHeight="700" WindowStartupLocation="CenterScreen" + ResizeMode="CanResizeWithGrip" x:ClassModifier="internal"> - + - - - - + - - - - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + - + \ No newline at end of file diff --git a/QuickLook/MainWindow.xaml.cs b/QuickLook/MainWindow.xaml.cs index 128598e..da8a530 100644 --- a/QuickLook/MainWindow.xaml.cs +++ b/QuickLook/MainWindow.xaml.cs @@ -1,100 +1,88 @@ using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Runtime.Remoting.Channels; using System.Windows; using System.Windows.Input; using System.Windows.Media.Animation; using System.Windows.Threading; +using QuickLook.Annotations; using QuickLook.ExtensionMethods; +using QuickLook.Utilities; namespace QuickLook { /// /// Interaction logic for MainWindow.xaml /// - internal partial class MainWindow : Window + internal partial class MainWindow : Window, INotifyPropertyChanged { internal MainWindow() { InitializeComponent(); - WindowContainer.Width = LoadingIcon.Width; - WindowContainer.Height = LoadingIcon.Height; - ViewContentContainer.Opacity = 0; + DataContext = this; + + ContentRendered += (sender, e) => AeroGlass.EnableBlur(this); + Closed += MainWindow_Closed; + + buttonCloseWindow.MouseLeftButtonUp += CloseCurrentWindow; + titlebarTitleArea.MouseDown += (sender, e) => DragMove(); + } + + private void MainWindow_Closed(object sender, EventArgs e) + { + viewContentContainer.ViewerPlugin.Close(); } internal new void Show() { - Height = ViewContentContainer.Height; - Width = ViewContentContainer.Width; + loadingIconLayer.Opacity = 1; + + Height = viewContentContainer.PreferedSize.Height + titlebar.Height; + Width = viewContentContainer.PreferedSize.Width; base.Show(); } - internal void ShowFinishLoadingAnimation(TimeSpan delay = new TimeSpan()) + internal void ShowFinishLoadingAnimation() { - var speed = 200; + var speed = 100; var sb = new Storyboard(); - var ptl = new ParallelTimeline {BeginTime = delay}; - - var aWidth = new DoubleAnimation - { - From = WindowContainer.Width, - To = ViewContentContainer.Width, - Duration = TimeSpan.FromMilliseconds(speed), - DecelerationRatio = 0.3 - }; - - var aHeight = new DoubleAnimation - { - From = WindowContainer.Height, - To = ViewContentContainer.Height, - Duration = TimeSpan.FromMilliseconds(speed), - DecelerationRatio = 0.3 - }; - - var aOpacity = new DoubleAnimation - { - From = 0, - To = 1, - BeginTime = TimeSpan.FromMilliseconds(speed * 0.25), - Duration = TimeSpan.FromMilliseconds(speed * 0.75) - }; - + var ptl = new ParallelTimeline(); + var aOpacityR = new DoubleAnimation { From = 1, To = 0, - Duration = TimeSpan.FromMilliseconds(speed * 2) + Duration = TimeSpan.FromMilliseconds(speed) }; - - Storyboard.SetTarget(aWidth, WindowContainer); - Storyboard.SetTarget(aHeight, WindowContainer); - Storyboard.SetTarget(aOpacity, ViewContentContainer); - Storyboard.SetTarget(aOpacityR, LoadingIconLayer); - Storyboard.SetTargetProperty(aWidth, new PropertyPath(WidthProperty)); - Storyboard.SetTargetProperty(aHeight, new PropertyPath(HeightProperty)); - Storyboard.SetTargetProperty(aOpacity, new PropertyPath(OpacityProperty)); + + Storyboard.SetTarget(aOpacityR, loadingIconLayer); Storyboard.SetTargetProperty(aOpacityR, new PropertyPath(OpacityProperty)); - - ptl.Children.Add(aWidth); - ptl.Children.Add(aHeight); - ptl.Children.Add(aOpacity); + ptl.Children.Add(aOpacityR); sb.Children.Add(ptl); sb.Begin(); - Dispatcher.DelayWithPriority(speed * 2, o => LoadingIconLayer.Visibility = Visibility.Hidden, null, + Dispatcher.DelayWithPriority(speed, o => loadingIconLayer.Visibility = Visibility.Hidden, null, DispatcherPriority.Render); } - private void Close_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + private void CloseCurrentWindow(object sender, MouseButtonEventArgs e) { Close(); + } - // useless code to make everyone happy - GC.Collect(); + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } \ No newline at end of file diff --git a/QuickLook/PathToPluginMatcher.cs b/QuickLook/PathToPluginMatcher.cs deleted file mode 100644 index 86b9d06..0000000 --- a/QuickLook/PathToPluginMatcher.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.IO; -using System.Linq; -using QuickLook.Plugin; - -namespace QuickLook -{ - internal static class PathToPluginMatcher - { - internal static IViewer FindMatch(string[] paths) - { - if (paths.Length == 0) - return null; - - //TODO: Handle multiple files? - var path = paths.First(); - - return FindByExtension(path) ?? FindByContent(path); - } - - private static IViewer FindByExtension(string path) - { - if (string.IsNullOrEmpty(path)) - return null; - - var ext = Path.GetExtension(path).ToLower(); - - return PluginManager.GetInstance() - .LoadedPlugins.FirstOrDefault(plugin => - { - if ((plugin.Type & PluginType.ByExtension) == 0) - return false; - - return plugin.SupportExtensions.Any(e => e == ext); - }); - } - - private static IViewer FindByContent(string path) - { - if (string.IsNullOrEmpty(path)) - return null; - - byte[] sample; - using (var br = new BinaryReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) - { - sample = br.ReadBytes(256); - } - - return PluginManager.GetInstance() - .LoadedPlugins.FirstOrDefault(plugin => - { - if ((plugin.Type & PluginType.ByContent) == 0) - return false; - - return plugin.CheckSupportByContent(sample); - }); - } - } -} \ No newline at end of file diff --git a/QuickLook/Plugin/IViewer.cs b/QuickLook/Plugin/IViewer.cs index b1f7a4d..d1d85e1 100644 --- a/QuickLook/Plugin/IViewer.cs +++ b/QuickLook/Plugin/IViewer.cs @@ -2,9 +2,8 @@ { public interface IViewer { - PluginType Type { get; } - string[] SupportExtensions { get; } - bool CheckSupportByContent(byte[] sample); + int Priority { get; } + bool CanHandle(string sample); void View(string path, ViewContentContainer container); void Close(); } diff --git a/QuickLook/Plugin/ViewContentContainer.xaml b/QuickLook/Plugin/ViewContentContainer.xaml index b4de811..c419cd0 100644 --- a/QuickLook/Plugin/ViewContentContainer.xaml +++ b/QuickLook/Plugin/ViewContentContainer.xaml @@ -7,7 +7,7 @@ mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> - + diff --git a/QuickLook/Plugin/ViewContentContainer.xaml.cs b/QuickLook/Plugin/ViewContentContainer.xaml.cs index f2cf657..cb68ab8 100644 --- a/QuickLook/Plugin/ViewContentContainer.xaml.cs +++ b/QuickLook/Plugin/ViewContentContainer.xaml.cs @@ -1,20 +1,71 @@ -using System.Windows.Controls; +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Controls; +using QuickLook.Annotations; namespace QuickLook.Plugin { /// /// Interaction logic for ViewContentContainer.xaml /// - public partial class ViewContentContainer : UserControl + public partial class ViewContentContainer : UserControl,INotifyPropertyChanged { + private string _title = String.Empty; + public ViewContentContainer() { InitializeComponent(); } + public string Title + { + set + { + _title = value; + OnPropertyChanged(nameof(Title)); + } + get => _title; + } + public void SetContent(object content) { - Container.Content = content; + container.Content = content; + } + + public double SetPreferedSizeFit(Size size, double maxRatio) + { + if (maxRatio > 1) + maxRatio = 1; + + var max = GetMaximumDisplayBound(); + + var widthRatio = (max.Width * maxRatio) / size.Width; + var heightRatio = (max.Height * maxRatio) / size.Height; + + var ratio = Math.Min(widthRatio, heightRatio); + + PreferedSize = new Size {Width = size.Width * ratio, Height = size.Height * ratio}; + + return ratio; + } + + public IViewer ViewerPlugin { get; set; } + + public Size PreferedSize { get; set; } + + public Size GetMaximumDisplayBound() + { + return new Size(SystemParameters.VirtualScreenWidth, SystemParameters.VirtualScreenHeight); + } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } \ No newline at end of file diff --git a/QuickLook/PluginManager.cs b/QuickLook/PluginManager.cs index 83cdad8..b2de1a3 100644 --- a/QuickLook/PluginManager.cs +++ b/QuickLook/PluginManager.cs @@ -11,18 +11,27 @@ namespace QuickLook { private static PluginManager _instance; - internal PluginManager() + private PluginManager() { LoadPlugins(); } - internal List LoadedPlugins { get; } = new List(); + internal List LoadedPlugins { get; private set; } = new List(); internal static PluginManager GetInstance() { return _instance ?? (_instance = new PluginManager()); } + internal static IViewer FindMatch(string path) + { + if (string.IsNullOrEmpty(path)) + return null; + + return GetInstance() + .LoadedPlugins.FirstOrDefault(plugin => plugin.CanHandle(path)); + } + private void LoadPlugins() { Directory.GetFiles(Path.Combine(App.AppPath, "Plugins\\"), "QuickLook.Plugin.*.dll", @@ -37,6 +46,8 @@ namespace QuickLook select t).ToList() .ForEach(type => LoadedPlugins.Add((IViewer) Activator.CreateInstance(type))); }); + + LoadedPlugins = LoadedPlugins.OrderByDescending(i => i.Priority).ToList(); } } } \ No newline at end of file diff --git a/QuickLook/Properties/Annotations.cs b/QuickLook/Properties/Annotations.cs new file mode 100644 index 0000000..448da52 --- /dev/null +++ b/QuickLook/Properties/Annotations.cs @@ -0,0 +1,1048 @@ +/* MIT License + +Copyright (c) 2016 JetBrains http://www.jetbrains.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ + +using System; + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace QuickLook.Annotations +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage. + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + public sealed class CanBeNullAttribute : Attribute { } + + /// + /// Indicates that the value of the marked element could never be null. + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + public sealed class NotNullAttribute : Attribute { } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemNotNullAttribute : Attribute { } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemCanBeNullAttribute : Attribute { } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form. + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method | + AttributeTargets.Property | AttributeTargets.Delegate)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute([NotNull] string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + [NotNull] public string FormatParameterName { get; private set; } + } + + /// + /// For a parameter that is expected to be one of the limited set of values. + /// Specify fields of which type should be used as values for this parameter. + /// + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, + AllowMultiple = true)] + public sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InvokerParameterNameAttribute : Attribute { } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() { } + public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) + { + ParameterName = parameterName; + } + + [CanBeNull] public string ParameterName { get; private set; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If method has single input parameter, it's name could be omitted.
+ /// Using halt (or void/nothing, which is the same) for method output + /// means that the methos doesn't return normally (throws or terminates the process).
+ /// Value canbenull is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute + /// with rows separated by semicolon. There is no notion of order rows, all rows are checked + /// for applicability and applied per each program state tracked by R# analysis.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("halt <= condition: false")] + /// public void Assert(bool condition, string text) // regular assertion method + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) { } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + [NotNull] public string Contract { get; private set; } + + public bool ForceFullStates { get; private set; } + } + + /// + /// Indicates that marked element should be localized or not. + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) { } + + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; private set; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// class ComponentAttribute : Attribute { } + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// class MyComponent : IComponent { } + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof(Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] public Type BaseType { get; private set; } + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will not be marked as unused (as well as by other usage inspections). + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; private set; } + + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + /// + /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes + /// as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] + public sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } + + [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + /// Only entity marked with attribute considered used. + Access = 1, + /// Indicates implicit assignment to a member. + Assign = 2, + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specify what is considered used implicitly when marked + /// with or . + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Default = Itself, + Itself = 1, + /// Members of entity marked with attribute are considered used. + Members = 2, + /// Entity marked with attribute and all its members considered used. + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() { } + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [CanBeNull] public string Comment { get; private set; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InstantHandleAttribute : Attribute { } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Waring: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class PureAttribute : Attribute { } + + /// + /// Indicates that the return value of method invocation must be used. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() { } + + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + [CanBeNull] public string Justification { get; private set; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] + public sealed class ProvidesContextAttribute : Attribute { } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() { } + + public PathReferenceAttribute([NotNull, PathReference] string basePath) + { + BasePath = basePath; + } + + [CanBeNull] public string BasePath { get; private set; } + } + + /// + /// An extension method marked with this attribute is processed by ReSharper code completion + /// as a 'Source Template'. When extension method is completed over some expression, it's source code + /// is automatically expanded like a template at call site. + /// + /// + /// Template method body can contain valid source code and/or special comments starting with '$'. + /// Text inside these comments is added as source code when the template is applied. Template parameters + /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. + /// Use the attribute to specify macros for parameters. + /// + /// + /// In this example, the 'forEach' method is a source template available over all values + /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: + /// + /// [SourceTemplate] + /// public static void forEach<T>(this IEnumerable<T> xs) { + /// foreach (var x in xs) { + /// //$ $END$ + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class SourceTemplateAttribute : Attribute { } + + /// + /// Allows specifying a macro for a parameter of a source template. + /// + /// + /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression + /// is defined in the property. When applied on a method, the target + /// template parameter is defined in the property. To apply the macro silently + /// for the parameter, set the property value = -1. + /// + /// + /// Applying the attribute on a source template method: + /// + /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] + /// public static void forEach<T>(this IEnumerable<T> collection) { + /// foreach (var item in collection) { + /// //$ $END$ + /// } + /// } + /// + /// Applying the attribute on a template method parameter: + /// + /// [SourceTemplate] + /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { + /// /*$ var $x$Id = "$newguid$" + x.ToString(); + /// x.DoSomething($x$Id); */ + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] + public sealed class MacroAttribute : Attribute + { + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + [CanBeNull] public string Expression { get; set; } + + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// > + public int Editable { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + [CanBeNull] public string Target { get; set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() { } + + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() { } + + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() { } + + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcMasterAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcModelTypeAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcPartialViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class AspMvcSuppressViewErrorAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcDisplayTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcEditorTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcViewComponentAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewComponentViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public sealed class AspMvcActionSelectorAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() { } + + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [CanBeNull] public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + /// + /// Razor attribute. Indicates that a parameter or a method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class RazorSectionAttribute : Attribute { } + + /// + /// Indicates how method, constructor invocation or property access + /// over collection type affects content of the collection. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] + public sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; private set; } + } + + [Flags] + public enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + /// Method only reads content of the collection but does not modify it. + Read = 1, + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class AssertionMethodAttribute : Attribute { } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; private set; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + public enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3, + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception. + /// + [Obsolete("Use [ContractAnnotation('=> halt')] instead")] + [AttributeUsage(AttributeTargets.Method)] + public sealed class TerminatesProgramAttribute : Attribute { } + + /// + /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LinqTunnelAttribute : Attribute { } + + /// + /// Indicates that IEnumerable, passed as parameter, is not enumerated. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class NoEnumerationAttribute : Attribute { } + + /// + /// Indicates that parameter is regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RegexPatternAttribute : Attribute { } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns + /// + [AttributeUsage( + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] + public sealed class NoReorderAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class XamlItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + [NotNull] public string TagName { get; private set; } + + [NotNull] public Type ControlType { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspMethodPropertyAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + [NotNull] public string Attribute { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspTypePropertyAttribute : Attribute + { + public bool CreateConstructorReferences { get; private set; } + + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) + { + Type = type; + FieldName = fieldName; + } + + [NotNull] public string Type { get; private set; } + + [NotNull] public string FieldName { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorDirectiveAttribute : Attribute + { + public RazorDirectiveAttribute([NotNull] string directive) + { + Directive = directive; + } + + [NotNull] public string Directive { get; private set; } + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorHelperCommonAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class RazorLayoutAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteLiteralMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RazorWriteMethodParameterAttribute : Attribute { } +} \ No newline at end of file diff --git a/QuickLook/QuickLook.csproj b/QuickLook/QuickLook.csproj index 502902f..52ab907 100644 --- a/QuickLook/QuickLook.csproj +++ b/QuickLook/QuickLook.csproj @@ -73,13 +73,16 @@ MSBuild:Compile Designer - ViewContentContainer.xaml + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -92,6 +95,8 @@ + + MainWindow.xaml diff --git a/QuickLook/Styles/ScrollBarStyleDictionary.xaml b/QuickLook/Styles/ScrollBarStyleDictionary.xaml new file mode 100644 index 0000000..087562e --- /dev/null +++ b/QuickLook/Styles/ScrollBarStyleDictionary.xaml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QuickLook/Utilities/AeroGlass.cs b/QuickLook/Utilities/AeroGlass.cs new file mode 100644 index 0000000..c15bb41 --- /dev/null +++ b/QuickLook/Utilities/AeroGlass.cs @@ -0,0 +1,67 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; + +namespace QuickLook.Utilities +{ + internal class AeroGlass + { + internal static void EnableBlur(Window window) + { + var windowHelper = new WindowInteropHelper(window); + + var accent = new AccentPolicy(); + var accentStructSize = Marshal.SizeOf(accent); + accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; + + var accentPtr = Marshal.AllocHGlobal(accentStructSize); + Marshal.StructureToPtr(accent, accentPtr, false); + + var data = new WindowCompositionAttributeData(); + data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY; + data.SizeOfData = accentStructSize; + data.Data = accentPtr; + + SetWindowCompositionAttribute(windowHelper.Handle, ref data); + + Marshal.FreeHGlobal(accentPtr); + } + + [DllImport("user32.dll")] + private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); + + [StructLayout(LayoutKind.Sequential)] + private struct WindowCompositionAttributeData + { + public WindowCompositionAttribute Attribute; + public IntPtr Data; + public int SizeOfData; + } + + private enum WindowCompositionAttribute + { + // ... + WCA_ACCENT_POLICY = 19 + // ... + } + + private enum AccentState + { + ACCENT_DISABLED = 0, + ACCENT_ENABLE_GRADIENT = 1, + ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, + ACCENT_ENABLE_BLURBEHIND = 3, + ACCENT_INVALID_STATE = 4 + } + + [StructLayout(LayoutKind.Sequential)] + private struct AccentPolicy + { + public AccentState AccentState; + public int AccentFlags; + public int GradientColor; + public int AnimationId; + } + } +} \ No newline at end of file