diff --git a/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/Plugin.cs index 96f97ea..6f310cb 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ArchiveViewer/Plugin.cs @@ -45,17 +45,19 @@ namespace QuickLook.Plugin.ArchiveViewer return true; } - public void Prepare(string path, ViewContentContainer container) + public void BoundViewSize(string path, ViewerObject context) { - container.PreferedSize = new Size {Width = 800, Height = 600}; + context.PreferredSize = new Size {Width = 800, Height = 600}; } - public void View(string path, ViewContentContainer container) + public void View(string path, ViewerObject context) { _panel = new ArchiveInfoPanel(path); - container.SetContent(_panel); - container.Title = $"{Path.GetFileName(path)}"; + context.ViewerContent = _panel; + context.Title = $"{Path.GetFileName(path)}"; + + context.IsBusy = false; } public void Dispose() diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs index 792d0d1..83a5d23 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs @@ -33,19 +33,21 @@ namespace QuickLook.Plugin.ImageViewer } } - public void Prepare(string path, ViewContentContainer container) + public void BoundViewSize(string path, ViewerObject context) { _imageSize = ImageFileHelper.GetImageSize(path); - container.SetPreferedSizeFit(_imageSize, 0.8); + context.SetPreferredSizeFit(_imageSize, 0.8); } - public void View(string path, ViewContentContainer container) + public void View(string path, ViewerObject context) { _ip = new ImagePanel(path); - container.SetContent(_ip); - container.Title = $"{Path.GetFileName(path)} ({_imageSize.Width} × {_imageSize.Height})"; + context.ViewerContent = _ip; + context.Title = $"{Path.GetFileName(path)} ({_imageSize.Width} × {_imageSize.Height})"; + + context.IsBusy = false; } public void Dispose() diff --git a/QuickLook.Plugin/QuickLook.Plugin.OfficeViewer/PluginInterface.cs b/QuickLook.Plugin/QuickLook.Plugin.OfficeViewer/PluginInterface.cs index 3e67679..9292728 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.OfficeViewer/PluginInterface.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.OfficeViewer/PluginInterface.cs @@ -31,12 +31,12 @@ namespace QuickLook.Plugin.OfficeViewer return false; } - public void Prepare(string path, ViewContentContainer container) + public void BoundViewSize(string path, ViewerObject context) { - container.SetPreferedSizeFit(new Size {Width = 800, Height = 600}, 0.8); + context.SetPreferredSizeFit(new Size {Width = 800, Height = 600}, 0.8); } - public void View(string path, ViewContentContainer container) + public void View(string path, ViewerObject context) { using (var officeApp = new OfficeInteropWrapper(path)) { @@ -59,12 +59,14 @@ namespace QuickLook.Plugin.OfficeViewer throw ex; } - container.Title = $"{Path.GetFileName(path)} (1 / {_pdfViewer.TotalPages})"; + context.Title = $"{Path.GetFileName(path)} (1 / {_pdfViewer.TotalPages})"; }; - _pdfViewer.CurrentPageChanged += (sender, e) => container.Title = + _pdfViewer.CurrentPageChanged += (sender, e) => context.Title = $"{Path.GetFileName(path)} ({_pdfViewer.CurrectPage + 1} / {_pdfViewer.TotalPages})"; - container.SetContent(_pdfViewer); + context.ViewerContent = _pdfViewer; + + context.IsBusy = false; } public void Dispose() diff --git a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/Plugin.cs index 6ffddd7..ef7934a 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/Plugin.cs @@ -7,6 +7,7 @@ namespace QuickLook.Plugin.PDFViewer public class Plugin : IViewer { private PdfViewerControl _pdfControl; + public int Priority => int.MaxValue; public bool CanHandle(string path) @@ -23,26 +24,28 @@ namespace QuickLook.Plugin.PDFViewer } } - public void Prepare(string path, ViewContentContainer container) + public void BoundViewSize(string path, ViewerObject context) { _pdfControl = new PdfViewerControl(); var desiredSize = _pdfControl.GetDesiredControlSizeByFirstPage(path); - container.SetPreferedSizeFit(desiredSize, 0.8); + context.SetPreferredSizeFit(desiredSize, 0.8); } - public void View(string path, ViewContentContainer container) + public void View(string path, ViewerObject context) { - container.SetContent(_pdfControl); + context.ViewerContent = _pdfControl; _pdfControl.Loaded += (sender, e) => { _pdfControl.LoadPdf(path); - container.Title = $"{Path.GetFileName(path)} (1 / {_pdfControl.TotalPages})"; - _pdfControl.CurrentPageChanged += (sender2, e2) => container.Title = + context.Title = $"{Path.GetFileName(path)} (1 / {_pdfControl.TotalPages})"; + _pdfControl.CurrentPageChanged += (sender2, e2) => context.Title = $"{Path.GetFileName(path)} ({_pdfControl.CurrectPage + 1} / {_pdfControl.TotalPages})"; + + context.IsBusy = false; }; } diff --git a/QuickLook.Plugin/QuickLook.Plugin.TextViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.TextViewer/Plugin.cs index 2ba1f69..6c6bd99 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.TextViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.TextViewer/Plugin.cs @@ -40,17 +40,19 @@ namespace QuickLook.Plugin.TextViewer } } - public void Prepare(string path, ViewContentContainer container) + public void BoundViewSize(string path, ViewerObject context) { - container.PreferedSize = new Size {Width = 800, Height = 600}; + context.PreferredSize = new Size {Width = 800, Height = 600}; } - public void View(string path, ViewContentContainer container) + public void View(string path, ViewerObject context) { _tvp = new TextViewerPanel(path); - container.SetContent(_tvp); - container.Title = $"{Path.GetFileName(path)}"; + context.ViewerContent = _tvp; + context.Title = $"{Path.GetFileName(path)}"; + + context.IsBusy = false; } public void Dispose() diff --git a/QuickLook/App.xaml b/QuickLook/App.xaml index 286264b..f6e5309 100644 --- a/QuickLook/App.xaml +++ b/QuickLook/App.xaml @@ -8,6 +8,7 @@ + diff --git a/QuickLook/Controls/BackgroundVisualHost.cs b/QuickLook/Controls/BackgroundVisualHost.cs new file mode 100644 index 0000000..79302c4 --- /dev/null +++ b/QuickLook/Controls/BackgroundVisualHost.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections; +using System.Threading; +using System.Windows; +using System.Windows.Media; +using System.Windows.Threading; + +namespace QuickLook.Controls +{ + public delegate Visual CreateContentFunction(); + + public class BackgroundVisualHost : FrameworkElement + { + protected override int VisualChildrenCount => _hostVisual != null ? 1 : 0; + + protected override IEnumerator LogicalChildren + { + get + { + if (_hostVisual != null) + yield return _hostVisual; + } + } + + protected override Visual GetVisualChild(int index) + { + if (_hostVisual != null && index == 0) + return _hostVisual; + + throw new IndexOutOfRangeException("index"); + } + + private void CreateContentHelper() + { + _threadedHelper = new ThreadedVisualHelper(CreateContent, SafeInvalidateMeasure); + _hostVisual = _threadedHelper.HostVisual; + } + + private void SafeInvalidateMeasure() + { + Dispatcher.BeginInvoke(new Action(InvalidateMeasure), DispatcherPriority.Loaded); + } + + private void HideContentHelper() + { + if (_threadedHelper != null) + { + _threadedHelper.Exit(); + _threadedHelper = null; + InvalidateMeasure(); + } + } + + protected override Size MeasureOverride(Size availableSize) + { + if (_threadedHelper != null) + return _threadedHelper.DesiredSize; + + return base.MeasureOverride(availableSize); + } + + private class ThreadedVisualHelper + { + private readonly CreateContentFunction _createContent; + private readonly Action _invalidateMeasure; + private readonly AutoResetEvent _sync = + new AutoResetEvent(false); + + public ThreadedVisualHelper( + CreateContentFunction createContent, + Action invalidateMeasure) + { + HostVisual = new HostVisual(); + _createContent = createContent; + _invalidateMeasure = invalidateMeasure; + + var backgroundUi = new Thread(CreateAndShowContent); + backgroundUi.SetApartmentState(ApartmentState.STA); + backgroundUi.Name = "BackgroundVisualHostThread"; + backgroundUi.IsBackground = true; + backgroundUi.Start(); + + _sync.WaitOne(); + } + + public HostVisual HostVisual { get; } + public Size DesiredSize { get; private set; } + private Dispatcher Dispatcher { get; set; } + + public void Exit() + { + Dispatcher.BeginInvokeShutdown(DispatcherPriority.Send); + } + + private void CreateAndShowContent() + { + Dispatcher = Dispatcher.CurrentDispatcher; + var source = + new VisualTargetPresentationSource(HostVisual); + _sync.Set(); + source.RootVisual = _createContent(); + DesiredSize = source.DesiredSize; + _invalidateMeasure(); + + Dispatcher.Run(); + source.Dispose(); + } + } + + #region Private Fields + + private ThreadedVisualHelper _threadedHelper; + private HostVisual _hostVisual; + + #endregion + + #region IsContentShowingProperty + + /// + /// Identifies the IsContentShowing dependency property. + /// + public static readonly DependencyProperty IsContentShowingProperty = DependencyProperty.Register( + "IsContentShowing", + typeof(bool), + typeof(BackgroundVisualHost), + new FrameworkPropertyMetadata(false, OnIsContentShowingChanged)); + + /// + /// Gets or sets if the content is being displayed. + /// + public bool IsContentShowing + { + get => (bool) GetValue(IsContentShowingProperty); + set => SetValue(IsContentShowingProperty, value); + } + + private static void OnIsContentShowingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var bvh = (BackgroundVisualHost) d; + + if (bvh.CreateContent != null) + if ((bool) e.NewValue) + bvh.CreateContentHelper(); + else + bvh.HideContentHelper(); + } + + #endregion + + #region CreateContent Property + + /// + /// Identifies the CreateContent dependency property. + /// + public static readonly DependencyProperty CreateContentProperty = DependencyProperty.Register( + "CreateContent", + typeof(CreateContentFunction), + typeof(BackgroundVisualHost), + new FrameworkPropertyMetadata(OnCreateContentChanged)); + + /// + /// Gets or sets the function used to create the visual to display in a background thread. + /// + public CreateContentFunction CreateContent + { + get => (CreateContentFunction) GetValue(CreateContentProperty); + set => SetValue(CreateContentProperty, value); + } + + private static void OnCreateContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var bvh = (BackgroundVisualHost) d; + + if (bvh.IsContentShowing) + { + bvh.HideContentHelper(); + if (e.NewValue != null) + bvh.CreateContentHelper(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/QuickLook/Controls/BusyDecorator.cs b/QuickLook/Controls/BusyDecorator.cs new file mode 100644 index 0000000..de44137 --- /dev/null +++ b/QuickLook/Controls/BusyDecorator.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; + +namespace QuickLook.Controls +{ + [StyleTypedProperty(Property = "BusyStyle", StyleTargetType = typeof(Control))] + public class BusyDecorator : Decorator + { + private readonly BackgroundVisualHost _busyHost = new BackgroundVisualHost(); + + static BusyDecorator() + { + DefaultStyleKeyProperty.OverrideMetadata( + typeof(BusyDecorator), + new FrameworkPropertyMetadata(typeof(BusyDecorator))); + } + + public BusyDecorator() + { + AddLogicalChild(_busyHost); + AddVisualChild(_busyHost); + + SetBinding(_busyHost, IsBusyIndicatorShowingProperty, BackgroundVisualHost.IsContentShowingProperty); + SetBinding(_busyHost, BusyHorizontalAlignmentProperty, HorizontalAlignmentProperty); + SetBinding(_busyHost, BusyVerticalAlignmentProperty, VerticalAlignmentProperty); + } + + protected override int VisualChildrenCount => Child != null ? 2 : 1; + + protected override IEnumerator LogicalChildren + { + get + { + if (Child != null) + yield return Child; + + yield return _busyHost; + } + } + + protected override Visual GetVisualChild(int index) + { + if (Child != null) + switch (index) + { + case 0: + return Child; + + case 1: + return _busyHost; + } + else if (index == 0) + return _busyHost; + + throw new IndexOutOfRangeException("index"); + } + + private void SetBinding(DependencyObject obj, DependencyProperty source, DependencyProperty target) + { + var b = new Binding(); + b.Source = this; + b.Path = new PropertyPath(source); + BindingOperations.SetBinding(obj, target, b); + } + + protected override Size MeasureOverride(Size constraint) + { + var ret = new Size(0, 0); + if (Child != null) + { + Child.Measure(constraint); + ret = Child.DesiredSize; + } + + _busyHost.Measure(constraint); + + return new Size(Math.Max(ret.Width, _busyHost.DesiredSize.Width), + Math.Max(ret.Height, _busyHost.DesiredSize.Height)); + } + + protected override Size ArrangeOverride(Size arrangeSize) + { + var ret = new Size(0, 0); + if (Child != null) + { + Child.Arrange(new Rect(arrangeSize)); + ret = Child.RenderSize; + } + + _busyHost.Arrange(new Rect(arrangeSize)); + + return new Size(Math.Max(ret.Width, _busyHost.RenderSize.Width), + Math.Max(ret.Height, _busyHost.RenderSize.Height)); + } + + #region IsBusyIndicatorShowing Property + + /// + /// Identifies the IsBusyIndicatorShowing dependency property. + /// + public static readonly DependencyProperty IsBusyIndicatorShowingProperty = DependencyProperty.Register( + "IsBusyIndicatorShowing", + typeof(bool), + typeof(BusyDecorator), + new FrameworkPropertyMetadata(false, + FrameworkPropertyMetadataOptions.AffectsMeasure)); + + /// + /// Gets or sets if the BusyIndicator is being shown. + /// + public bool IsBusyIndicatorShowing + { + get => (bool) GetValue(IsBusyIndicatorShowingProperty); + set => SetValue(IsBusyIndicatorShowingProperty, value); + } + + #endregion + + #region BusyStyle + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty BusyStyleProperty = + DependencyProperty.Register( + "BusyStyle", + typeof(Style), + typeof(BusyDecorator), + new FrameworkPropertyMetadata(OnBusyStyleChanged)); + + /// + /// Gets or sets the Style to apply to the Control that is displayed as the busy indication. + /// + public Style BusyStyle + { + get => (Style) GetValue(BusyStyleProperty); + set => SetValue(BusyStyleProperty, value); + } + + private static void OnBusyStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var bd = (BusyDecorator) d; + var nVal = (Style) e.NewValue; + bd._busyHost.CreateContent = () => new Control {Style = nVal}; + } + + #endregion + + #region BusyHorizontalAlignment + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty BusyHorizontalAlignmentProperty = DependencyProperty.Register( + "BusyHorizontalAlignment", + typeof(HorizontalAlignment), + typeof(BusyDecorator), + new FrameworkPropertyMetadata(HorizontalAlignment.Center)); + + /// + /// Gets or sets the HorizontalAlignment to use to layout the control that contains the busy indicator control. + /// + public HorizontalAlignment BusyHorizontalAlignment + { + get => (HorizontalAlignment) GetValue(BusyHorizontalAlignmentProperty); + set => SetValue(BusyHorizontalAlignmentProperty, value); + } + + #endregion + + #region BusyVerticalAlignment + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty BusyVerticalAlignmentProperty = DependencyProperty.Register( + "BusyVerticalAlignment", + typeof(VerticalAlignment), + typeof(BusyDecorator), + new FrameworkPropertyMetadata(VerticalAlignment.Center)); + + /// + /// Gets or sets the the VerticalAlignment to use to layout the control that contains the busy indicator. + /// + public VerticalAlignment BusyVerticalAlignment + { + get => (VerticalAlignment) GetValue(BusyVerticalAlignmentProperty); + set => SetValue(BusyVerticalAlignmentProperty, value); + } + + #endregion + } +} \ No newline at end of file diff --git a/QuickLook/Controls/VisualTargetPresentationSource.cs b/QuickLook/Controls/VisualTargetPresentationSource.cs new file mode 100644 index 0000000..141984a --- /dev/null +++ b/QuickLook/Controls/VisualTargetPresentationSource.cs @@ -0,0 +1,64 @@ +using System.Windows; +using System.Windows.Media; + +namespace QuickLook.Controls +{ + public class VisualTargetPresentationSource : PresentationSource + { + private readonly VisualTarget _visualTarget; + private bool _isDisposed; + + public VisualTargetPresentationSource(HostVisual hostVisual) + { + _visualTarget = new VisualTarget(hostVisual); + AddSource(); + } + + public Size DesiredSize { get; private set; } + + public override Visual RootVisual + { + get => _visualTarget.RootVisual; + set + { + var oldRoot = _visualTarget.RootVisual; + + // Set the root visual of the VisualTarget. This visual will + // now be used to visually compose the scene. + _visualTarget.RootVisual = value; + + // Tell the PresentationSource that the root visual has + // changed. This kicks off a bunch of stuff like the + // Loaded event. + RootChanged(oldRoot, value); + + // Kickoff layout... + var rootElement = value as UIElement; + if (rootElement != null) + { + rootElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + rootElement.Arrange(new Rect(rootElement.DesiredSize)); + + DesiredSize = rootElement.DesiredSize; + } + else + { + DesiredSize = new Size(0, 0); + } + } + } + + public override bool IsDisposed => _isDisposed; + + protected override CompositionTarget GetCompositionTargetCore() + { + return _visualTarget; + } + + internal void Dispose() + { + RemoveSource(); + _isDisposed = true; + } + } +} \ No newline at end of file diff --git a/QuickLook/Utilities/AeroGlass.cs b/QuickLook/Helpers/AeroGlassHelper.cs similarity index 96% rename from QuickLook/Utilities/AeroGlass.cs rename to QuickLook/Helpers/AeroGlassHelper.cs index d055894..46e2084 100644 --- a/QuickLook/Utilities/AeroGlass.cs +++ b/QuickLook/Helpers/AeroGlassHelper.cs @@ -3,9 +3,9 @@ using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; -namespace QuickLook.Utilities +namespace QuickLook.Helpers { - internal static class AeroGlass + internal static class AeroGlassHelper { internal static void EnableBlur(Window window) { diff --git a/QuickLook/Helpers/DpiHelpers.cs b/QuickLook/Helpers/DpiHelpers.cs new file mode 100644 index 0000000..c8194fc --- /dev/null +++ b/QuickLook/Helpers/DpiHelpers.cs @@ -0,0 +1,46 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace QuickLook.Helpers +{ + internal static class DpiHelper + { + public const float DEFAULT_DPI = 96; + + public static Dpi GetCurrentDpi() + { + var g = Graphics.FromHwnd(IntPtr.Zero); + var 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); + + private enum DeviceCap + { + /// + /// Logical pixels inch in X + /// + LOGPIXELSX = 88, + /// + /// Logical pixels inch in Y + /// + LOGPIXELSY = 90 + } + + public struct Dpi + { + public float HorizontalDpi; + public float VerticalDpi; + } + } +} \ No newline at end of file diff --git a/QuickLook/Utilities/WindowHelper.cs b/QuickLook/Helpers/WindowHelper.cs similarity index 98% rename from QuickLook/Utilities/WindowHelper.cs rename to QuickLook/Helpers/WindowHelper.cs index 9d901bd..3808b9a 100644 --- a/QuickLook/Utilities/WindowHelper.cs +++ b/QuickLook/Helpers/WindowHelper.cs @@ -3,7 +3,7 @@ using System.Text; using System.Windows.Interop; using QuickLook.NativeMethods; -namespace QuickLook.Utilities +namespace QuickLook.Helpers { internal static class WindowHelper { diff --git a/QuickLook/MainWindow.xaml b/QuickLook/MainWindow.xaml index c9ab06d..9f34b87 100644 --- a/QuickLook/MainWindow.xaml +++ b/QuickLook/MainWindow.xaml @@ -2,72 +2,84 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:fa="http://schemas.fontawesome.io/icons/" - xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:QuickLook" + xmlns:control="clr-namespace:QuickLook.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:plugin="clr-namespace:QuickLook.Plugin" mc:Ignorable="d" x:Class="QuickLook.MainWindow" x:Name="mainWindow" UseLayoutRounding="True" - Topmost="True" d:DesignWidth="624" d:DesignHeight="700" + d:DesignWidth="624" d:DesignHeight="700" MinWidth="275" MinHeight="150" WindowStartupLocation="CenterScreen" x:ClassModifier="internal" Focusable="False" ShowActivated="False" ShowInTaskbar="False" WindowStyle="None" ResizeMode="CanResizeWithGrip" AllowsTransparency="True"> - + - - - - - - - + + - - + - - - - - - - + + + + + diff --git a/QuickLook/MainWindow.xaml.cs b/QuickLook/MainWindow.xaml.cs index ee9b328..7d5e9ca 100644 --- a/QuickLook/MainWindow.xaml.cs +++ b/QuickLook/MainWindow.xaml.cs @@ -1,54 +1,74 @@ using System; -using System.ComponentModel; -using System.Runtime.CompilerServices; +using System.Diagnostics; using System.Windows; using System.Windows.Input; using System.Windows.Interop; -using System.Windows.Media.Animation; -using System.Windows.Threading; -using QuickLook.Annotations; -using QuickLook.ExtensionMethods; +using QuickLook.Helpers; using QuickLook.Plugin; -using QuickLook.Utilities; namespace QuickLook { /// /// Interaction logic for MainWindow.xaml /// - internal partial class MainWindow : Window, INotifyPropertyChanged, IDisposable + internal partial class MainWindow : Window, IDisposable { internal MainWindow() { + // this object should be initialized before loading UI components, because many of which are binding to it. + ViewerObject = new ViewerObject(); + InitializeComponent(); - DataContext = this; + // do not set TopMost property if we are now debugging. it makes debugging painful... + if (!Debugger.IsAttached) + Topmost = true; - ContentRendered += (sender, e) => AeroGlass.EnableBlur(this); + // restore changes by Designer + windowPanel.Opacity = 0d; + busyIndicatorLayer.Visibility = Visibility.Visible; - buttonCloseWindow.MouseLeftButtonUp += CloseCurrentWindow; - titlebarTitleArea.MouseLeftButtonDown += (sender, e) => DragMove(); + Loaded += (sender, e) => AeroGlassHelper.EnableBlur(this); + + buttonCloseWindow.MouseLeftButtonUp += (sender, e) => Close(); + titlebarTitleArea.MouseLeftButtonDown += DragMoveCurrentWindow; } + public ViewerObject ViewerObject { get; } + public void Dispose() { GC.SuppressFinalize(this); - viewContentContainer?.Dispose(); + ViewerObject?.Dispose(); } - public event PropertyChangedEventHandler PropertyChanged; - - internal new void Show() + private void DragMoveCurrentWindow(object sender, MouseButtonEventArgs e) { - loadingIconLayer.Opacity = 1; + if (WindowState == WindowState.Maximized) + { + var dpi = DpiHelper.GetCurrentDpi(); - Height = viewContentContainer.PreferedSize.Height + titlebar.Height + windowBorder.BorderThickness.Top + + // MouseDevice.GetPosition() returns device-dependent coordinate, however WPF is not like that + var point = PointToScreen(e.MouseDevice.GetPosition(this)); + + Left = point.X / (dpi.HorizontalDpi / DpiHelper.DEFAULT_DPI) - RestoreBounds.Width * 0.5; + Top = point.Y / (dpi.VerticalDpi / DpiHelper.DEFAULT_DPI); + + WindowState = WindowState.Normal; + } + + DragMove(); + } + + private new void Show() + { + Height = ViewerObject.PreferredSize.Height + titlebar.Height + windowBorder.BorderThickness.Top + windowBorder.BorderThickness.Bottom; - Width = viewContentContainer.PreferedSize.Width + windowBorder.BorderThickness.Left + + Width = ViewerObject.PreferredSize.Width + windowBorder.BorderThickness.Left + windowBorder.BorderThickness.Right; - ResizeMode = viewContentContainer.CanResize ? ResizeMode.CanResizeWithGrip : ResizeMode.NoResize; + ResizeMode = ViewerObject.CanResize ? ResizeMode.CanResizeWithGrip : ResizeMode.NoResize; base.Show(); @@ -57,54 +77,15 @@ namespace QuickLook internal void BeginShow(IViewer matchedPlugin, string path) { - viewContentContainer.ViewerPlugin = matchedPlugin; + ViewerObject.CurrentContentContainer = viewContentContainer; + ViewerObject.ViewerPlugin = matchedPlugin; // get window size before showing it - matchedPlugin.Prepare(path, viewContentContainer); + matchedPlugin.BoundViewSize(path, ViewerObject); Show(); - matchedPlugin.View(path, viewContentContainer); - - ShowFinishLoadingAnimation(); - } - - private void ShowFinishLoadingAnimation() - { - var speed = 100; - - var sb = new Storyboard(); - var ptl = new ParallelTimeline(); - - var aOpacityR = new DoubleAnimation - { - From = 1, - To = 0, - Duration = TimeSpan.FromMilliseconds(speed) - }; - - Storyboard.SetTarget(aOpacityR, loadingIconLayer); - Storyboard.SetTargetProperty(aOpacityR, new PropertyPath(OpacityProperty)); - - ptl.Children.Add(aOpacityR); - - sb.Children.Add(ptl); - - sb.Begin(); - - Dispatcher.DelayWithPriority(speed, o => loadingIconLayer.Visibility = Visibility.Hidden, null, - DispatcherPriority.Render); - } - - private void CloseCurrentWindow(object sender, MouseButtonEventArgs e) - { - Close(); - } - - [NotifyPropertyChangedInvocator] - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + matchedPlugin.View(path, ViewerObject); } ~MainWindow() diff --git a/QuickLook/Plugin/IViewer.cs b/QuickLook/Plugin/IViewer.cs index 216a397..02869f1 100644 --- a/QuickLook/Plugin/IViewer.cs +++ b/QuickLook/Plugin/IViewer.cs @@ -1,11 +1,40 @@ namespace QuickLook.Plugin { + /// + /// Interface implemented by every QuickLook.Plugin + /// public interface IViewer { + /// + /// Set the priority of this plugin. A plugin with a higher priority may override one with lower priority. + /// Set this to int.MaxValue for a maximum priority, int.MinValue for minimum. + /// int Priority { get; } + + /// + /// Determine whether this plugin can open this file. Please also check the file header, if applicable. + /// + /// The full path of the target file. bool CanHandle(string path); - void Prepare(string path, ViewContentContainer container); - void View(string path, ViewContentContainer container); + + /// + /// Tell QuickLook the desired window size. Please not do any work that costs a lot of time. + /// + /// The full path of the target file. + /// A runtime object which allows interaction between this plugin and QuickLook. + void BoundViewSize(string path, ViewerObject context); + + /// + /// Start the loading process. During the process a busy indicator will be shown. Finish by setting context.IsBusy to + /// false. + /// + /// The full path of the target file. + /// A runtime object which allows interaction between this plugin and QuickLook. + void View(string path, ViewerObject context); + + /// + /// Release any unmanaged resource here. + /// void Dispose(); } } \ No newline at end of file diff --git a/QuickLook/Plugin/InfoPanel/PluginInterface.cs b/QuickLook/Plugin/InfoPanel/PluginInterface.cs index 1fa3cbf..2253d8f 100644 --- a/QuickLook/Plugin/InfoPanel/PluginInterface.cs +++ b/QuickLook/Plugin/InfoPanel/PluginInterface.cs @@ -14,19 +14,21 @@ namespace QuickLook.Plugin.InfoPanel return true; } - public void Prepare(string path, ViewContentContainer container) + public void BoundViewSize(string path, ViewerObject context) { _ip = new InfoPanel(); - container.CanResize = false; - container.PreferedSize = new Size {Width = _ip.Width, Height = _ip.Height}; + context.CanResize = false; + context.PreferredSize = new Size {Width = _ip.Width, Height = _ip.Height}; } - public void View(string path, ViewContentContainer container) + public void View(string path, ViewerObject context) { _ip.DisplayInfo(path); - container.SetContent(_ip); + context.ViewerContent = _ip; + + context.IsBusy = false; } public void Dispose() diff --git a/QuickLook/Plugin/ViewContentContainer.xaml.cs b/QuickLook/Plugin/ViewContentContainer.xaml.cs deleted file mode 100644 index d260677..0000000 --- a/QuickLook/Plugin/ViewContentContainer.xaml.cs +++ /dev/null @@ -1,87 +0,0 @@ -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, INotifyPropertyChanged, IDisposable - { - private string _title = string.Empty; - - public ViewContentContainer() - { - InitializeComponent(); - } - - public string Title - { - set - { - _title = value; - OnPropertyChanged(nameof(Title)); - } - get => _title; - } - - public IViewer ViewerPlugin { get; set; } - - public Size PreferedSize { get; set; } - - public bool CanResize { get; set; } = true; - - public void Dispose() - { - GC.SuppressFinalize(this); - - ViewerPlugin?.Dispose(); - } - - public event PropertyChangedEventHandler PropertyChanged; - - public void SetContent(object 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); - - if (ratio > 1) ratio = 1; - - PreferedSize = new Size {Width = size.Width * ratio, Height = size.Height * ratio}; - - return ratio; - } - - public Size GetMaximumDisplayBound() - { - return new Size(SystemParameters.VirtualScreenWidth, SystemParameters.VirtualScreenHeight); - } - - [NotifyPropertyChangedInvocator] - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - ~ViewContentContainer() - { - Dispose(); - } - } -} \ No newline at end of file diff --git a/QuickLook/Plugin/ViewerObject.cs b/QuickLook/Plugin/ViewerObject.cs new file mode 100644 index 0000000..211d36f --- /dev/null +++ b/QuickLook/Plugin/ViewerObject.cs @@ -0,0 +1,119 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows; +using QuickLook.Annotations; + +namespace QuickLook.Plugin +{ + /// + /// A runtime object which allows interaction between this plugin and QuickLook. + /// + public class ViewerObject : INotifyPropertyChanged, IDisposable + { + private bool _isBusy = true; + + private string _title = ""; + internal ViewContentContainer CurrentContentContainer; + internal IViewer ViewerPlugin; + + /// + /// Get or set the title of Viewer window. + /// + public string Title + { + get => _title; + set + { + _title = value; + OnPropertyChanged(); + } + } + + /// + /// Get or set the viewer content control. + /// + public object ViewerContent + { + get => CurrentContentContainer.container.Content; + set => CurrentContentContainer.container.Content = value; + } + + /// + /// Show or hide the busy indicator icon. + /// + public bool IsBusy + { + get => _isBusy; + set + { + _isBusy = value; + OnPropertyChanged(); + } + } + + /// + /// Set the exact size you want. + /// + public Size PreferredSize { get; set; } = new Size {Width = 800, Height = 600}; + + /// + /// Set whether user are allowed to resize the viewer window. + /// + public bool CanResize { get; set; } = true; + + public void Dispose() + { + GC.SuppressFinalize(this); + + ViewerPlugin?.Dispose(); + ViewerPlugin = null; + } + + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Set the size of viewer window and shrink to fit (to screen resolution). + /// The window can take maximum (maxRatio*resolution) space. + /// + /// The desired size. + /// The maximum percent (over screen resolution) it can take. + public double SetPreferredSizeFit(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); + + if (ratio > 1) ratio = 1; + + PreferredSize = new Size {Width = size.Width * ratio, Height = size.Height * ratio}; + + return ratio; + } + + /// + /// Get the device-independent resolution. + /// + public Size GetMaximumDisplayBound() + { + return new Size(SystemParameters.VirtualScreenWidth, SystemParameters.VirtualScreenHeight); + } + + [NotifyPropertyChangedInvocator] + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + ~ViewerObject() + { + Dispose(); + } + } +} \ No newline at end of file diff --git a/QuickLook/QuickLook.csproj b/QuickLook/QuickLook.csproj index 62ccb2e..5cfab23 100644 --- a/QuickLook/QuickLook.csproj +++ b/QuickLook/QuickLook.csproj @@ -75,6 +75,9 @@ MSBuild:Compile Designer + + + @@ -85,10 +88,12 @@ - + + ViewContentContainer.xaml - + + MSBuild:Compile @@ -111,16 +116,20 @@ - + MainWindow.xaml Code - + Designer MSBuild:Compile + + MSBuild:Compile + Designer + diff --git a/QuickLook/Styles/BusyDecorator.xaml b/QuickLook/Styles/BusyDecorator.xaml new file mode 100644 index 0000000..d2a1a20 --- /dev/null +++ b/QuickLook/Styles/BusyDecorator.xaml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/QuickLook/Plugin/ViewContentContainer.xaml b/QuickLook/ViewContentContainer.xaml similarity index 91% rename from QuickLook/Plugin/ViewContentContainer.xaml rename to QuickLook/ViewContentContainer.xaml index c419cd0..83e09e7 100644 --- a/QuickLook/Plugin/ViewContentContainer.xaml +++ b/QuickLook/ViewContentContainer.xaml @@ -1,4 +1,4 @@ - + /// Interaction logic for ViewContentContainer.xaml + /// + public partial class ViewContentContainer : UserControl + { + public ViewContentContainer() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/QuickLook/ViewWindowManager.cs b/QuickLook/ViewWindowManager.cs index eea6064..9c6f4de 100644 --- a/QuickLook/ViewWindowManager.cs +++ b/QuickLook/ViewWindowManager.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using QuickLook.ExtensionMethods; +using QuickLook.Helpers; using QuickLook.Plugin; -using QuickLook.Utilities; namespace QuickLook {