diff --git a/QuickLook.Common b/QuickLook.Common
index eb72b2e..2dc2616 160000
--- a/QuickLook.Common
+++ b/QuickLook.Common
@@ -1 +1 @@
-Subproject commit eb72b2eabb45b45479d2adcd235d23f941da4a99
+Subproject commit 2dc2616aebfd79c428e5a4e94a40368df5bbe3d4
diff --git a/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/Exporter.cs b/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/Exporter.cs
index de010ea..1672b2d 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/Exporter.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/Exporter.cs
@@ -1,4 +1,21 @@
-using MediaInfoLib;
+// Copyright © 2017-2025 QL-Win Contributors
+//
+// This file is part of QuickLook program.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+using MediaInfoLib;
using System.ComponentModel.Composition;
namespace QuickLook.Plugin.MediaInfoViewer;
diff --git a/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/MoreMenuProvider.cs b/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/MoreMenuProvider.cs
new file mode 100644
index 0000000..c948eaa
--- /dev/null
+++ b/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/MoreMenuProvider.cs
@@ -0,0 +1,53 @@
+// Copyright © 2017-2025 QL-Win Contributors
+//
+// This file is part of QuickLook program.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+using QuickLook.Common.Commands;
+using QuickLook.Common.Helpers;
+using QuickLook.Common.Plugin.MoreMenu;
+using System;
+using System.Collections.Generic;
+using System.Windows.Input;
+
+namespace QuickLook.Plugin.MediaInfoViewer;
+
+public sealed class MoreMenuProvider
+{
+ public static Lazy Instance { get; set; } = new(() => new());
+
+ public ICommand ShowWithMediaInfoCommand { get; }
+
+ public MoreMenuProvider()
+ {
+ ShowWithMediaInfoCommand = new RelayCommand(ShowWithMediaInfo);
+ }
+
+ public IEnumerable Get()
+ {
+ yield return new MoreMenuItem()
+ {
+ Icon = "\xea69",
+ Header = "Show Media Info",
+ MenuItems = null,
+ Command = ShowWithMediaInfoCommand,
+ };
+ }
+
+ public void ShowWithMediaInfo()
+ {
+ PluginHelper.InvokePluginPreview("QuickLook.Plugin.MediaInfoViewer");
+ }
+}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/Plugin.cs
index 172bf2c..be49dc4 100644
--- a/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/Plugin.cs
+++ b/QuickLook.Plugin/QuickLook.Plugin.MediaInfoViewer/Plugin.cs
@@ -18,7 +18,9 @@
using MediaInfoLib;
using QuickLook.Common.Helpers;
using QuickLook.Common.Plugin;
+using QuickLook.Common.Plugin.MoreMenu;
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
@@ -26,12 +28,14 @@ using System.Windows.Media;
namespace QuickLook.Plugin.MediaInfoViewer;
-public class Plugin : IViewer
+public class Plugin : IViewer, IMoreMenuExtended
{
private TextViewerPanel _tvp;
public int Priority => 0;
+ public IEnumerable MenuItems => MoreMenuProvider.Instance.Value.Get();
+
public void Init()
{
}
diff --git a/QuickLook/PipeServerManager.cs b/QuickLook/PipeServerManager.cs
index 902ab76..5d281fe 100644
--- a/QuickLook/PipeServerManager.cs
+++ b/QuickLook/PipeServerManager.cs
@@ -27,7 +27,7 @@ using System.Windows.Threading;
namespace QuickLook;
-internal static class PipeMessages
+public static class PipeMessages
{
public const string RunAndClose = "QuickLook.App.PipeMessages.RunAndClose";
public const string Switch = "QuickLook.App.PipeMessages.Switch";
@@ -38,7 +38,7 @@ internal static class PipeMessages
public const string Quit = "QuickLook.App.PipeMessages.Quit";
}
-internal class PipeServerManager : IDisposable
+public class PipeServerManager : IDisposable
{
private static readonly string PipeName = "QuickLook.App.Pipe." + WindowsIdentity.GetCurrent().User?.Value;
private static PipeServerManager _instance;
diff --git a/QuickLook/PluginManager.cs b/QuickLook/PluginManager.cs
index 2325880..b4ccb8f 100644
--- a/QuickLook/PluginManager.cs
+++ b/QuickLook/PluginManager.cs
@@ -29,7 +29,7 @@ using UnblockZoneIdentifier;
namespace QuickLook;
-internal class PluginManager
+public class PluginManager
{
private static PluginManager _instance;
diff --git a/QuickLook/ViewWindowManager.cs b/QuickLook/ViewWindowManager.cs
index a821a0b..ee1ce2b 100644
--- a/QuickLook/ViewWindowManager.cs
+++ b/QuickLook/ViewWindowManager.cs
@@ -22,11 +22,10 @@ using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.ExceptionServices;
-using System.Windows;
namespace QuickLook;
-internal class ViewWindowManager : IDisposable
+public class ViewWindowManager : IDisposable
{
private static ViewWindowManager _instance;
@@ -239,7 +238,7 @@ internal class ViewWindowManager : IDisposable
};
}
- internal static ViewWindowManager GetInstance()
+ public static ViewWindowManager GetInstance()
{
return _instance ??= new ViewWindowManager();
}
diff --git a/QuickLook/ViewerWindow.Actions.cs b/QuickLook/ViewerWindow.Actions.cs
index 6a01c27..8fee114 100644
--- a/QuickLook/ViewerWindow.Actions.cs
+++ b/QuickLook/ViewerWindow.Actions.cs
@@ -17,15 +17,22 @@
using QuickLook.Common.Helpers;
using QuickLook.Common.Plugin;
+using QuickLook.Common.Plugin.MoreMenu;
using QuickLook.Helpers;
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Runtime.ExceptionServices;
using System.Windows;
+using System.Windows.Controls;
using System.Windows.Input;
+using System.Windows.Media;
using System.Windows.Threading;
+using Wpf.Ui.Controls;
+using MenuItem = System.Windows.Controls.MenuItem;
namespace QuickLook;
@@ -57,7 +64,7 @@ public partial class ViewerWindow
private void PositionWindow(Size size)
{
- // if the window is now now maximized, do not move it
+ // If the window is now now maximized, do not move it
if (WindowState == WindowState.Maximized)
return;
@@ -70,7 +77,7 @@ public partial class ViewerWindow
private Rect ResizeAndCentreExistingWindow(Size size)
{
- // align window just like in macOS ...
+ // Align window just like in macOS ...
//
// |10%| 80% |10%|
// |---|-----------|---|---
@@ -88,11 +95,11 @@ public partial class ViewerWindow
var limitPercentX = 0.1 * scale.Horizontal;
var limitPercentY = 0.1 * scale.Vertical;
- // use absolute pixels for calculation
+ // Use absolute pixels for calculation
var pxSize = new Size(scale.Horizontal * size.Width, scale.Vertical * size.Height);
var pxOldRect = this.GetWindowRectInPixel();
- // scale to new size, maintain centre
+ // Scale to new size, maintain centre
var pxNewRect = Rect.Inflate(pxOldRect,
(pxSize.Width - pxOldRect.Width) / 2,
(pxSize.Height - pxOldRect.Height) / 2);
@@ -121,7 +128,7 @@ public partial class ViewerWindow
pxNewRect.Offset(0,
Math.Max(0, desktopRect.Top - pxNewRect.Top) + Math.Min(0, desktopRect.Bottom - pxNewRect.Bottom));
- // return absolute location and relative size
+ // Return absolute location and relative size
return new Rect(pxNewRect.Location, size);
}
@@ -135,13 +142,13 @@ public partial class ViewerWindow
desktopRect.X + (desktopRect.Width - pxSize.Width) / 2,
desktopRect.Y + (desktopRect.Height - pxSize.Height) / 2);
- // return absolute location and relative size
+ // Return absolute location and relative size
return new Rect(pxLocation, size);
}
internal void UnloadPlugin()
{
- // the focused element will not processed by GC: https://stackoverflow.com/questions/30848939/memory-leak-due-to-window-efectivevalues-retention
+ // The focused element will not processed by GC: https://stackoverflow.com/questions/30848939/memory-leak-due-to-window-efectivevalues-retention
FocusManager.SetFocusedElement(this, null);
Keyboard.DefaultRestoreFocusMode =
RestoreFocusMode.None; // WPF will put the focused item into a "_restoreFocus" list ... omg
@@ -180,10 +187,10 @@ public partial class ViewerWindow
ContextObject.Reset();
- // assign monitor color profile
+ // Assign monitor color profile
ContextObject.ColorProfileName = DisplayDeviceHelper.GetMonitorColorProfileFromWindow(this);
- // get window size before showing it
+ // Get window size before showing it
try
{
Plugin.Prepare(path, ContextObject);
@@ -194,9 +201,30 @@ public partial class ViewerWindow
return;
}
+ // Initial the more menu
+ ClearMoreMenuUnpin();
+ foreach (var plugin in PluginManager.GetInstance().LoadedPlugins)
+ {
+ if (plugin == Plugin)
+ {
+ if (Plugin is IMoreMenu moreMenu && moreMenu.MenuItems is not null)
+ {
+ InsertMoreMenu(moreMenu.MenuItems);
+ }
+ continue;
+ }
+ else
+ {
+ if (plugin is IMoreMenuExtended moreMenu && moreMenu.MenuItems is not null)
+ {
+ InsertMoreMenu(moreMenu.MenuItems);
+ }
+ }
+ }
+
SetOpenWithButtonAndPath();
- // revert UI changes
+ // Revert UI changes
ContextObject.IsBusy = true;
var newHeight = ContextObject.PreferredSize.Height + BorderThickness.Top + BorderThickness.Bottom +
@@ -204,7 +232,7 @@ public partial class ViewerWindow
var newWidth = ContextObject.PreferredSize.Width + BorderThickness.Left + BorderThickness.Right;
var newSize = new Size(newWidth, newHeight);
- // if use has adjusted the window size, keep it
+ // If use has adjusted the window size, keep it
if (_customWindowSize != Size.Empty)
newSize = _customWindowSize;
else
@@ -231,7 +259,7 @@ public partial class ViewerWindow
_autoReloadWatcher.EnableRaisingEvents = true;
}
- // load plugin, do not block UI
+ // Load plugin, do not block UI
Dispatcher.BeginInvoke(new Action(() =>
{
try
@@ -246,12 +274,79 @@ public partial class ViewerWindow
DispatcherPriority.Input);
}
+ private void ClearMoreMenuUnpin()
+ {
+ var toRemove = buttonMore.ContextMenu.Items
+ .OfType() // MenuItem and Separator
+ .Where(item => item.Tag is not "PinMenu")
+ .ToArray();
+
+ foreach (var item in toRemove)
+ {
+ buttonMore.ContextMenu.Items.Remove(item);
+ }
+ }
+
+ private void InsertMoreMenu(IEnumerable moreMenu)
+ {
+ int count = 0;
+
+ foreach (IMenuItem item in moreMenu)
+ {
+ if (item is null) continue;
+
+ if (!item.IsSeparator)
+ {
+ MenuItem menuItem = new()
+ {
+ Header = item.Header,
+ Icon = ResolveIcon(item.Icon),
+ Visibility = item.IsVisible ? Visibility.Visible : Visibility.Collapsed,
+ Command = item.Command,
+ };
+
+ buttonMore.ContextMenu.Items.Insert(count++, menuItem);
+ }
+ else
+ {
+ buttonMore.ContextMenu.Items.Insert(count++, new Separator());
+ }
+ }
+
+ if (moreMenu.Any())
+ {
+ buttonMore.ContextMenu.Items.Insert(count++, new Separator());
+ }
+ }
+
+ private object ResolveIcon(object icon)
+ {
+ if (icon is string glyph)
+ {
+ return new FontIcon()
+ {
+ FontFamily = (FontFamily)Application.Current.Resources["SymbolThemeFontFamily"],
+ Glyph = glyph,
+ };
+ }
+ else if (icon is UIElement)
+ {
+ return icon;
+ }
+ else
+ {
+ // Not supported yet
+ }
+
+ return null;
+ }
+
private void SetOpenWithButtonAndPath()
{
- // share icon
+ // Share icon
buttonShare.Visibility = ShareHelper.IsShareSupported(_path) ? Visibility.Visible : Visibility.Collapsed;
- // open icon
+ // Open icon
if (Directory.Exists(_path))
{
buttonOpen.ToolTip = string.Format(TranslationHelper.Get("MW_BrowseFolder"), Path.GetFileName(_path));
@@ -265,7 +360,7 @@ public partial class ViewerWindow
return;
}
- // not an exe
+ // Not an exe
var found = FileHelper.GetAssocApplication(_path, out appFriendlyName);
if (found)
{
@@ -273,7 +368,7 @@ public partial class ViewerWindow
return;
}
- // assoc not found
+ // Assoc not found
buttonOpen.ToolTip = string.Format(TranslationHelper.Get("MW_Open"), Path.GetFileName(_path));
}
diff --git a/QuickLook/ViewerWindow.xaml b/QuickLook/ViewerWindow.xaml
index 128719b..6f5fda0 100644
--- a/QuickLook/ViewerWindow.xaml
+++ b/QuickLook/ViewerWindow.xaml
@@ -132,14 +132,15 @@
ToolTip="More">
-