diff --git a/OPTIONS.md b/OPTIONS.md
index a5fd6b0cb..1b4a8cbeb 100644
--- a/OPTIONS.md
+++ b/OPTIONS.md
@@ -74,6 +74,13 @@ Example:
- Example:
- `True`
+### ``
+- Default: `False`
+- Type: `Boolean`
+- Description: Automatically hide the preview window's toolbar after the pointer leaves it.
+- Example:
+ - `True`
+
### ``
- Default: `True`
- Type: `Boolean`
diff --git a/QuickLook.Common/Styles/MainWindowStyles.Dark.xaml b/QuickLook.Common/Styles/MainWindowStyles.Dark.xaml
index ccc6054ed..9add43c03 100644
--- a/QuickLook.Common/Styles/MainWindowStyles.Dark.xaml
+++ b/QuickLook.Common/Styles/MainWindowStyles.Dark.xaml
@@ -10,5 +10,5 @@
-
-
\ No newline at end of file
+
+
diff --git a/QuickLook.Common/Styles/MainWindowStyles.xaml b/QuickLook.Common/Styles/MainWindowStyles.xaml
index deee5d596..a1357dfd5 100644
--- a/QuickLook.Common/Styles/MainWindowStyles.xaml
+++ b/QuickLook.Common/Styles/MainWindowStyles.xaml
@@ -15,7 +15,7 @@
-
+
-
\ No newline at end of file
+
diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml
index e177a8509..a0f550962 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml
+++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml
@@ -102,6 +102,7 @@
Margin="0,0,8,0"
Content=""
Style="{StaticResource CaptionButtonStyle}"
+ ToolTip="{Binding CopyTooltip, ElementName=imagePanel}"
Visibility="{Binding ElementName=imagePanel, Path=CopyIconVisibility}" />
+ ToolTipService.InitialShowDelay="0"
+ Visibility="{Binding ElementName=imagePanel, Path=MetaIconVisibility}">
+
+
+
+
+
+
-
-
- Camera maker
- :
- SONY
-
-
-
-
-
\ No newline at end of file
diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml.cs
index 5c9654925..e33f58cd4 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/ImagePanel.xaml.cs
@@ -28,6 +28,7 @@
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -66,6 +67,9 @@ public partial class ImagePanel : UserControl, INotifyPropertyChanged, IDisposab
private Visibility _reverseColorVisibility = Visibility.Collapsed;
private Visibility _metaIconVisibility = Visibility.Visible;
+ public string CopyTooltip { get; } = GetTranslation("IV_Copy");
+ public string BackgroundTooltip { get; } = GetTranslation("IV_Background");
+
public ImagePanel()
{
InitializeComponent();
@@ -78,11 +82,6 @@ public ImagePanel()
buttonReverseColor.Click += OnReverseColorOnClick;
- buttonMeta.Click += (sender, e) =>
- textMeta.Visibility = textMeta.Visibility == Visibility.Collapsed
- ? Visibility.Visible
- : Visibility.Collapsed;
-
buttonBackgroundColour.Click += OnBackgroundColourOnClick;
SizeChanged += ImagePanel_SizeChanged;
@@ -111,6 +110,15 @@ public ImagePanel()
viewPanel.ManipulationDelta += ViewPanel_ManipulationDelta;
}
+ private static string GetTranslation(string key)
+ {
+ var translationFile = Path.Combine(
+ Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
+ "Translations.config");
+
+ return TranslationHelper.Get(key, translationFile);
+ }
+
internal ImagePanel(ContextObject context, MetaProvider meta) : this()
{
ContextObject = context;
@@ -401,19 +409,28 @@ private void OnBackgroundColourOnClick(object sender, RoutedEventArgs e)
private void ShowMeta()
{
- textMeta.Inlines.Clear();
- Meta.GetExif().Values.ForEach(m =>
+ textMetaContent.Inlines.Clear();
+ Meta.GetExif()
+ .OrderBy(item => item.Key switch
+ {
+ "_.Size.Width" => 0,
+ "_.Size.Height" => 1,
+ _ => 2,
+ })
+ .Select(item => item.Value)
+ .ForEach(m =>
{
if (string.IsNullOrWhiteSpace(m.Item1) || string.IsNullOrWhiteSpace(m.Item2))
return;
- textMeta.Inlines.Add(new Run(m.Item1) { FontWeight = FontWeights.SemiBold });
- textMeta.Inlines.Add(": ");
- textMeta.Inlines.Add(m.Item2);
- textMeta.Inlines.Add("\r\n");
+ textMetaContent.Inlines.Add(new Run(m.Item1) { FontWeight = FontWeights.SemiBold });
+ textMetaContent.Inlines.Add(": ");
+ textMetaContent.Inlines.Add(m.Item2);
+ textMetaContent.Inlines.Add("\r\n");
});
- textMeta.Inlines.Remove(textMeta.Inlines.LastInline);
- if (!textMeta.Inlines.Any())
+ if (textMetaContent.Inlines.LastInline != null)
+ textMetaContent.Inlines.Remove(textMetaContent.Inlines.LastInline);
+ if (!textMetaContent.Inlines.Any())
MetaIconVisibility = Visibility.Collapsed;
}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Translations.config b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Translations.config
index 1a96fa15a..9adf5c3da 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Translations.config
+++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Translations.config
@@ -3,6 +3,9 @@
Torna a obrir com a codi font
+ Copiar la imatge
+ Mostrar les metadades
+ Canvia el fons
Servei PlantUML en línia
La previsualització d'aquest fitxer requereix enviar el seu contingut a un servidor PlantUML remot (plantuml.com) per al renderitzat.
@@ -11,6 +14,9 @@ Voleu permetre l'accés a la xarxa?
פתח מחדש כקוד מקור
+ העתק תמונה
+ הצג מטא-נתונים
+ החלף רקע
שירות PlantUML מקוון
תצוגה מקדימה של קובץ זה מחייבת שליחת תוכנו לשרת PlantUML מרוחק (plantuml.com) לעיבוד.
@@ -19,6 +25,9 @@ Voleu permetre l'accés a la xarxa?
सोर्स कोड के रूप में पुनः खोलें
+ छवि कॉपी करें
+ मेटाडेटा दिखाएँ
+ पृष्ठभूमि बदलें
PlantUML ऑनलाइन सेवा
इस फ़ाइल का पूर्वावलोकन करने के लिए इसकी सामग्री को रेंडरिंग के लिए एक दूरस्थ PlantUML सर्वर (plantuml.com) पर भेजना आवश्यक है।
@@ -27,6 +36,9 @@ Voleu permetre l'accés a la xarxa?
Újranyitás forráskódként
+ Kép másolása
+ Metaadatok megjelenítése
+ Háttér váltása
PlantUML online szolgáltatás
Ennek a fájlnak az előnézete megköveteli a tartalmának elküldését egy távoli PlantUML kiszolgálóra (plantuml.com) renderelés céljából.
@@ -35,6 +47,9 @@ Engedélyezi a hálózati hozzáférést?
Buka ulang sebagai kode sumber
+ Salin gambar
+ Tampilkan metadata
+ Beralih latar belakang
Layanan PlantUML Online
Pratinjau file ini memerlukan pengiriman kontennya ke server PlantUML jarak jauh (plantuml.com) untuk dirender.
@@ -43,6 +58,9 @@ Izinkan akses jaringan?
स्रोत कोड म्हणून पुन्हा उघडा
+ प्रतिमा कॉपी करा
+ मेटाडेटा दाखवा
+ पार्श्वभूमी बदला
PlantUML ऑनलाइन सेवा
या फाइलचे पूर्वावलोकन करण्यासाठी तिची सामग्री रेंडरिंगसाठी दूरस्थ PlantUML सर्व्हरवर (plantuml.com) पाठवणे आवश्यक आहे.
@@ -51,6 +69,9 @@ Izinkan akses jaringan?
Åpne på nytt som kildekode
+ Kopier bilde
+ Vis metadata
+ Bytt bakgrunn
PlantUML nettjeneste
Forhåndsvisning av denne filen krever at innholdet sendes til en ekstern PlantUML-server (plantuml.com) for gjengivelse.
@@ -59,6 +80,9 @@ Tillate nettverkstilgang?
Opnieuw openen als broncode
+ Afbeelding kopiëren
+ Metagegevens tonen
+ Achtergrond wisselen
PlantUML online service
Voor het bekijken van dit bestand moet de inhoud worden verzonden naar een externe PlantUML-server (plantuml.com) voor weergave.
@@ -67,6 +91,9 @@ Netwerktogang toestaan?
Reabrir como código-fonte
+ Copiar imagem
+ Mostrar metadados
+ Alternar plano de fundo
Serviço PlantUML Online
A visualização deste arquivo requer o envio do seu conteúdo para um servidor PlantUML remoto (plantuml.com) para renderização.
@@ -75,6 +102,9 @@ Permitir acesso à rede?
Reabrir como código-fonte
+ Copiar imagem
+ Mostrar metadados
+ Alternar fundo
Serviço PlantUML Online
A pré-visualização deste ficheiro requer o envio do seu conteúdo para um servidor PlantUML remoto (plantuml.com) para renderização.
@@ -83,6 +113,9 @@ Permitir acesso à rede?
Открыть заново как исходный код
+ Копировать изображение
+ Показать метаданные
+ Изменить фон
Онлайн-сервис PlantUML
Предварительный просмотр этого файла требует отправки его содержимого на удалённый сервер PlantUML (plantuml.com) для рендеринга.
@@ -91,6 +124,9 @@ Permitir acesso à rede?
Znovu otvoriť ako zdrojový kód
+ Kopírovať obrázok
+ Zobraziť metadáta
+ Prepnúť pozadie
Online služba PlantUML
Náhľad tohto súboru vyžaduje odoslanie jeho obsahu na vzdialený server PlantUML (plantuml.com) na vykresľovanie.
@@ -99,6 +135,9 @@ Povoliť prístup k sieti?
Kaynak kod olarak yeniden aç
+ Görüntüyü kopyala
+ Üst verileri göster
+ Arka planı değiştir
PlantUML Çevrimiçi Hizmeti
Bu dosyanın önizlemesi, içeriğinin işlenmek üzere uzak bir PlantUML sunucusuna (plantuml.com) gönderilmesini gerektirir.
@@ -107,6 +146,9 @@ Ağ erişimine izin verilsin mi?
Mở lại dưới dạng mã nguồn
+ Sao chép hình ảnh
+ Hiện siêu dữ liệu
+ Chuyển đổi nền
Dịch vụ PlantUML trực tuyến
Xem trước tệp này yêu cầu gửi nội dung của nó đến máy chủ PlantUML từ xa (plantuml.com) để kết xuất.
@@ -115,6 +157,9 @@ Cho phép truy cập mạng?
Reopen as source code
+ Copy image
+ Show metadata
+ Toggle background
PlantUML Online Service
Previewing this file requires sending its content to a remote PlantUML server (plantuml.com) for rendering.
@@ -123,6 +168,9 @@ Allow network access?
소스 코드로 다시 열기
+ 이미지 복사
+ 메타데이터 표시
+ 배경 전환
PlantUML 온라인 서비스
이 파일을 미리 보려면 내용을 원격 PlantUML 서버 (plantuml.com)로 보내 렌더링해야 합니다.
@@ -131,6 +179,9 @@ Allow network access?
Als Quellcode erneut öffnen
+ Bild kopieren
+ Metadaten anzeigen
+ Hintergrund wechseln
PlantUML Online-Dienst
Für die Vorschau dieser Datei muss ihr Inhalt zur Darstellung an einen entfernten PlantUML-Server (plantuml.com) gesendet werden.
@@ -139,6 +190,9 @@ Netzwerkzugriff erlauben?
Rouvrir en tant que code source
+ Copier l’image
+ Afficher les métadonnées
+ Changer l’arrière-plan
Service PlantUML en ligne
L'aperçu de ce fichier nécessite l'envoi de son contenu à un serveur PlantUML distant (plantuml.com) pour le rendu.
@@ -147,6 +201,9 @@ Autoriser l'accès au réseau ?
ソースコードとして再度開く
+ 画像をコピー
+ メタデータを表示
+ 背景を切り替え
PlantUML オンラインサービス
このファイルをプレビューするには、内容をリモートの PlantUML サーバー (plantuml.com) に送信してレンダリングする必要があります。
@@ -155,6 +212,9 @@ Autoriser l'accès au réseau ?
以源代码方式重新打开
+ 复制图像
+ 显示元数据
+ 切换背景
PlantUML 在线服务
预览此文件需要将内容发送到远程 PlantUML 服务器 (plantuml.com) 进行渲染。
@@ -163,6 +223,9 @@ Autoriser l'accès au réseau ?
以原始碼方式重新開啟
+ 複製影像
+ 顯示中繼資料
+ 切換背景
PlantUML 線上服務
預覽此檔案需要將內容傳送至遠端 PlantUML 伺服器 (plantuml.com) 進行渲染。
@@ -171,6 +234,9 @@ Autoriser l'accès au réseau ?
Reabrir como código fuente
+ Copiar imagen
+ Mostrar metadatos
+ Cambiar fondo
Servicio PlantUML en línea
La vista previa de este archivo requiere enviar su contenido a un servidor PlantUML remoto (plantuml.com) para su procesamiento.
@@ -179,6 +245,9 @@ Autoriser l'accès au réseau ?
Riapri come codice sorgente
+ Copia immagine
+ Mostra metadati
+ Cambia sfondo
Servizio PlantUML online
L'anteprima di questo file richiede l'invio del suo contenuto a un server PlantUML remoto (plantuml.com) per il rendering.
@@ -187,6 +256,9 @@ Consentire l'accesso alla rete?
إعادة الفتح كرمز مصدر
+ نسخ الصورة
+ إظهار البيانات الوصفية
+ تبديل الخلفية
خدمة PlantUML عبر الإنترنت
يتطلب معاينة هذا الملف إرسال محتواه إلى خادم PlantUML البعيد (plantuml.com) للعرض.
@@ -195,6 +267,9 @@ Consentire l'accesso alla rete?
Otwórz ponownie jako kod źródłowy
+ Kopiuj obraz
+ Pokaż metadane
+ Przełącz tło
Usługa PlantUML online
Podgląd tego pliku wymaga wysłania jego zawartości do zdalnego serwera PlantUML (plantuml.com) w celu renderowania.
@@ -203,6 +278,9 @@ Czy zezwolić na dostęp do sieci?
Επαναάνοιγμα ως πηγαίος κώδικας
+ Αντιγραφή εικόνας
+ Εμφάνιση μεταδεδομένων
+ Εναλλαγή φόντου
Διαδικτυακή υπηρεσία PlantUML
Η προεπισκόπηση αυτού του αρχείου απαιτεί την αποστολή του περιεχομένου του σε έναν απομακρυσμένο διακομιστή PlantUML (plantuml.com) για απόδοση.
@@ -211,6 +289,9 @@ Czy zezwolić na dostęp do sieci?
Відкрити знову як вихідний код
+ Копіювати зображення
+ Показати метадані
+ Змінити тло
Онлайн-сервіс PlantUML
Попередній перегляд цього файлу вимагає надсилання його вмісту на віддалений сервер PlantUML (plantuml.com) для рендерингу.
@@ -219,6 +300,9 @@ Czy zezwolić na dostęp do sieci?
Öppna igen som källkod
+ Kopiera bild
+ Visa metadata
+ Byt bakgrund
PlantUML onlinetjänst
Förhandsgranskning av den här filen kräver att dess innehåll skickas till en fjärr-PlantUML-server (plantuml.com) för renderering.
@@ -227,6 +311,9 @@ Tillåt nätverksåtkomst?
Redeschideți ca cod sursă
+ Copiază imaginea
+ Arată metadatele
+ Comută fundalul
Serviciul online PlantUML
Previzualizarea acestui fișier necesită trimiterea conținutului său la un server PlantUML la distanță (plantuml.com) pentru randare.
diff --git a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/AsyncPageToThumbnailConverter.cs b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/AsyncPageToThumbnailConverter.cs
index dec92f40f..b0da285aa 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/AsyncPageToThumbnailConverter.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/AsyncPageToThumbnailConverter.cs
@@ -18,7 +18,6 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
-using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media.Imaging;
@@ -31,7 +30,7 @@ internal class AsyncPageToThumbnailConverter : IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
- if (values.Length < 3)
+ if (values.Length < 2)
throw new Exception("PageIdToImageConverter");
if (values[0] is not PdfDocumentWrapper handle) return null;
@@ -39,17 +38,11 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
var pageId = (int)values[1];
if (pageId < 0) return null;
- if (values[2] is not UserControl thisPdfViewer) return null;
-
var task = Task.Run(() =>
{
try
{
- if (handle is null || thisPdfViewer is null) return Loading;
- return thisPdfViewer.Dispatcher.Invoke(() =>
- {
- return handle.RenderThumbnail(pageId);
- });
+ return handle.RenderThumbnail(pageId);
}
catch (Exception)
{
diff --git a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfDocumentWrapper.cs b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfDocumentWrapper.cs
index e1883d734..037f9da0a 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfDocumentWrapper.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfDocumentWrapper.cs
@@ -16,42 +16,85 @@
// along with this program. If not, see .
using System;
+using System.Drawing;
using System.IO;
+using System.Linq;
using PdfiumViewer;
namespace QuickLook.Plugin.PDFViewer;
public class PdfDocumentWrapper : IDisposable
{
+ private readonly string _path;
+ private readonly string _password;
+ private SizeF[] _pageSizes;
+ private bool _disposed;
+
+ public PdfDocumentWrapper(string path, string password = null)
+ {
+ _path = path;
+ _password = password;
+ PdfDocument = PdfDocument.Load(path, password);
+ _pageSizes = PdfDocument.PageSizes.ToArray();
+ PageCount = PdfDocument.PageCount;
+ }
+
public PdfDocumentWrapper(Stream stream, string password = null)
{
+ _password = password;
PdfStream = new MemoryStream((int)stream.Length);
stream.CopyTo(PdfStream);
+ PdfStream.Position = 0;
PdfDocument = PdfDocument.Load(PdfStream, password);
+ _pageSizes = PdfDocument.PageSizes.ToArray();
+ PageCount = PdfDocument.PageCount;
}
public PdfDocument PdfDocument { get; private set; }
public MemoryStream PdfStream { get; private set; }
+ public int PageCount { get; private set; }
+
+ public SizeF GetPageSize(int page) => _pageSizes[page];
+
public void Dispose()
{
- PdfDocument.Dispose();
- PdfDocument = null;
- PdfStream.Dispose();
- PdfStream = null;
+ lock (PdfPageExtension.RenderLock)
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+ PdfDocument?.Dispose();
+ PdfDocument = null;
+ PdfStream?.Dispose();
+ PdfStream = null;
+ }
}
public void Refresh()
{
- var oldD = PdfDocument;
+ lock (PdfPageExtension.RenderLock)
+ {
+ if (_disposed)
+ return;
- PdfStream.Position = 0;
- var newObj = new PdfDocumentWrapper(PdfStream);
- PdfDocument = newObj.PdfDocument;
- PdfStream = newObj.PdfStream;
+ if (PdfStream != null)
+ PdfStream.Position = 0;
+
+ var newDocument = _path != null
+ ? PdfDocument.Load(_path, _password)
+ : PdfDocument.Load(PdfStream, _password);
+ var newPageSizes = newDocument.PageSizes.ToArray();
+ var oldDocument = PdfDocument;
+
+ PdfDocument = newDocument;
+ PageCount = newDocument.PageCount;
+ _pageSizes = newPageSizes;
- oldD.Dispose();
+ oldDocument.Dispose();
+ }
}
}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfPageExtension.cs b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfPageExtension.cs
index 35ea04663..4db38de62 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfPageExtension.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfPageExtension.cs
@@ -27,11 +27,11 @@ namespace QuickLook.Plugin.PDFViewer;
internal static class PdfPageExtension
{
private static int _renderCount;
- private static readonly object LockObj = new();
+ internal static readonly object RenderLock = new();
public static BitmapSource RenderThumbnail(this PdfDocumentWrapper doc, int page)
{
- lock (LockObj)
+ lock (RenderLock)
{
if (_renderCount++ == 50)
{
@@ -40,7 +40,7 @@ public static BitmapSource RenderThumbnail(this PdfDocumentWrapper doc, int page
}
}
- var size = doc.PdfDocument.PageSizes[page];
+ var size = doc.GetPageSize(page);
var factorX = 60d / size.Width;
var factorY = 120d / size.Height;
@@ -55,9 +55,9 @@ public static BitmapSource Render(this PdfDocumentWrapper doc, int page, double
Bitmap bitmap;
- lock (LockObj)
+ lock (RenderLock)
{
- var size = doc.PdfDocument.PageSizes[page];
+ var size = doc.GetPageSize(page);
var realWidth = (int)Math.Round(size.Width * scale.Horizontal * factor);
var realHeight = (int)Math.Round(size.Height * scale.Vertical * factor);
diff --git a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml
index 59b41481f..aa55ec539 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml
+++ b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml
@@ -62,7 +62,6 @@
-
@@ -88,4 +87,4 @@
ZoomWithControlKey="True" />
-
\ No newline at end of file
+
diff --git a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml.cs b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml.cs
index f684547a9..0d4552773 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PdfViewerControl.xaml.cs
@@ -15,8 +15,6 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-using PdfiumViewer;
-using QuickLook.Common.ExtensionMethods;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
@@ -24,8 +22,10 @@
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Media.Imaging;
namespace QuickLook.Plugin.PDFViewer;
@@ -33,6 +33,7 @@ public partial class PdfViewerControl : UserControl, INotifyPropertyChanged, IDi
{
private int _changePageDeltaSum;
private bool _initPage = true;
+ private int _renderRequestId;
private double _maxZoomFactor = double.NaN;
private double _minZoomFactor = double.NaN;
@@ -52,9 +53,9 @@ public PdfViewerControl()
pagePanel.ImageScrolled += NavigatePage;
}
- public ObservableCollection PageThumbnails { get; set; } = [];
+ public ObservableCollection PageThumbnails { get; private set; } = [];
- public int TotalPages => PdfDocumentWrapper.PdfDocument.PageCount;
+ public int TotalPages => PdfDocumentWrapper.PageCount;
public int CurrentPage
{
@@ -81,6 +82,7 @@ public void Dispose()
pagePanel.ImageScrolled -= NavigatePage;
}
+ _renderRequestId++;
_pdfLoaded = false;
PdfDocumentWrapper?.Dispose();
PdfDocumentWrapper = null;
@@ -142,33 +144,37 @@ private void PrevPage()
}
}
- private void ReRenderCurrentPage()
+ private async void ReRenderCurrentPage()
{
if (!_pdfLoaded)
return;
- if (CurrentPage < 0 || CurrentPage >= TotalPages)
+ var page = CurrentPage;
+ var document = PdfDocumentWrapper;
+ if (document == null || page < 0 || page >= TotalPages)
return;
- Debug.WriteLine($"Re-rendering page {CurrentPage}");
+ Debug.WriteLine($"Re-rendering page {page}");
var pos = pagePanel.GetScrollPosition();
+ var requestId = ++_renderRequestId;
double factor;
+ var pageSize = document.GetPageSize(page);
// First time showing. Set thresholds here.
if (double.IsNaN(_minZoomFactor) || double.IsNaN(_maxZoomFactor))
{
- factor = Math.Min(pagePanel.ActualHeight / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Height,
- pagePanel.ActualWidth / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Width);
+ factor = Math.Min(pagePanel.ActualHeight / pageSize.Height,
+ pagePanel.ActualWidth / pageSize.Width);
_viewRenderFactor = factor;
_minZoomFactor = 0.1 * factor;
_maxZoomFactor = 5 * factor;
}
else if (pagePanel.ZoomToFit)
{
- factor = Math.Min(pagePanel.ActualHeight / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Height,
- pagePanel.ActualWidth / PdfDocumentWrapper.PdfDocument.PageSizes[CurrentPage].Width);
+ factor = Math.Min(pagePanel.ActualHeight / pageSize.Height,
+ pagePanel.ActualWidth / pageSize.Width);
}
else
{
@@ -179,7 +185,19 @@ private void ReRenderCurrentPage()
pagePanel.MaxZoomFactor = _maxZoomFactor / factor;
}
- var image = PdfDocumentWrapper.Render(CurrentPage, factor);
+ BitmapSource image;
+ try
+ {
+ image = await Task.Run(() => document.Render(page, factor));
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Failed to render PDF page {page}: {ex}");
+ return;
+ }
+
+ if (!_pdfLoaded || requestId != _renderRequestId || !ReferenceEquals(document, PdfDocumentWrapper))
+ return;
pagePanel.Source = image;
pagePanel.ResetZoom();
@@ -187,8 +205,6 @@ private void ReRenderCurrentPage()
_viewRenderFactor = factor;
pagePanel.SetScrollPosition(pos);
-
- Dispatcher.Delay(500, t => GC.Collect());
}
private void UpdatePageViewWhenSelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -210,56 +226,64 @@ private void UpdatePageViewWhenSelectionChanged(object sender, SelectionChangedE
}
}
- public static Size GetDesiredControlSizeByFirstPage(string path, string password = null)
+ public static Size GetDesiredControlSizeByFirstPage(PdfDocumentWrapper document)
{
- Size size;
-
- using (var s = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ var size = new Size(0, 0);
+ for (var page = 0; page < Math.Min(5, document.PageCount); page++)
{
- using (var tempHandle = PdfDocument.Load(s, password))
- {
- size = new Size(0, 0);
- tempHandle.PageSizes.Take(5).ForEach(p =>
- {
- size.Width = Math.Max(size.Width, p.Width);
- size.Height = Math.Max(size.Height, p.Height);
- });
-
- if (tempHandle.PageCount > 1)
- size.Width += /*listThumbnails.ActualWidth*/ 150;
- }
+ var pageSize = document.GetPageSize(page);
+ size.Width = Math.Max(size.Width, pageSize.Width);
+ size.Height = Math.Max(size.Height, pageSize.Height);
}
+ if (document.PageCount > 1)
+ size.Width += 150;
+
return new Size(size.Width * 3, size.Height * 3);
}
public void LoadPdf(string path, string password = null)
{
- var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
- PdfDocumentWrapper = new PdfDocumentWrapper(stream, password);
+ LoadPdf(new PdfDocumentWrapper(path, password));
+ }
+
+ public void LoadPdf(PdfDocumentWrapper document)
+ {
+ var previousDocument = PdfDocumentWrapper;
+ _pdfLoaded = false;
+ _renderRequestId++;
+ listThumbnails.SelectedIndex = -1;
+ if (previousDocument != null && !ReferenceEquals(previousDocument, document))
+ previousDocument.Dispose();
+
+ PdfDocumentWrapper = document;
_pdfLoaded = true;
+ _initPage = true;
+ _minZoomFactor = double.NaN;
+ _maxZoomFactor = double.NaN;
+ _viewRenderFactor = double.NaN;
- if (PdfDocumentWrapper.PdfDocument.PageCount < 2)
+ if (PdfDocumentWrapper.PageCount < 2)
listThumbnails.Visibility = Visibility.Collapsed;
+ else
+ listThumbnails.Visibility = Visibility.Visible;
BeginLoadThumbnails();
+
+ if (PdfDocumentWrapper.PageCount > 0)
+ listThumbnails.SelectedIndex = 0;
}
public void LoadPdf(MemoryStream stream)
{
stream.Position = 0;
- PdfDocumentWrapper = new PdfDocumentWrapper(stream);
- _pdfLoaded = true;
-
- if (PdfDocumentWrapper.PdfDocument.PageCount < 2)
- listThumbnails.Visibility = Visibility.Collapsed;
-
- BeginLoadThumbnails();
+ LoadPdf(new PdfDocumentWrapper(stream));
}
private void BeginLoadThumbnails()
{
- Enumerable.Range(0, PdfDocumentWrapper.PdfDocument.PageCount).ForEach(PageThumbnails.Add);
+ PageThumbnails = new ObservableCollection(Enumerable.Range(0, PdfDocumentWrapper.PageCount));
+ OnPropertyChanged(nameof(PageThumbnails));
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
diff --git a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/Plugin.cs
index 54733c2d7..bd30f26af 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/Plugin.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.PDFViewer/Plugin.cs
@@ -33,6 +33,7 @@ public sealed class Plugin : IViewer
private string _path;
private PdfViewerControl _pdfControl;
private PasswordControl _passwordControl;
+ private PdfDocumentWrapper _preparedDocument;
public int Priority => -1;
@@ -59,16 +60,22 @@ public bool CanHandle(string path)
public void Prepare(string path, ContextObject context)
{
+ _preparedDocument?.Dispose();
+ _preparedDocument = null;
_context = context;
_path = path;
try
{
- var desiredSize = PdfViewerControl.GetDesiredControlSizeByFirstPage(path);
+ _preparedDocument = new PdfDocumentWrapper(path);
+ var desiredSize = PdfViewerControl.GetDesiredControlSizeByFirstPage(_preparedDocument);
context.SetPreferredSizeFit(desiredSize, 0.9);
}
catch (PdfException ex) when (ex.Message == "Password required or incorrect password")
{
+ _preparedDocument?.Dispose();
+ _preparedDocument = null;
+
// Fallback to a size to request the password
context.PreferredSize = new Size { Width = 800, Height = 600 };
}
@@ -85,7 +92,9 @@ public void View(string path, ContextObject context)
{
try
{
- _pdfControl.LoadPdf(path);
+ var document = _preparedDocument;
+ _preparedDocument = null;
+ _pdfControl.LoadPdf(document ?? new PdfDocumentWrapper(path));
context.Title = $"1 / {_pdfControl.TotalPages}: {Path.GetFileName(path)}";
@@ -100,7 +109,8 @@ public void View(string path, ContextObject context)
{
try
{
- var desiredSize = PdfViewerControl.GetDesiredControlSizeByFirstPage(path, password);
+ var document = new PdfDocumentWrapper(path, password);
+ var desiredSize = PdfViewerControl.GetDesiredControlSizeByFirstPage(document);
context.SetPreferredSizeFit(desiredSize, 0.9); // Actually it is no longer effective here
context.ViewerContent = _pdfControl;
@@ -120,7 +130,7 @@ public void View(string path, ContextObject context)
}
context.IsBusy = true;
- _pdfControl.LoadPdf(path, password);
+ _pdfControl.LoadPdf(document);
context.Title = $"1 / {_pdfControl.TotalPages}: {Path.GetFileName(path)}";
@@ -158,6 +168,9 @@ public void Cleanup()
_pdfControl?.Dispose();
_pdfControl = null;
+ _preparedDocument?.Dispose();
+ _preparedDocument = null;
+
_context = null;
}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/Plugin.cs
index 311c725c7..64fd4d3a4 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/Plugin.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/Plugin.cs
@@ -31,7 +31,8 @@ public sealed class Plugin : IViewer
private ViewerPanel _vp;
- public int Priority => -3;
+ // Let cheap text, archive, and model-format checks run before probing files with MediaInfo.
+ public int Priority => -9;
static Plugin()
{
@@ -52,7 +53,10 @@ public void Init()
public bool CanHandle(string path)
{
- if (!Directory.Exists(path))
+ if (Directory.Exists(path))
+ return false;
+
+ if (File.Exists(path))
{
try
{
diff --git a/QuickLook/Controls/GlassLayer/GlassLayer.xaml b/QuickLook/Controls/GlassLayer/GlassLayer.xaml
index f40cb87cc..7b0811648 100644
--- a/QuickLook/Controls/GlassLayer/GlassLayer.xaml
+++ b/QuickLook/Controls/GlassLayer/GlassLayer.xaml
@@ -11,7 +11,7 @@
-
+
@@ -49,4 +49,4 @@
-
\ No newline at end of file
+
diff --git a/QuickLook/ViewWindowManager.cs b/QuickLook/ViewWindowManager.cs
index e18fc7004..69d6faabb 100644
--- a/QuickLook/ViewWindowManager.cs
+++ b/QuickLook/ViewWindowManager.cs
@@ -22,6 +22,9 @@
using System.Diagnostics;
using System.IO;
using System.Runtime.ExceptionServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Threading;
namespace QuickLook;
@@ -31,6 +34,8 @@ public class ViewWindowManager : IDisposable
private string _invokedPath = string.Empty;
private ViewerWindow _viewerWindow;
+ private readonly SemaphoreSlim _pluginMatchLock = new(1, 1);
+ private int _previewRequestId;
internal ViewWindowManager()
{
@@ -66,6 +71,8 @@ public void RunAndClosePreview()
public void ClosePreview()
{
+ _previewRequestId++;
+
if (!_viewerWindow.IsVisible)
return;
@@ -163,10 +170,7 @@ public void InvokePreview(string path = null)
_invokedPath = path;
RunFocusMonitor();
-
- var matchedPlugin = PluginManager.GetInstance().FindMatch(path);
-
- BeginShowNewWindow(path, matchedPlugin);
+ BeginPreviewRequest(path);
}
public void InvokePluginPreview(string plugin, string path = null)
@@ -194,6 +198,7 @@ public void InvokePluginPreview(string plugin, string path = null)
if (matchedPlugin != null)
{
+ _previewRequestId++;
BeginShowNewWindow(path, matchedPlugin);
}
}
@@ -203,9 +208,7 @@ public void ReloadPreview()
if (!_viewerWindow.IsVisible || string.IsNullOrEmpty(_invokedPath))
return;
- var matchedPlugin = PluginManager.GetInstance().FindMatch(_invokedPath);
-
- BeginShowNewWindow(_invokedPath, matchedPlugin);
+ BeginPreviewRequest(_invokedPath);
}
public void ToggleFullscreen()
@@ -223,6 +226,49 @@ private void BeginShowNewWindow(string path, IViewer matchedPlugin)
_viewerWindow.BeginShow(matchedPlugin, path, CurrentPluginFailed);
}
+ private void BeginPreviewRequest(string path)
+ {
+ var requestId = ++_previewRequestId;
+
+ _viewerWindow.UnloadPlugin();
+ _viewerWindow.BeginLoading(path);
+
+ _ = FindPluginAndShowAsync(path, requestId);
+ }
+
+ private async Task FindPluginAndShowAsync(string path, int requestId)
+ {
+ await _pluginMatchLock.WaitAsync();
+
+ try
+ {
+ if (requestId != _previewRequestId)
+ return;
+
+ var matchedPlugin = await Task.Run(() => PluginManager.GetInstance().FindMatch(path));
+
+ if (requestId != _previewRequestId || !_viewerWindow.IsVisible)
+ return;
+
+ // Let WPF draw the loading window before a plugin runs its preparation step.
+ await _viewerWindow.Dispatcher.InvokeAsync(() => { }, DispatcherPriority.ApplicationIdle);
+
+ if (requestId != _previewRequestId || !_viewerWindow.IsVisible)
+ return;
+
+ BeginShowNewWindow(path, matchedPlugin);
+ }
+ catch (Exception e)
+ {
+ if (requestId == _previewRequestId && _viewerWindow.IsVisible)
+ CurrentPluginFailed(path, ExceptionDispatchInfo.Capture(e));
+ }
+ finally
+ {
+ _pluginMatchLock.Release();
+ }
+ }
+
private void CurrentPluginFailed(string path, ExceptionDispatchInfo e)
{
var plugin = _viewerWindow.Plugin?.GetType();
diff --git a/QuickLook/ViewerWindow.Actions.cs b/QuickLook/ViewerWindow.Actions.cs
index c5fb50506..2f285553e 100644
--- a/QuickLook/ViewerWindow.Actions.cs
+++ b/QuickLook/ViewerWindow.Actions.cs
@@ -294,6 +294,12 @@ internal void BeginShow(IViewer matchedPlugin, string path,
return;
}
+ if (SettingHelper.Get("HideToolbar", false))
+ {
+ ContextObject.TitlebarAutoHide = true;
+ ContextObject.TitlebarOverlap = true;
+ }
+
if (ContextObject.IsBlocked)
{
ContextObject.ViewerContent = new System.Windows.Controls.TextBlock
@@ -393,6 +399,42 @@ internal void BeginShow(IViewer matchedPlugin, string path,
}, DispatcherPriority.Input);
}
+ internal void BeginLoading(string path)
+ {
+ _path = path;
+ ContextObject.Reset();
+ ContextObject.Title = Path.GetFileName(path);
+ ContextObject.PreferredSize = new Size(800, 600);
+ ContextObject.IsBusy = true;
+
+ if (SettingHelper.Get("HideToolbar", false))
+ {
+ ContextObject.TitlebarAutoHide = true;
+ ContextObject.TitlebarOverlap = true;
+ }
+
+ var newHeight = ContextObject.PreferredSize.Height + BorderThickness.Top + BorderThickness.Bottom +
+ (ContextObject.TitlebarOverlap ? 0 : windowCaptionContainer.Height);
+ var newWidth = ContextObject.PreferredSize.Width + BorderThickness.Left + BorderThickness.Right;
+ var newSize = _customWindowSize == Size.Empty
+ ? new Size(newWidth, newHeight)
+ : _customWindowSize;
+
+ if (_customWindowSize == Size.Empty)
+ _ignoreNextWindowSizeChange = true;
+
+ PositionWindow(newSize);
+
+ if (IsVisible)
+ return;
+
+ Dispatcher.BeginInvoke(new Action(() => this.BringToFront(Topmost)), DispatcherPriority.Render);
+ if (!SettingHelper.Get("ShowWindowTransition", true, "QuickLook"))
+ this.ShowWithoutTransition();
+ else
+ Show();
+ }
+
private void ClearMoreMenuUnpin()
{
var toRemove = buttonMore.ContextMenu.Items
diff --git a/QuickLook/ViewerWindow.xaml b/QuickLook/ViewerWindow.xaml
index 147cf9bc1..1a765b8c0 100644
--- a/QuickLook/ViewerWindow.xaml
+++ b/QuickLook/ViewerWindow.xaml
@@ -65,18 +65,17 @@
-
+
+ Duration="0:0:0.015" />
-
-
+
@@ -84,6 +83,7 @@
@@ -245,4 +245,4 @@
IsBusyIndicatorShowing="{Binding ElementName=mainWindow, Path=ContextObject.IsBusy}" />
-
\ No newline at end of file
+
diff --git a/QuickLook/ViewerWindow.xaml.cs b/QuickLook/ViewerWindow.xaml.cs
index 7eb190af7..cacf3ca05 100644
--- a/QuickLook/ViewerWindow.xaml.cs
+++ b/QuickLook/ViewerWindow.xaml.cs
@@ -40,8 +40,17 @@ namespace QuickLook;
public partial class ViewerWindow : Window
{
+ private const double TitlebarRevealBandPercentage = 0.33;
+ private const double TitlebarRevealMovementThreshold = 10;
+
+ private readonly DispatcherTimer _titlebarAutoHideTimer = new(DispatcherPriority.Normal)
+ {
+ Interval = TimeSpan.FromSeconds(1),
+ };
private Size _customWindowSize = Size.Empty;
private bool _ignoreNextWindowSizeChange;
+ private bool _trackingTitlebarRevealMovement;
+ private Point _titlebarRevealStart;
private string _path = string.Empty;
private FileSystemWatcher _autoReloadWatcher;
private readonly bool _autoReload;
@@ -55,6 +64,10 @@ internal ViewerWindow()
InitializeComponent();
+ _titlebarAutoHideTimer.Tick += AutoHideCaptionContainer;
+ Closed += (_, _) => _titlebarAutoHideTimer.Stop();
+ MouseLeave += HideCaptionContainerOnMouseLeave;
+
_autoReload = SettingHelper.Get("AutoReload", false);
Icon = (App.IsWin10 ? Properties.Resources.app_white_png : Properties.Resources.app_png).ToBitmapSource();
@@ -458,25 +471,80 @@ private void SaveWindowSizeOnSizeChanged(object sender, SizeChangedEventArgs e)
private void ShowWindowCaptionContainer(object sender, MouseEventArgs e)
{
- var show = (Storyboard)windowCaptionContainer.FindResource("ShowCaptionContainerStoryboard");
+ if (!ContextObject.TitlebarAutoHide || !ContextObject.TitlebarOverlap)
+ {
+ _titlebarAutoHideTimer.Stop();
+ _trackingTitlebarRevealMovement = false;
+ return;
+ }
- if (windowCaptionContainer.Opacity == 0 || windowCaptionContainer.Opacity == 1)
- show.Begin();
+ if (windowCaptionContainer.Opacity > 0)
+ {
+ _trackingTitlebarRevealMovement = false;
+ if (windowCaptionContainer.Opacity < 1)
+ {
+ var showAnimation = (Storyboard)windowCaptionContainer.FindResource("ShowCaptionContainerStoryboard");
+ showAnimation.Begin();
+ }
+
+ _titlebarAutoHideTimer.Stop();
+ _titlebarAutoHideTimer.Start();
+ return;
+ }
+
+ var pointerPosition = e.GetPosition(windowFrameContainer);
+ if (pointerPosition.Y > windowFrameContainer.ActualHeight * TitlebarRevealBandPercentage)
+ {
+ _trackingTitlebarRevealMovement = false;
+ return;
+ }
+
+ if (windowCaptionContainer.Opacity != 0)
+ {
+ _trackingTitlebarRevealMovement = false;
+ return;
+ }
+
+ if (!_trackingTitlebarRevealMovement)
+ {
+ _titlebarRevealStart = pointerPosition;
+ _trackingTitlebarRevealMovement = true;
+ return;
+ }
+
+ var horizontalMovement = pointerPosition.X - _titlebarRevealStart.X;
+ var verticalMovement = pointerPosition.Y - _titlebarRevealStart.Y;
+ if ((horizontalMovement * horizontalMovement) + (verticalMovement * verticalMovement)
+ < TitlebarRevealMovementThreshold * TitlebarRevealMovementThreshold)
+ return;
+
+ _trackingTitlebarRevealMovement = false;
+ var show = (Storyboard)windowCaptionContainer.FindResource("ShowCaptionContainerStoryboard");
+ show.Begin();
+ _titlebarAutoHideTimer.Stop();
+ _titlebarAutoHideTimer.Start();
}
private void AutoHideCaptionContainer(object sender, EventArgs e)
{
+ _titlebarAutoHideTimer.Stop();
+
if (!ContextObject.TitlebarAutoHide)
return;
if (!ContextObject.TitlebarOverlap)
return;
- if (windowCaptionContainer.IsMouseOver)
+ if (windowCaptionContainer.Opacity <= 0)
return;
var hide = (Storyboard)windowCaptionContainer.FindResource("HideCaptionContainerStoryboard");
hide.Begin();
}
+
+ private void HideCaptionContainerOnMouseLeave(object sender, MouseEventArgs e)
+ {
+ AutoHideCaptionContainer(sender, e);
+ }
}