Async busy indicator; Change plugin interface, and more

This commit is contained in:
Paddy Xu
2017-04-30 22:52:54 +03:00
parent 0b0d9deccb
commit 73a2dafabf
23 changed files with 836 additions and 230 deletions

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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;
};
}

View File

@@ -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()

View File

@@ -8,6 +8,7 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles/ScrollBarStyleDictionary.xaml" />
<ResourceDictionary Source="Styles/BusyDecorator.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>

View File

@@ -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
/// <summary>
/// Identifies the IsContentShowing dependency property.
/// </summary>
public static readonly DependencyProperty IsContentShowingProperty = DependencyProperty.Register(
"IsContentShowing",
typeof(bool),
typeof(BackgroundVisualHost),
new FrameworkPropertyMetadata(false, OnIsContentShowingChanged));
/// <summary>
/// Gets or sets if the content is being displayed.
/// </summary>
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
/// <summary>
/// Identifies the CreateContent dependency property.
/// </summary>
public static readonly DependencyProperty CreateContentProperty = DependencyProperty.Register(
"CreateContent",
typeof(CreateContentFunction),
typeof(BackgroundVisualHost),
new FrameworkPropertyMetadata(OnCreateContentChanged));
/// <summary>
/// Gets or sets the function used to create the visual to display in a background thread.
/// </summary>
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
}
}

View File

@@ -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
/// <summary>
/// Identifies the IsBusyIndicatorShowing dependency property.
/// </summary>
public static readonly DependencyProperty IsBusyIndicatorShowingProperty = DependencyProperty.Register(
"IsBusyIndicatorShowing",
typeof(bool),
typeof(BusyDecorator),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Gets or sets if the BusyIndicator is being shown.
/// </summary>
public bool IsBusyIndicatorShowing
{
get => (bool) GetValue(IsBusyIndicatorShowingProperty);
set => SetValue(IsBusyIndicatorShowingProperty, value);
}
#endregion
#region BusyStyle
/// <summary>
/// Identifies the <see cref="BusyStyle" /> property.
/// </summary>
public static readonly DependencyProperty BusyStyleProperty =
DependencyProperty.Register(
"BusyStyle",
typeof(Style),
typeof(BusyDecorator),
new FrameworkPropertyMetadata(OnBusyStyleChanged));
/// <summary>
/// Gets or sets the Style to apply to the Control that is displayed as the busy indication.
/// </summary>
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
/// <summary>
/// Identifies the <see cref="BusyHorizontalAlignment" /> property.
/// </summary>
public static readonly DependencyProperty BusyHorizontalAlignmentProperty = DependencyProperty.Register(
"BusyHorizontalAlignment",
typeof(HorizontalAlignment),
typeof(BusyDecorator),
new FrameworkPropertyMetadata(HorizontalAlignment.Center));
/// <summary>
/// Gets or sets the HorizontalAlignment to use to layout the control that contains the busy indicator control.
/// </summary>
public HorizontalAlignment BusyHorizontalAlignment
{
get => (HorizontalAlignment) GetValue(BusyHorizontalAlignmentProperty);
set => SetValue(BusyHorizontalAlignmentProperty, value);
}
#endregion
#region BusyVerticalAlignment
/// <summary>
/// Identifies the <see cref="BusyVerticalAlignment" /> property.
/// </summary>
public static readonly DependencyProperty BusyVerticalAlignmentProperty = DependencyProperty.Register(
"BusyVerticalAlignment",
typeof(VerticalAlignment),
typeof(BusyDecorator),
new FrameworkPropertyMetadata(VerticalAlignment.Center));
/// <summary>
/// Gets or sets the the VerticalAlignment to use to layout the control that contains the busy indicator.
/// </summary>
public VerticalAlignment BusyVerticalAlignment
{
get => (VerticalAlignment) GetValue(BusyVerticalAlignmentProperty);
set => SetValue(BusyVerticalAlignmentProperty, value);
}
#endregion
}
}

View File

@@ -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;
}
}
}

View File

@@ -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)
{

View File

@@ -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
{
/// <summary>
/// Logical pixels inch in X
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Logical pixels inch in Y
/// </summary>
LOGPIXELSY = 90
}
public struct Dpi
{
public float HorizontalDpi;
public float VerticalDpi;
}
}
}

View File

@@ -3,7 +3,7 @@ using System.Text;
using System.Windows.Interop;
using QuickLook.NativeMethods;
namespace QuickLook.Utilities
namespace QuickLook.Helpers
{
internal static class WindowHelper
{

View File

@@ -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">
<Window.Background>
<SolidColorBrush Color="#DFFFFFFF" />
<SolidColorBrush Color="#E5FAFAFA" />
</Window.Background>
<Border x:Name="windowBorder" BorderThickness="1" BorderBrush="#FF7B7B7B">
<Grid>
<DockPanel Opacity="1">
<DockPanel x:Name="titlebar" Height="28" Dock="Top">
<DockPanel.Background>
<SolidColorBrush Color="#00B8B8B8" />
</DockPanel.Background>
<!--<DockPanel.Style>
<DockPanel x:Name="windowPanel" Opacity="1">
<DockPanel.Style>
<Style TargetType="{x:Type DockPanel}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<DataTrigger Binding="{Binding ViewerObject.IsBusy, ElementName=mainWindow}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.1" Storyboard.TargetProperty="Opacity"
To="1" />
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0"
To="1"
BeginTime="0:0:0.1"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.6" Storyboard.TargetProperty="Opacity"
To="0" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</DockPanel.Style>-->
<fa:ImageAwesome DockPanel.Dock="Right" x:Name="buttonCloseWindow" Icon="WindowClose" Height="15"
Margin="10,0"
</DockPanel.Style>
<DockPanel x:Name="titlebar" Height="28" Dock="Top">
<fa:ImageAwesome DockPanel.Dock="Right" x:Name="buttonCloseWindow" Icon="TimesCircle"
Height="15" Margin="10,0" Foreground="Gray"
Cursor="Hand" />
<Label x:Name="titlebarTitleArea" Content="{Binding Title, ElementName=viewContentContainer}"
<Label x:Name="titlebarTitleArea" Content="{Binding ViewerObject.Title, ElementName=mainWindow}"
FontSize="14" HorizontalContentAlignment="Center"
VerticalContentAlignment="Center" />
</DockPanel>
<Grid>
<plugin:ViewContentContainer x:Name="viewContentContainer" />
<local:ViewContentContainer x:Name="viewContentContainer" />
</Grid>
</DockPanel>
<Grid x:Name="loadingIconLayer" Opacity="0.3">
<Grid.Background>
<SolidColorBrush Color="#FFFFFFFF" />
</Grid.Background>
<Grid x:Name="loadingIcon" Height="50" Width="50" HorizontalAlignment="Center"
VerticalAlignment="Center">
<fa:ImageAwesome Icon="CircleOutlineNotch" Spin="True" SpinDuration="1" />
</Grid>
<Grid x:Name="busyIndicatorLayer" Visibility="Hidden">
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewerObject.IsBusy, ElementName=mainWindow}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ParallelTimeline>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="1"
To="0"
BeginTime="0:0:0.1"
Duration="0:0:0.2" />
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0.4"
Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</ParallelTimeline>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<control:BusyDecorator IsBusyIndicatorShowing="True" VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Grid>
</Grid>
</Border>

View File

@@ -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
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
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()

View File

@@ -1,11 +1,40 @@
namespace QuickLook.Plugin
{
/// <summary>
/// Interface implemented by every QuickLook.Plugin
/// </summary>
public interface IViewer
{
/// <summary>
/// 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.
/// </summary>
int Priority { get; }
/// <summary>
/// Determine whether this plugin can open this file. Please also check the file header, if applicable.
/// </summary>
/// <param name="path">The full path of the target file.</param>
bool CanHandle(string path);
void Prepare(string path, ViewContentContainer container);
void View(string path, ViewContentContainer container);
/// <summary>
/// Tell QuickLook the desired window size. Please not do any work that costs a lot of time.
/// </summary>
/// <param name="path">The full path of the target file.</param>
/// <param name="context">A runtime object which allows interaction between this plugin and QuickLook.</param>
void BoundViewSize(string path, ViewerObject context);
/// <summary>
/// Start the loading process. During the process a busy indicator will be shown. Finish by setting context.IsBusy to
/// false.
/// </summary>
/// <param name="path">The full path of the target file.</param>
/// <param name="context">A runtime object which allows interaction between this plugin and QuickLook.</param>
void View(string path, ViewerObject context);
/// <summary>
/// Release any unmanaged resource here.
/// </summary>
void Dispose();
}
}

View File

@@ -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()

View File

@@ -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
{
/// <summary>
/// Interaction logic for ViewContentContainer.xaml
/// </summary>
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();
}
}
}

View File

@@ -0,0 +1,119 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using QuickLook.Annotations;
namespace QuickLook.Plugin
{
/// <summary>
/// A runtime object which allows interaction between this plugin and QuickLook.
/// </summary>
public class ViewerObject : INotifyPropertyChanged, IDisposable
{
private bool _isBusy = true;
private string _title = "";
internal ViewContentContainer CurrentContentContainer;
internal IViewer ViewerPlugin;
/// <summary>
/// Get or set the title of Viewer window.
/// </summary>
public string Title
{
get => _title;
set
{
_title = value;
OnPropertyChanged();
}
}
/// <summary>
/// Get or set the viewer content control.
/// </summary>
public object ViewerContent
{
get => CurrentContentContainer.container.Content;
set => CurrentContentContainer.container.Content = value;
}
/// <summary>
/// Show or hide the busy indicator icon.
/// </summary>
public bool IsBusy
{
get => _isBusy;
set
{
_isBusy = value;
OnPropertyChanged();
}
}
/// <summary>
/// Set the exact size you want.
/// </summary>
public Size PreferredSize { get; set; } = new Size {Width = 800, Height = 600};
/// <summary>
/// Set whether user are allowed to resize the viewer window.
/// </summary>
public bool CanResize { get; set; } = true;
public void Dispose()
{
GC.SuppressFinalize(this);
ViewerPlugin?.Dispose();
ViewerPlugin = null;
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Set the size of viewer window and shrink to fit (to screen resolution).
/// The window can take maximum (maxRatio*resolution) space.
/// </summary>
/// <param name="size">The desired size.</param>
/// <param name="maxRatio">The maximum percent (over screen resolution) it can take.</param>
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;
}
/// <summary>
/// Get the device-independent resolution.
/// </summary>
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();
}
}
}

View File

@@ -75,6 +75,9 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Controls\BackgroundVisualHost.cs" />
<Compile Include="Controls\BusyDecorator.cs" />
<Compile Include="Controls\VisualTargetPresentationSource.cs" />
<Compile Include="ExtensionMethods\TypeExtensions.cs" />
<Compile Include="PluginManager.cs" />
<Compile Include="Plugin\InfoPanel\Extensions.cs" />
@@ -85,10 +88,12 @@
<Compile Include="Plugin\InfoPanel\PluginInterface.cs" />
<Compile Include="Plugin\InfoPanel\WindowsThumbnailProvider.cs" />
<Compile Include="Plugin\IViewer.cs" />
<Compile Include="Plugin\ViewContentContainer.xaml.cs">
<Compile Include="Helpers\DpiHelpers.cs" />
<Compile Include="ViewContentContainer.xaml.cs">
<DependentUpon>ViewContentContainer.xaml</DependentUpon>
</Compile>
<Compile Include="Utilities\WindowHelper.cs" />
<Compile Include="Plugin\ViewerObject.cs" />
<Compile Include="Helpers\WindowHelper.cs" />
<Compile Include="ViewWindowManager.cs" />
<Page Include="Plugin\InfoPanel\InfoPanel.xaml">
<Generator>MSBuild:Compile</Generator>
@@ -111,16 +116,20 @@
<Compile Include="NativeMethods\Kernel32.cs" />
<Compile Include="NativeMethods\User32.cs" />
<Compile Include="Properties\Annotations.cs" />
<Compile Include="Utilities\AeroGlass.cs" />
<Compile Include="Helpers\AeroGlassHelper.cs" />
<Compile Include="GlobalKeyboardHook.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="Plugin\ViewContentContainer.xaml">
<Page Include="ViewContentContainer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Styles\BusyDecorator.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">

View File

@@ -0,0 +1,23 @@
<ResourceDictionary
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:local="clr-namespace:QuickLook"
xmlns:controls="clr-namespace:QuickLook.Controls">
<Style TargetType="{x:Type controls:BusyDecorator}">
<Setter Property="BusyStyle">
<Setter.Value>
<Style TargetType="{x:Type Control}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<fa:ImageAwesome Width="40" Height="40" Icon="Refresh" Spin="True" SpinDuration="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -1,4 +1,4 @@
<UserControl x:Class="QuickLook.Plugin.ViewContentContainer"
<UserControl x:Class="QuickLook.ViewContentContainer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace QuickLook
{
/// <summary>
/// Interaction logic for ViewContentContainer.xaml
/// </summary>
public partial class ViewContentContainer : UserControl
{
public ViewContentContainer()
{
InitializeComponent();
}
}
}

View File

@@ -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
{