mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-10-21 02:56:09 +00:00
Support MediaInfoViewer in more menu
This commit is contained in:
Submodule QuickLook.Common updated: eb72b2eabb...2dc2616aeb
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using MediaInfoLib;
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
|
|
||||||
namespace QuickLook.Plugin.MediaInfoViewer;
|
namespace QuickLook.Plugin.MediaInfoViewer;
|
||||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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<MoreMenuProvider> Instance { get; set; } = new(() => new());
|
||||||
|
|
||||||
|
public ICommand ShowWithMediaInfoCommand { get; }
|
||||||
|
|
||||||
|
public MoreMenuProvider()
|
||||||
|
{
|
||||||
|
ShowWithMediaInfoCommand = new RelayCommand(ShowWithMediaInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IMenuItem> Get()
|
||||||
|
{
|
||||||
|
yield return new MoreMenuItem()
|
||||||
|
{
|
||||||
|
Icon = "\xea69",
|
||||||
|
Header = "Show Media Info",
|
||||||
|
MenuItems = null,
|
||||||
|
Command = ShowWithMediaInfoCommand,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowWithMediaInfo()
|
||||||
|
{
|
||||||
|
PluginHelper.InvokePluginPreview("QuickLook.Plugin.MediaInfoViewer");
|
||||||
|
}
|
||||||
|
}
|
@@ -18,7 +18,9 @@
|
|||||||
using MediaInfoLib;
|
using MediaInfoLib;
|
||||||
using QuickLook.Common.Helpers;
|
using QuickLook.Common.Helpers;
|
||||||
using QuickLook.Common.Plugin;
|
using QuickLook.Common.Plugin;
|
||||||
|
using QuickLook.Common.Plugin.MoreMenu;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
@@ -26,12 +28,14 @@ using System.Windows.Media;
|
|||||||
|
|
||||||
namespace QuickLook.Plugin.MediaInfoViewer;
|
namespace QuickLook.Plugin.MediaInfoViewer;
|
||||||
|
|
||||||
public class Plugin : IViewer
|
public class Plugin : IViewer, IMoreMenuExtended
|
||||||
{
|
{
|
||||||
private TextViewerPanel _tvp;
|
private TextViewerPanel _tvp;
|
||||||
|
|
||||||
public int Priority => 0;
|
public int Priority => 0;
|
||||||
|
|
||||||
|
public IEnumerable<IMenuItem> MenuItems => MoreMenuProvider.Instance.Value.Get();
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,7 @@ using System.Windows.Threading;
|
|||||||
|
|
||||||
namespace QuickLook;
|
namespace QuickLook;
|
||||||
|
|
||||||
internal static class PipeMessages
|
public static class PipeMessages
|
||||||
{
|
{
|
||||||
public const string RunAndClose = "QuickLook.App.PipeMessages.RunAndClose";
|
public const string RunAndClose = "QuickLook.App.PipeMessages.RunAndClose";
|
||||||
public const string Switch = "QuickLook.App.PipeMessages.Switch";
|
public const string Switch = "QuickLook.App.PipeMessages.Switch";
|
||||||
@@ -38,7 +38,7 @@ internal static class PipeMessages
|
|||||||
public const string Quit = "QuickLook.App.PipeMessages.Quit";
|
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 readonly string PipeName = "QuickLook.App.Pipe." + WindowsIdentity.GetCurrent().User?.Value;
|
||||||
private static PipeServerManager _instance;
|
private static PipeServerManager _instance;
|
||||||
|
@@ -29,7 +29,7 @@ using UnblockZoneIdentifier;
|
|||||||
|
|
||||||
namespace QuickLook;
|
namespace QuickLook;
|
||||||
|
|
||||||
internal class PluginManager
|
public class PluginManager
|
||||||
{
|
{
|
||||||
private static PluginManager _instance;
|
private static PluginManager _instance;
|
||||||
|
|
||||||
|
@@ -22,11 +22,10 @@ using System;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace QuickLook;
|
namespace QuickLook;
|
||||||
|
|
||||||
internal class ViewWindowManager : IDisposable
|
public class ViewWindowManager : IDisposable
|
||||||
{
|
{
|
||||||
private static ViewWindowManager _instance;
|
private static ViewWindowManager _instance;
|
||||||
|
|
||||||
@@ -239,7 +238,7 @@ internal class ViewWindowManager : IDisposable
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ViewWindowManager GetInstance()
|
public static ViewWindowManager GetInstance()
|
||||||
{
|
{
|
||||||
return _instance ??= new ViewWindowManager();
|
return _instance ??= new ViewWindowManager();
|
||||||
}
|
}
|
||||||
|
@@ -17,15 +17,22 @@
|
|||||||
|
|
||||||
using QuickLook.Common.Helpers;
|
using QuickLook.Common.Helpers;
|
||||||
using QuickLook.Common.Plugin;
|
using QuickLook.Common.Plugin;
|
||||||
|
using QuickLook.Common.Plugin.MoreMenu;
|
||||||
using QuickLook.Helpers;
|
using QuickLook.Helpers;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using Wpf.Ui.Controls;
|
||||||
|
using MenuItem = System.Windows.Controls.MenuItem;
|
||||||
|
|
||||||
namespace QuickLook;
|
namespace QuickLook;
|
||||||
|
|
||||||
@@ -57,7 +64,7 @@ public partial class ViewerWindow
|
|||||||
|
|
||||||
private void PositionWindow(Size size)
|
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)
|
if (WindowState == WindowState.Maximized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -70,7 +77,7 @@ public partial class ViewerWindow
|
|||||||
|
|
||||||
private Rect ResizeAndCentreExistingWindow(Size size)
|
private Rect ResizeAndCentreExistingWindow(Size size)
|
||||||
{
|
{
|
||||||
// align window just like in macOS ...
|
// Align window just like in macOS ...
|
||||||
//
|
//
|
||||||
// |10%| 80% |10%|
|
// |10%| 80% |10%|
|
||||||
// |---|-----------|---|---
|
// |---|-----------|---|---
|
||||||
@@ -88,11 +95,11 @@ public partial class ViewerWindow
|
|||||||
var limitPercentX = 0.1 * scale.Horizontal;
|
var limitPercentX = 0.1 * scale.Horizontal;
|
||||||
var limitPercentY = 0.1 * scale.Vertical;
|
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 pxSize = new Size(scale.Horizontal * size.Width, scale.Vertical * size.Height);
|
||||||
var pxOldRect = this.GetWindowRectInPixel();
|
var pxOldRect = this.GetWindowRectInPixel();
|
||||||
|
|
||||||
// scale to new size, maintain centre
|
// Scale to new size, maintain centre
|
||||||
var pxNewRect = Rect.Inflate(pxOldRect,
|
var pxNewRect = Rect.Inflate(pxOldRect,
|
||||||
(pxSize.Width - pxOldRect.Width) / 2,
|
(pxSize.Width - pxOldRect.Width) / 2,
|
||||||
(pxSize.Height - pxOldRect.Height) / 2);
|
(pxSize.Height - pxOldRect.Height) / 2);
|
||||||
@@ -121,7 +128,7 @@ public partial class ViewerWindow
|
|||||||
pxNewRect.Offset(0,
|
pxNewRect.Offset(0,
|
||||||
Math.Max(0, desktopRect.Top - pxNewRect.Top) + Math.Min(0, desktopRect.Bottom - pxNewRect.Bottom));
|
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);
|
return new Rect(pxNewRect.Location, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,13 +142,13 @@ public partial class ViewerWindow
|
|||||||
desktopRect.X + (desktopRect.Width - pxSize.Width) / 2,
|
desktopRect.X + (desktopRect.Width - pxSize.Width) / 2,
|
||||||
desktopRect.Y + (desktopRect.Height - pxSize.Height) / 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);
|
return new Rect(pxLocation, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void UnloadPlugin()
|
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);
|
FocusManager.SetFocusedElement(this, null);
|
||||||
Keyboard.DefaultRestoreFocusMode =
|
Keyboard.DefaultRestoreFocusMode =
|
||||||
RestoreFocusMode.None; // WPF will put the focused item into a "_restoreFocus" list ... omg
|
RestoreFocusMode.None; // WPF will put the focused item into a "_restoreFocus" list ... omg
|
||||||
@@ -180,10 +187,10 @@ public partial class ViewerWindow
|
|||||||
|
|
||||||
ContextObject.Reset();
|
ContextObject.Reset();
|
||||||
|
|
||||||
// assign monitor color profile
|
// Assign monitor color profile
|
||||||
ContextObject.ColorProfileName = DisplayDeviceHelper.GetMonitorColorProfileFromWindow(this);
|
ContextObject.ColorProfileName = DisplayDeviceHelper.GetMonitorColorProfileFromWindow(this);
|
||||||
|
|
||||||
// get window size before showing it
|
// Get window size before showing it
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Plugin.Prepare(path, ContextObject);
|
Plugin.Prepare(path, ContextObject);
|
||||||
@@ -194,9 +201,30 @@ public partial class ViewerWindow
|
|||||||
return;
|
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();
|
SetOpenWithButtonAndPath();
|
||||||
|
|
||||||
// revert UI changes
|
// Revert UI changes
|
||||||
ContextObject.IsBusy = true;
|
ContextObject.IsBusy = true;
|
||||||
|
|
||||||
var newHeight = ContextObject.PreferredSize.Height + BorderThickness.Top + BorderThickness.Bottom +
|
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 newWidth = ContextObject.PreferredSize.Width + BorderThickness.Left + BorderThickness.Right;
|
||||||
|
|
||||||
var newSize = new Size(newWidth, newHeight);
|
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)
|
if (_customWindowSize != Size.Empty)
|
||||||
newSize = _customWindowSize;
|
newSize = _customWindowSize;
|
||||||
else
|
else
|
||||||
@@ -231,7 +259,7 @@ public partial class ViewerWindow
|
|||||||
_autoReloadWatcher.EnableRaisingEvents = true;
|
_autoReloadWatcher.EnableRaisingEvents = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// load plugin, do not block UI
|
// Load plugin, do not block UI
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -246,12 +274,79 @@ public partial class ViewerWindow
|
|||||||
DispatcherPriority.Input);
|
DispatcherPriority.Input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearMoreMenuUnpin()
|
||||||
|
{
|
||||||
|
var toRemove = buttonMore.ContextMenu.Items
|
||||||
|
.OfType<FrameworkElement>() // 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<IMenuItem> 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()
|
private void SetOpenWithButtonAndPath()
|
||||||
{
|
{
|
||||||
// share icon
|
// Share icon
|
||||||
buttonShare.Visibility = ShareHelper.IsShareSupported(_path) ? Visibility.Visible : Visibility.Collapsed;
|
buttonShare.Visibility = ShareHelper.IsShareSupported(_path) ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
|
||||||
// open icon
|
// Open icon
|
||||||
if (Directory.Exists(_path))
|
if (Directory.Exists(_path))
|
||||||
{
|
{
|
||||||
buttonOpen.ToolTip = string.Format(TranslationHelper.Get("MW_BrowseFolder"), Path.GetFileName(_path));
|
buttonOpen.ToolTip = string.Format(TranslationHelper.Get("MW_BrowseFolder"), Path.GetFileName(_path));
|
||||||
@@ -265,7 +360,7 @@ public partial class ViewerWindow
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not an exe
|
// Not an exe
|
||||||
var found = FileHelper.GetAssocApplication(_path, out appFriendlyName);
|
var found = FileHelper.GetAssocApplication(_path, out appFriendlyName);
|
||||||
if (found)
|
if (found)
|
||||||
{
|
{
|
||||||
@@ -273,7 +368,7 @@ public partial class ViewerWindow
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// assoc not found
|
// Assoc not found
|
||||||
buttonOpen.ToolTip = string.Format(TranslationHelper.Get("MW_Open"), Path.GetFileName(_path));
|
buttonOpen.ToolTip = string.Format(TranslationHelper.Get("MW_Open"), Path.GetFileName(_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -132,14 +132,15 @@
|
|||||||
ToolTip="More">
|
ToolTip="More">
|
||||||
<Button.ContextMenu>
|
<Button.ContextMenu>
|
||||||
<ContextMenu FontSize="12">
|
<ContextMenu FontSize="12">
|
||||||
<MenuItem x:Name="moreItemCopyAsPath">
|
<MenuItem x:Name="moreItemCopyAsPath" Tag="PinMenu">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<ui:FontIcon FontFamily="{DynamicResource SymbolThemeFontFamily}" Glyph="{x:Static ui:FontSymbols.Copy}" />
|
<ui:FontIcon FontFamily="{DynamicResource SymbolThemeFontFamily}" Glyph="{x:Static ui:FontSymbols.Copy}" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Separator Visibility="Collapsed" />
|
<Separator Tag="PinMenu" Visibility="Collapsed" />
|
||||||
<MenuItem x:Name="moreItemOpenSettings"
|
<MenuItem x:Name="moreItemOpenSettings"
|
||||||
Header="Settings"
|
Header="Settings"
|
||||||
|
Tag="PinMenu"
|
||||||
Visibility="Collapsed">
|
Visibility="Collapsed">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<ui:FontIcon FontFamily="{DynamicResource SymbolThemeFontFamily}" Glyph="{x:Static ui:FontSymbols.Settings}" />
|
<ui:FontIcon FontFamily="{DynamicResource SymbolThemeFontFamily}" Glyph="{x:Static ui:FontSymbols.Settings}" />
|
||||||
|
Reference in New Issue
Block a user