replace v6 with experimental v7 code
25
src/MpvNet.Windows/.editorconfig
Normal file
@@ -0,0 +1,25 @@
|
||||
[*.cs]
|
||||
|
||||
# IDE0058: Expression value is never used
|
||||
dotnet_diagnostic.IDE0058.severity = none
|
||||
|
||||
# IDE0055: Fix formatting
|
||||
dotnet_diagnostic.IDE0055.severity = none
|
||||
|
||||
# IDE0022: Use block body for methods
|
||||
dotnet_diagnostic.IDE0022.severity = none
|
||||
|
||||
# IDE0040: Add accessibility modifiers
|
||||
dotnet_diagnostic.IDE0040.severity = none
|
||||
|
||||
# IDE0011: Add braces
|
||||
dotnet_diagnostic.IDE0011.severity = none
|
||||
|
||||
# IDE0010: Add missing cases
|
||||
dotnet_diagnostic.IDE0010.severity = none
|
||||
|
||||
# IDE0044: Add readonly modifier
|
||||
dotnet_diagnostic.IDE0044.severity = silent
|
||||
|
||||
# Member does not access instance data and can be marked as static
|
||||
dotnet_diagnostic.CA1822.severity = none
|
||||
5
src/MpvNet.Windows/App.config
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
</configSections>
|
||||
</configuration>
|
||||
134
src/MpvNet.Windows/Conf.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
|
||||
using MpvNet.ExtensionMethod;
|
||||
|
||||
namespace MpvNet.Windows;
|
||||
|
||||
public class Conf
|
||||
{
|
||||
public static List<Setting> LoadConf(string content)
|
||||
{
|
||||
List<Setting> settingsList = new List<Setting>();
|
||||
|
||||
foreach (ConfSection? section in ConfParser.Parse(content))
|
||||
{
|
||||
Setting? baseSetting = null;
|
||||
|
||||
if (section.HasName("option"))
|
||||
{
|
||||
OptionSetting optionSetting = new OptionSetting();
|
||||
baseSetting = optionSetting;
|
||||
optionSetting.Default = section.GetValue("default");
|
||||
optionSetting.Value = optionSetting.Default;
|
||||
|
||||
foreach (var it in section.GetValues("option"))
|
||||
{
|
||||
var opt = new OptionSettingOption();
|
||||
|
||||
if (it.Value.ContainsEx(" "))
|
||||
{
|
||||
opt.Name = it.Value![..it.Value!.IndexOf(" ")];
|
||||
opt.Help = it.Value[it.Value.IndexOf(" ")..].Trim();
|
||||
}
|
||||
else
|
||||
opt.Name = it.Value;
|
||||
|
||||
if (opt.Name == optionSetting.Default)
|
||||
opt.Text = opt.Name + " (Default)";
|
||||
|
||||
opt.OptionSetting = optionSetting;
|
||||
optionSetting.Options.Add(opt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StringSetting stringSetting = new StringSetting();
|
||||
baseSetting = stringSetting;
|
||||
stringSetting.Default = section.HasName("default") ? section.GetValue("default") : "";
|
||||
}
|
||||
|
||||
baseSetting.Name = section.GetValue("name");
|
||||
baseSetting.File = section.GetValue("file");
|
||||
baseSetting.Directory = section.GetValue("directory");
|
||||
|
||||
if (section.HasName("help")) baseSetting.Help = section.GetValue("help");
|
||||
if (section.HasName("url")) baseSetting.URL = section.GetValue("url");
|
||||
if (section.HasName("width")) baseSetting.Width = Convert.ToInt32(section.GetValue("width"));
|
||||
if (section.HasName("type")) baseSetting.Type = section.GetValue("type");
|
||||
|
||||
if (baseSetting.Help.ContainsEx("\\n"))
|
||||
baseSetting.Help = baseSetting.Help?.Replace("\\n", "\n");
|
||||
|
||||
settingsList.Add(baseSetting);
|
||||
}
|
||||
|
||||
return settingsList;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConfItem
|
||||
{
|
||||
public string Comment { get; set; } = "";
|
||||
public string File { get; set; } = "";
|
||||
public string LineComment { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public string Section { get; set; } = "";
|
||||
public string Value { get; set; } = "";
|
||||
|
||||
public bool IsSectionItem { get; set; }
|
||||
public Setting? SettingBase { get; set; }
|
||||
}
|
||||
|
||||
public class ConfParser
|
||||
{
|
||||
public static List<ConfSection> Parse(string content)
|
||||
{
|
||||
string[] lines = content.Split(BR.ToCharArray(), StringSplitOptions.None);
|
||||
var sections = new List<ConfSection>();
|
||||
ConfSection? currentGroup = null;
|
||||
|
||||
foreach (string it in lines)
|
||||
{
|
||||
string line = it.Trim();
|
||||
|
||||
if (line == "")
|
||||
{
|
||||
currentGroup = new ConfSection();
|
||||
sections.Add(currentGroup);
|
||||
}
|
||||
else if (line.Contains('='))
|
||||
{
|
||||
string name = line[..line.IndexOf("=")].Trim();
|
||||
string value = line[(line.IndexOf("=") + 1)..].Trim();
|
||||
|
||||
currentGroup?.Items.Add(new StringPair(name, value));
|
||||
}
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConfSection
|
||||
{
|
||||
public List<StringPair> Items { get; set; } = new List<StringPair>();
|
||||
|
||||
public bool HasName(string name)
|
||||
{
|
||||
foreach (var i in Items)
|
||||
if (i.Name == name)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string? GetValue(string name)
|
||||
{
|
||||
foreach (var i in Items)
|
||||
if (i.Name == name)
|
||||
return i.Value;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<StringPair> GetValues(string name) => Items.Where(i => i.Name == name).ToList();
|
||||
}
|
||||
68
src/MpvNet.Windows/FileAssociation.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
using MpvNet.Windows.Help;
|
||||
|
||||
namespace MpvNet.Windows;
|
||||
|
||||
public static class FileAssociation
|
||||
{
|
||||
public static void Register(string perceivedType, string[] extensions)
|
||||
{
|
||||
string exePath = Environment.ProcessPath!;
|
||||
string exeFilename = Path.GetFileName(exePath);
|
||||
string exeFilenameNoExt = Path.GetFileNameWithoutExtension(exePath);
|
||||
|
||||
string[] protocols = { "ytdl", "rtsp", "srt", "srtp" };
|
||||
|
||||
if (perceivedType != "unreg")
|
||||
{
|
||||
foreach (string it in protocols)
|
||||
{
|
||||
RegistryHelp.SetValue($@"HKCR\{it}", $"{it.ToUpper()} Protocol", "");
|
||||
RegistryHelp.SetValue($@"HKCR\{it}\shell\open\command", "", $"\"{exePath}\" \"%1\"");
|
||||
}
|
||||
|
||||
RegistryHelp.SetValue(@"HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths\" + exeFilename, "", exePath);
|
||||
RegistryHelp.SetValue(@"HKCR\Applications\" + exeFilename, "FriendlyAppName", "mpv.net media player");
|
||||
RegistryHelp.SetValue(@"HKCR\Applications\" + exeFilename + @"\shell\open\command", "", $"\"{exePath}\" \"%1\"");
|
||||
RegistryHelp.SetValue(@"HKCR\SystemFileAssociations\video\OpenWithList\" + exeFilename, "", "");
|
||||
RegistryHelp.SetValue(@"HKCR\SystemFileAssociations\audio\OpenWithList\" + exeFilename, "", "");
|
||||
RegistryHelp.SetValue(@"HKLM\SOFTWARE\RegisteredApplications", "mpv.net", @"SOFTWARE\Clients\Media\mpv.net\Capabilities");
|
||||
RegistryHelp.SetValue(@"HKLM\SOFTWARE\Clients\Media\mpv.net\Capabilities", "ApplicationDescription", "mpv.net media player");
|
||||
RegistryHelp.SetValue(@"HKLM\SOFTWARE\Clients\Media\mpv.net\Capabilities", "ApplicationName", "mpv.net");
|
||||
|
||||
foreach (string ext in extensions)
|
||||
{
|
||||
RegistryHelp.SetValue(@"HKCR\Applications\" + exeFilename + @"\SupportedTypes", "." + ext, "");
|
||||
RegistryHelp.SetValue(@"HKCR\" + "." + ext, "", exeFilenameNoExt + "." + ext);
|
||||
RegistryHelp.SetValue(@"HKCR\" + "." + ext + @"\OpenWithProgIDs", exeFilenameNoExt + "." + ext, "");
|
||||
RegistryHelp.SetValue(@"HKCR\" + "." + ext, "PerceivedType", perceivedType);
|
||||
RegistryHelp.SetValue(@"HKCR\" + exeFilenameNoExt + "." + ext + @"\shell\open\command", "", $"\"{exePath}\" \"%1\"");
|
||||
RegistryHelp.SetValue(@"HKLM\SOFTWARE\Clients\Media\mpv.net\Capabilities\FileAssociations", "." + ext, exeFilenameNoExt + "." + ext);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string i in protocols)
|
||||
RegistryHelp.RemoveKey($@"HKCR\{i}");
|
||||
|
||||
RegistryHelp.RemoveKey(@"HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths\" + exeFilename);
|
||||
RegistryHelp.RemoveKey(@"HKCR\Applications\" + exeFilename);
|
||||
RegistryHelp.RemoveKey(@"HKLM\SOFTWARE\Clients\Media\mpv.net");
|
||||
RegistryHelp.RemoveKey(@"HKCR\SystemFileAssociations\video\OpenWithList\" + exeFilename);
|
||||
RegistryHelp.RemoveKey(@"HKCR\SystemFileAssociations\audio\OpenWithList\" + exeFilename);
|
||||
|
||||
RegistryHelp.RemoveValue(@"HKLM\SOFTWARE\RegisteredApplications", "mpv.net");
|
||||
|
||||
foreach (string id in Registry.ClassesRoot.GetSubKeyNames())
|
||||
{
|
||||
if (id.StartsWith(exeFilenameNoExt + "."))
|
||||
Registry.ClassesRoot.DeleteSubKeyTree(id);
|
||||
|
||||
RegistryHelp.RemoveValue($@"HKCR\Software\Classes\{id}\OpenWithProgIDs", exeFilenameNoExt + id);
|
||||
RegistryHelp.RemoveValue($@"HKLM\Software\Classes\{id}\OpenWithProgIDs", exeFilenameNoExt + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/MpvNet.Windows/GlobalUsings.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Diagnostics;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
|
||||
global using static MpvNet.Global;
|
||||
303
src/MpvNet.Windows/GuiCommand.cs
Normal file
@@ -0,0 +1,303 @@
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows;
|
||||
using System.Globalization;
|
||||
|
||||
using MpvNet.ExtensionMethod;
|
||||
using MpvNet.Help;
|
||||
using MpvNet.Windows.WinForms;
|
||||
using MpvNet.Windows.WPF.Views;
|
||||
using MpvNet.Windows.WPF;
|
||||
using MpvNet.Windows.WPF.MsgBox;
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet;
|
||||
|
||||
public class GuiCommand
|
||||
{
|
||||
Dictionary<string, Action<IList<string>>>? _commands;
|
||||
|
||||
public event Action<float>? ScaleWindow;
|
||||
public event Action<string>? MoveWindow;
|
||||
public event Action<double>? WindowScaleMpv;
|
||||
public event Action<float>? WindowScaleNet;
|
||||
public event Action? ShowMenu;
|
||||
|
||||
public static GuiCommand Current { get; } = new();
|
||||
|
||||
public Dictionary<string, Action<IList<string>>> Commands => _commands ??= new()
|
||||
{
|
||||
["show-about"] = args => ShowDialog(typeof(AboutWindow)),
|
||||
["show-conf-editor"] = args => ShowDialog(typeof(ConfWindow)),
|
||||
["show-input-editor"] = args => ShowDialog(typeof(InputWindow)),
|
||||
["show-audio-devices"] = args => Msg.ShowInfo(Player.GetPropertyOsdString("audio-device-list")),
|
||||
["show-profiles"] = args => Msg.ShowInfo(Player.GetProfiles()),
|
||||
["load-sub"] = LoadSubtitle,
|
||||
["open-files"] = OpenFiles,
|
||||
["open-optical-media"] = Open_DVD_Or_BD_Folder,
|
||||
["load-audio"] = LoadAudio,
|
||||
["open-clipboard"] = OpenFromClipboard,
|
||||
["reg-file-assoc"] = RegisterFileAssociations,
|
||||
["scale-window"] = args => ScaleWindow?.Invoke(float.Parse(args[0], CultureInfo.InvariantCulture)),
|
||||
["show-media-info"] = ShowMediaInfo,
|
||||
["move-window"] = args => MoveWindow?.Invoke(args[0]),
|
||||
["window-scale"] = args => WindowScaleNet?.Invoke(float.Parse(args[0], CultureInfo.InvariantCulture)),
|
||||
["show-menu"] = args => ShowMenu?.Invoke(),
|
||||
["show-command-palette"] = args => ShowCommandPalette(),
|
||||
|
||||
|
||||
// backward compatibility
|
||||
["show-info"] = args => ShowMediaInfo(new[] { "osd" }), // backward compatibility
|
||||
["playlist-random"] = args => PlaylistRandom(), // backward compatibility
|
||||
["quick-bookmark"] = args => QuickBookmark(), // backward compatibility
|
||||
["show-commands"] = args => ShowCommands(), // backward compatibility
|
||||
["show-history"] = args => ShowHistory(), // backward compatibility
|
||||
["show-playlist"] = args => ShowPlaylist(), // backward compatibility
|
||||
};
|
||||
|
||||
public void ShowDialog(Type winType)
|
||||
{
|
||||
Window? win = Activator.CreateInstance(winType) as Window;
|
||||
new WindowInteropHelper(win).Owner = MainForm.Instance!.Handle;
|
||||
win?.ShowDialog();
|
||||
}
|
||||
|
||||
public void LoadSubtitle(IList<string> args)
|
||||
{
|
||||
using var dialog = new OpenFileDialog();
|
||||
string path = Player.GetPropertyString("path");
|
||||
|
||||
if (File.Exists(path))
|
||||
dialog.InitialDirectory = Path.GetDirectoryName(path);
|
||||
|
||||
dialog.Multiselect = true;
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK)
|
||||
foreach (string filename in dialog.FileNames)
|
||||
Player.CommandV("sub-add", filename);
|
||||
}
|
||||
|
||||
public void OpenFiles(IList<string> args)
|
||||
{
|
||||
bool append = false;
|
||||
|
||||
foreach (string arg in args)
|
||||
if (arg == "append")
|
||||
append = true;
|
||||
|
||||
using var dialog = new OpenFileDialog() { Multiselect = true };
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK)
|
||||
Player.LoadFiles(dialog.FileNames, true, append);
|
||||
}
|
||||
|
||||
public void Open_DVD_Or_BD_Folder(IList<string> args)
|
||||
{
|
||||
var dialog = new FolderBrowserDialog();
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK)
|
||||
Player.LoadDiskFolder(dialog.SelectedPath);
|
||||
}
|
||||
|
||||
public void OpenFromClipboard(IList<string> args)
|
||||
{
|
||||
if (System.Windows.Forms.Clipboard.ContainsFileDropList())
|
||||
{
|
||||
string[] files = System.Windows.Forms.Clipboard.GetFileDropList().Cast<string>().ToArray();
|
||||
Player.LoadFiles(files, false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
string clipboard = System.Windows.Forms.Clipboard.GetText();
|
||||
List<string> files = new List<string>();
|
||||
|
||||
foreach (string i in clipboard.Split(BR.ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
|
||||
if (i.Contains("://") || File.Exists(i))
|
||||
files.Add(i);
|
||||
|
||||
if (files.Count == 0)
|
||||
{
|
||||
Terminal.WriteError("The clipboard does not contain a valid URL or file.");
|
||||
return;
|
||||
}
|
||||
|
||||
Player.LoadFiles(files.ToArray(), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadAudio(IList<string> args)
|
||||
{
|
||||
using var dialog = new OpenFileDialog();
|
||||
string path = Player.GetPropertyString("path");
|
||||
|
||||
if (File.Exists(path))
|
||||
dialog.InitialDirectory = Path.GetDirectoryName(path);
|
||||
|
||||
dialog.Multiselect = true;
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK)
|
||||
foreach (string i in dialog.FileNames)
|
||||
Player.CommandV("audio-add", i);
|
||||
}
|
||||
|
||||
public void RegisterFileAssociations(IList<string> args)
|
||||
{
|
||||
string perceivedType = args[0];
|
||||
string[] extensions = Array.Empty<string>();
|
||||
|
||||
switch (perceivedType)
|
||||
{
|
||||
case "video": extensions = FileTypes.Video; break;
|
||||
case "audio": extensions = FileTypes.Audio; break;
|
||||
case "image": extensions = FileTypes.Image; break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using Process proc = new Process();
|
||||
proc.StartInfo.FileName = Environment.ProcessPath;
|
||||
proc.StartInfo.Arguments = "--register-file-associations " +
|
||||
perceivedType + " " + string.Join(" ", extensions);
|
||||
proc.StartInfo.Verb = "runas";
|
||||
proc.StartInfo.UseShellExecute = true;
|
||||
proc.Start();
|
||||
proc.WaitForExit();
|
||||
|
||||
if (proc.ExitCode == 0)
|
||||
Msg.ShowInfo("File associations were successfully " +
|
||||
(perceivedType == "unreg" ? "removed" : "created") +
|
||||
".\n\nFile Explorer icons will refresh after process restart.");
|
||||
else
|
||||
Msg.ShowError("Error creating file associations.");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void ShowMediaInfo(IList<string> args)
|
||||
{
|
||||
if (Player.PlaylistPos == -1)
|
||||
return;
|
||||
|
||||
bool full = args.Contains("full");
|
||||
bool raw = args.Contains("raw");
|
||||
bool editor = args.Contains("editor");
|
||||
bool osd = args.Contains("osd") || args == null || args.Count == 0;
|
||||
|
||||
long fileSize = 0;
|
||||
|
||||
string text = "";
|
||||
string path = Player.GetPropertyString("path");
|
||||
|
||||
if (File.Exists(path) && osd)
|
||||
{
|
||||
if (FileTypes.Audio.Contains(path.Ext()))
|
||||
{
|
||||
text = Player.GetPropertyOsdString("filtered-metadata");
|
||||
Player.CommandV("show-text", text, "5000");
|
||||
return;
|
||||
}
|
||||
else if (FileTypes.Image.Contains(path.Ext()))
|
||||
{
|
||||
fileSize = new FileInfo(path).Length;
|
||||
|
||||
text = "Width: " + Player.GetPropertyInt("width") + "\n" +
|
||||
"Height: " + Player.GetPropertyInt("height") + "\n" +
|
||||
"Size: " + Convert.ToInt32(fileSize / 1024.0) + " KB\n" +
|
||||
"Type: " + path.Ext().ToUpper();
|
||||
|
||||
Player.CommandV("show-text", text, "5000");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.Contains("://"))
|
||||
{
|
||||
if (path.Contains("://"))
|
||||
path = Player.GetPropertyString("media-title");
|
||||
string videoFormat = Player.GetPropertyString("video-format").ToUpper();
|
||||
string audioCodec = Player.GetPropertyString("audio-codec-name").ToUpper();
|
||||
int width = Player.GetPropertyInt("video-params/w");
|
||||
int height = Player.GetPropertyInt("video-params/h");
|
||||
TimeSpan len = TimeSpan.FromSeconds(Player.GetPropertyDouble("duration"));
|
||||
text = path.FileName() + "\n";
|
||||
text += FormatTime(len.TotalMinutes) + ":" + FormatTime(len.Seconds) + "\n";
|
||||
if (fileSize > 0)
|
||||
text += Convert.ToInt32(fileSize / 1024.0 / 1024.0) + " MB\n";
|
||||
text += $"{width} x {height}\n";
|
||||
text += $"{videoFormat}\n{audioCodec}";
|
||||
Player.CommandV("show-text", text, "5000");
|
||||
return;
|
||||
}
|
||||
|
||||
if (App.MediaInfo && !osd && File.Exists(path) && !path.Contains(@"\\.\pipe\"))
|
||||
using (MediaInfo mediaInfo = new MediaInfo(path))
|
||||
text = Regex.Replace(mediaInfo.GetSummary(full, raw), "Unique ID.+", "");
|
||||
else
|
||||
{
|
||||
Player.UpdateExternalTracks();
|
||||
text = "N: " + Player.GetPropertyString("filename") + BR;
|
||||
lock (Player.MediaTracksLock)
|
||||
foreach (MediaTrack track in Player.MediaTracks)
|
||||
text += track.Text + BR;
|
||||
}
|
||||
|
||||
text = text.TrimEx();
|
||||
|
||||
if (editor)
|
||||
ShowTextWithEditor("media-info", text);
|
||||
else if (osd)
|
||||
Command.ShowText(text.Replace("\r", ""), 5000, 16);
|
||||
else
|
||||
{
|
||||
MessageBoxEx.SetFont("Consolas");
|
||||
Msg.ShowInfo(text);
|
||||
MessageBoxEx.SetFont("Segoe UI");
|
||||
}
|
||||
}
|
||||
|
||||
public static string FormatTime(double value) => ((int)value).ToString("00");
|
||||
|
||||
public void ShowTextWithEditor(string name, string text)
|
||||
{
|
||||
string file = Path.Combine(Path.GetTempPath(), name + ".txt");
|
||||
App.TempFiles.Add(file);
|
||||
File.WriteAllText(file, BR + text.Trim() + BR);
|
||||
ProcessHelp.ShellExecute(file);
|
||||
}
|
||||
|
||||
public void ShowCommandPalette()
|
||||
{
|
||||
MainForm.Instance?.BeginInvoke(() => {
|
||||
CommandPalette.Instance.SetItems(CommandPalette.GetItems());
|
||||
MainForm.Instance.ShowCommandPalette();
|
||||
CommandPalette.Instance.SelectFirst();
|
||||
});
|
||||
}
|
||||
|
||||
// backward compatibility
|
||||
public void PlaylistRandom() =>
|
||||
Msg.ShowInfo("This feature was moved to a user script,\nwhich can be found here:\n\n" +
|
||||
"https://github.com/stax76/mpv-scripts/blob/main/misc.lua");
|
||||
|
||||
// backward compatibility
|
||||
public void QuickBookmark() =>
|
||||
Msg.ShowInfo("This feature was moved to a user script,\nwhich can be found here:\n\n" +
|
||||
"https://github.com/stax76/mpv-scripts/blob/main/misc.lua");
|
||||
|
||||
// backward compatibility
|
||||
public void ShowCommands() =>
|
||||
Msg.ShowInfo("This feature was moved to a user script,\nwhich can be found here:\n\n" +
|
||||
"https://github.com/stax76/mpv-scripts#command_palette");
|
||||
|
||||
// backward compatibility
|
||||
public void ShowHistory() =>
|
||||
Msg.ShowInfo("This feature was moved to a user script,\nwhich can be found here:\n\n" +
|
||||
"https://github.com/stax76/mpv-scripts/blob/main/history.lua");
|
||||
|
||||
// backward compatibility
|
||||
public void ShowPlaylist() =>
|
||||
Msg.ShowInfo("This feature was moved to a user script,\nwhich can be found here:\n\n" +
|
||||
"https://github.com/stax76/mpv-scripts#command_palette");
|
||||
}
|
||||
74
src/MpvNet.Windows/Help/RegistryHelp.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace MpvNet.Windows.Help;
|
||||
|
||||
public static class RegistryHelp
|
||||
{
|
||||
static string? _appKey;
|
||||
|
||||
public static string? ProductName { get; set; }
|
||||
|
||||
public static string AppKey {
|
||||
get
|
||||
{
|
||||
if (ProductName == null)
|
||||
throw new Exception("ProductName cannot be null.");
|
||||
|
||||
return _appKey ??= @"HKCU\Software\" + ProductName;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetInt(string name, object value) => SetValue(AppKey, name, value);
|
||||
|
||||
public static void SetString(string name, string value) => SetValue(AppKey, name, value);
|
||||
|
||||
public static void SetValue(string name, object value)
|
||||
{
|
||||
using RegistryKey regKey = GetRootKey(AppKey).CreateSubKey(AppKey[5..], RegistryKeyPermissionCheck.ReadWriteSubTree);
|
||||
regKey.SetValue(name, value);
|
||||
}
|
||||
|
||||
public static void SetValue(string path, string name, object value)
|
||||
{
|
||||
using RegistryKey regKey = GetRootKey(path).CreateSubKey(path[5..], RegistryKeyPermissionCheck.ReadWriteSubTree);
|
||||
regKey.SetValue(name, value);
|
||||
}
|
||||
|
||||
public static string GetString(string name, string defaultValue = "") =>
|
||||
GetValue(AppKey, name, defaultValue)?.ToString() ?? defaultValue;
|
||||
|
||||
public static int GetInt(string name, int defaultValue = 0) =>
|
||||
GetValue(AppKey, name, defaultValue) is int i ? i : defaultValue;
|
||||
|
||||
public static object? GetValue(string name) => GetValue(AppKey, name, null);
|
||||
|
||||
public static object? GetValue(string path, string name, object? defaultValue = null)
|
||||
{
|
||||
using RegistryKey? regKey = GetRootKey(path).OpenSubKey(path[5..]);
|
||||
return regKey?.GetValue(name, defaultValue);
|
||||
}
|
||||
|
||||
public static void RemoveKey(string path)
|
||||
{
|
||||
try {
|
||||
GetRootKey(path).DeleteSubKeyTree(path[5..], false);
|
||||
} catch { }
|
||||
}
|
||||
|
||||
public static void RemoveValue(string path, string name)
|
||||
{
|
||||
try {
|
||||
using RegistryKey? regKey = GetRootKey(path).OpenSubKey(path[5..], true);
|
||||
regKey?.DeleteValue(name, false);
|
||||
} catch { }
|
||||
}
|
||||
|
||||
static RegistryKey GetRootKey(string path) => path[..4] switch
|
||||
{
|
||||
"HKLM" => Registry.LocalMachine,
|
||||
"HKCU" => Registry.CurrentUser,
|
||||
"HKCR" => Registry.ClassesRoot,
|
||||
_ => throw new Exception(),
|
||||
};
|
||||
}
|
||||
15
src/MpvNet.Windows/Misc.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using MpvNet.ExtensionMethod;
|
||||
|
||||
namespace MpvNet.Windows;
|
||||
|
||||
public class Misc
|
||||
{
|
||||
public static void CopyMpvnetCom()
|
||||
{
|
||||
string dir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData).AddSep() +
|
||||
"Microsoft\\WindowsApps\\";
|
||||
|
||||
if (File.Exists(dir + "MpvNet.exe") && !File.Exists(dir + "MpvNet.com"))
|
||||
File.Copy(Folder.Startup + "MpvNet.com", dir + "MpvNet.com");
|
||||
}
|
||||
}
|
||||
43
src/MpvNet.Windows/MpvNet.Windows.csproj
Normal file
@@ -0,0 +1,43 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<RootNamespace>MpvNet.Windows</RootNamespace>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AssemblyName>mpvnet</AssemblyName>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>mpv-icon.ico</ApplicationIcon>
|
||||
<Product>mpv.net</Product>
|
||||
<AssemblyVersion>7.0.0.0</AssemblyVersion>
|
||||
<FileVersion>7.0.0.0</FileVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="mpv-icon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MpvNet\MpvNet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="WPF\Views\AboutWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<XamlRuntime>Wpf</XamlRuntime>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Misc\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
130
src/MpvNet.Windows/Native/StockIcon.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public class StockIcon
|
||||
{
|
||||
[DllImport("shell32.dll")]
|
||||
public static extern int SHGetStockIconInfo(SHSTOCKICONID siid, SHSTOCKICONFLAGS uFlags, ref SHSTOCKICONINFO info);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool DestroyIcon(IntPtr handle);
|
||||
|
||||
public static IntPtr GetIcon(SHSTOCKICONID identifier, SHSTOCKICONFLAGS flags)
|
||||
{
|
||||
SHSTOCKICONINFO info = new SHSTOCKICONINFO();
|
||||
info.cbSize = Convert.ToUInt32(Marshal.SizeOf(typeof(SHSTOCKICONINFO)));
|
||||
Marshal.ThrowExceptionForHR(SHGetStockIconInfo(identifier, flags, ref info));
|
||||
return info.hIcon;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct SHSTOCKICONINFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public IntPtr hIcon;
|
||||
int iSysImageIndex;
|
||||
int iIcon;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
string szPath;
|
||||
}
|
||||
|
||||
public enum SHSTOCKICONFLAGS : uint
|
||||
{
|
||||
SHGSI_ICONLOCATION = 0,
|
||||
SHGSI_ICON = 0x000000100,
|
||||
SHGSI_SYSICONINDEX = 0x000004000,
|
||||
SHGSI_LINKOVERLAY = 0x000008000,
|
||||
SHGSI_SELECTED = 0x000010000,
|
||||
SHGSI_LARGEICON = 0x000000000,
|
||||
SHGSI_SMALLICON = 0x000000001,
|
||||
SHGSI_SHELLICONSIZE = 0x000000004
|
||||
}
|
||||
|
||||
public enum SHSTOCKICONID : uint
|
||||
{
|
||||
DocumentNotAssociated = 0,
|
||||
DocumentAssociated = 1,
|
||||
Application = 2,
|
||||
Folder = 3,
|
||||
FolderOpen = 4,
|
||||
Drive525 = 5,
|
||||
Drive35 = 6,
|
||||
DriveRemove = 7,
|
||||
DriveFixed = 8,
|
||||
DriveNetwork = 9,
|
||||
DriveNetworkDisabled = 10,
|
||||
DriveCD = 11,
|
||||
DriveRAM = 12,
|
||||
World = 13,
|
||||
Server = 15,
|
||||
Printer = 16,
|
||||
MyNetwork = 17,
|
||||
Find = 22,
|
||||
Help = 23,
|
||||
Share = 28,
|
||||
Link = 29,
|
||||
SlowFile = 30,
|
||||
Recycler = 31,
|
||||
RecyclerFull = 32,
|
||||
MediaCDAudio = 40,
|
||||
Lock = 47,
|
||||
AutoList = 49,
|
||||
PrinterNet = 50,
|
||||
ServerShare = 51,
|
||||
PrinterFax = 52,
|
||||
PrinterFaxNet = 53,
|
||||
PrinterFile = 54,
|
||||
Stack = 55,
|
||||
MediaSVCD = 56,
|
||||
StuffedFolder = 57,
|
||||
DriveUnknown = 58,
|
||||
DriveDVD = 59,
|
||||
MediaDVD = 60,
|
||||
MediaDVDRAM = 61,
|
||||
MediaDVDRW = 62,
|
||||
MediaDVDR = 63,
|
||||
MediaDVDROM = 64,
|
||||
MediaCDAudioPlus = 65,
|
||||
MediaCDRW = 66,
|
||||
MediaCDR = 67,
|
||||
MediaCDBurn = 68,
|
||||
MediaBlankCD = 69,
|
||||
MediaCDROM = 70,
|
||||
AudioFiles = 71,
|
||||
ImageFiles = 72,
|
||||
VideoFiles = 73,
|
||||
MixedFiles = 74,
|
||||
FolderBack = 75,
|
||||
FolderFront = 76,
|
||||
Shield = 77,
|
||||
Warning = 78,
|
||||
Info = 79,
|
||||
Error = 80,
|
||||
Key = 81,
|
||||
Software = 82,
|
||||
Rename = 83,
|
||||
Delete = 84,
|
||||
MediaAudioDVD = 85,
|
||||
MediaMovieDVD = 86,
|
||||
MediaEnhancedCD = 87,
|
||||
MediaEnhancedDVD = 88,
|
||||
MediaHDDVD = 89,
|
||||
MediaBluRay = 90,
|
||||
MediaVCD = 91,
|
||||
MediaDVDPlusR = 92,
|
||||
MediaDVDPlusRW = 93,
|
||||
DesktopPC = 94,
|
||||
MobilePC = 95,
|
||||
Users = 96,
|
||||
MediaSmartMedia = 97,
|
||||
MediaCompactFlash = 98,
|
||||
DeviceCellPhone = 99,
|
||||
DeviceCamera = 100,
|
||||
DeviceVideoCamera = 101,
|
||||
DeviceAudioPlayer = 102,
|
||||
NetworkConnect = 103,
|
||||
Internet = 104,
|
||||
ZipFile = 105,
|
||||
Settings = 106
|
||||
}
|
||||
}
|
||||
54
src/MpvNet.Windows/Native/Taskbar.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public class Taskbar
|
||||
{
|
||||
public IntPtr Handle { get; set; }
|
||||
|
||||
public Taskbar(IntPtr handle) => Handle = handle;
|
||||
|
||||
ITaskbarList3 Instance = (ITaskbarList3)new TaskBarCommunication();
|
||||
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF")]
|
||||
interface ITaskbarList3
|
||||
{
|
||||
// ITaskbarList
|
||||
[PreserveSig] void HrInit();
|
||||
[PreserveSig] void AddTab(IntPtr hwnd);
|
||||
[PreserveSig] void DeleteTab(IntPtr hwnd);
|
||||
[PreserveSig] void ActivateTab(IntPtr hwnd);
|
||||
[PreserveSig] void SetActiveAlt(IntPtr hwnd);
|
||||
// ITaskbarList2
|
||||
[PreserveSig] void MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen);
|
||||
// ITaskbarList3
|
||||
[PreserveSig] void SetProgressValue(IntPtr hwnd, ulong ullCompleted, ulong ullTotal);
|
||||
[PreserveSig] void SetProgressState(IntPtr hwnd, TaskbarStates state);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[ClassInterface(ClassInterfaceType.None)]
|
||||
[Guid("56FDF344-FD6D-11d0-958A-006097C9A090")]
|
||||
class TaskBarCommunication
|
||||
{
|
||||
}
|
||||
|
||||
public void SetState(TaskbarStates taskbarState)
|
||||
{
|
||||
Instance.SetProgressState(Handle, taskbarState);
|
||||
}
|
||||
|
||||
public void SetValue(double progressValue, double progressMax)
|
||||
{
|
||||
Instance.SetProgressValue(Handle, (ulong)progressValue, (ulong)progressMax);
|
||||
}
|
||||
}
|
||||
|
||||
public enum TaskbarStates
|
||||
{
|
||||
NoProgress = 0,
|
||||
Indeterminate = 0x1,
|
||||
Normal = 0x2,
|
||||
Error = 0x4,
|
||||
Paused = 0x8
|
||||
}
|
||||
211
src/MpvNet.Windows/Native/WinApi.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MpvNet.Windows.Native;
|
||||
|
||||
public static class WinApi
|
||||
{
|
||||
public static Version WindowsTen1607 { get; } = new Version(10, 0, 14393); // Windows 10 1607
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool AttachConsole(int dwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool FreeConsole();
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr LoadLibrary(string path);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern uint ActivateKeyboardLayout(IntPtr hkl, uint flags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool GetWindowRect(IntPtr hwnd, out Rect lpRect);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr FindWindowEx(
|
||||
IntPtr parentHandle, IntPtr childAfter, string lclassName, string? windowTitle);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref CopyDataStruct lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern int RegisterWindowMessage(string id);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool AllowSetForegroundWindow(int dwProcessId);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern void ReleaseCapture();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int GetDpiForWindow(IntPtr hwnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool AdjustWindowRect(ref Rect lpRect, uint dwStyle, bool bMenu);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool AdjustWindowRectExForDpi(
|
||||
ref Rect lpRect, uint dwStyle, bool bMenu, uint dwExStyle, uint dpi);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetWindowPos(
|
||||
IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
|
||||
static extern IntPtr GetWindowLong32(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
|
||||
|
||||
public static IntPtr GetWindowLong(IntPtr hWnd, int nIndex)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
return GetWindowLongPtr(hWnd, nIndex);
|
||||
else
|
||||
return GetWindowLong32(hWnd, nIndex);
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
|
||||
public static extern IntPtr SetWindowLong32(IntPtr hWnd, int nIndex, uint dwNewLong);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, uint dwNewLong);
|
||||
|
||||
public static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
return SetWindowLongPtr(hWnd, nIndex, dwNewLong);
|
||||
else
|
||||
return SetWindowLong32(hWnd, nIndex, dwNewLong);
|
||||
}
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
|
||||
|
||||
[DllImport("dwmapi.dll")]
|
||||
public static extern int DwmGetWindowAttribute(
|
||||
IntPtr hwnd, uint dwAttribute, out Rect pvAttribute, uint cbAttribute);
|
||||
|
||||
public static bool GetDwmWindowRect(IntPtr handle, out Rect rect)
|
||||
{
|
||||
const uint DWMWA_EXTENDED_FRAME_BOUNDS = 9;
|
||||
|
||||
return 0 == DwmGetWindowAttribute(handle, DWMWA_EXTENDED_FRAME_BOUNDS,
|
||||
out rect, (uint)Marshal.SizeOf<Rect>());
|
||||
}
|
||||
|
||||
public static Rectangle GetWorkingArea(IntPtr handle, Rectangle workingArea)
|
||||
{
|
||||
if (handle != IntPtr.Zero && GetDwmWindowRect(handle, out Rect dwmRect) &&
|
||||
GetWindowRect(handle, out Rect rect))
|
||||
{
|
||||
int left = workingArea.Left;
|
||||
int top = workingArea.Top;
|
||||
int right = workingArea.Right;
|
||||
int bottom = workingArea.Bottom;
|
||||
|
||||
left += rect.Left - dwmRect.Left;
|
||||
top -= rect.Top - dwmRect.Top;
|
||||
right -= dwmRect.Right - rect.Right;
|
||||
bottom -= dwmRect.Bottom - rect.Bottom;
|
||||
|
||||
return new Rectangle(left, top, right - left, bottom - top);
|
||||
}
|
||||
|
||||
return workingArea;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Rect
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
|
||||
public Rect(Rectangle r)
|
||||
{
|
||||
Left = r.Left;
|
||||
Top = r.Top;
|
||||
Right = r.Right;
|
||||
Bottom = r.Bottom;
|
||||
}
|
||||
|
||||
public Rect(int left, int top, int right, int bottom)
|
||||
{
|
||||
Left = left;
|
||||
Top = top;
|
||||
Right = right;
|
||||
Bottom = bottom;
|
||||
}
|
||||
|
||||
public Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, Bottom);
|
||||
public Size Size => new Size(Right - Left, Bottom - Top);
|
||||
public int Width => Right - Left;
|
||||
public int Height => Bottom - Top;
|
||||
|
||||
public static Rect FromRectangle(Rectangle rect)
|
||||
{
|
||||
return new Rect(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{Left=" + Left + ",Top=" + Top + ",Right=" + Right + ",Bottom=" + Bottom + "}";
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CopyDataStruct
|
||||
{
|
||||
public IntPtr dwData;
|
||||
public int cbData;
|
||||
[MarshalAs(UnmanagedType.LPTStr)]
|
||||
public string lpData;
|
||||
}
|
||||
|
||||
public static int GetResizeBorder(int v)
|
||||
{
|
||||
switch (v)
|
||||
{
|
||||
case 1 /* WMSZ_LEFT */ : return 3;
|
||||
case 3 /* WMSZ_TOP */ : return 2;
|
||||
case 2 /* WMSZ_RIGHT */ : return 3;
|
||||
case 6 /* WMSZ_BOTTOM */ : return 2;
|
||||
case 4 /* WMSZ_TOPLEFT */ : return 1;
|
||||
case 5 /* WMSZ_TOPRIGHT */ : return 1;
|
||||
case 7 /* WMSZ_BOTTOMLEFT */ : return 3;
|
||||
case 8 /* WMSZ_BOTTOMRIGHT */ : return 3;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SubtractWindowBorders(IntPtr hwnd, ref Rect rc, int dpi)
|
||||
{
|
||||
Rect r = new Rect(0, 0, 0, 0);
|
||||
AddWindowBorders(hwnd, ref r, dpi);
|
||||
rc.Left -= r.Left;
|
||||
rc.Top -= r.Top;
|
||||
rc.Right -= r.Right;
|
||||
rc.Bottom -= r.Bottom;
|
||||
}
|
||||
|
||||
public static void AddWindowBorders(IntPtr hwnd, ref Rect rc, int dpi)
|
||||
{
|
||||
uint windowStyle = (uint)GetWindowLong(hwnd, -16); // GWL_STYLE
|
||||
uint windowStyleEx = (uint)GetWindowLong(hwnd, -20); // GWL_EXSTYLE
|
||||
|
||||
if (Environment.OSVersion.Version >= WindowsTen1607)
|
||||
AdjustWindowRectExForDpi(ref rc, windowStyle, false, windowStyleEx, (uint)dpi);
|
||||
else
|
||||
AdjustWindowRect(ref rc, windowStyle, false);
|
||||
}
|
||||
}
|
||||
BIN
src/MpvNet.Windows/Package/Images/LockScreenLogo.scale-200.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src/MpvNet.Windows/Package/Images/SplashScreen.scale-200.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 18 KiB |
BIN
src/MpvNet.Windows/Package/Images/Square44x44Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/MpvNet.Windows/Package/Images/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/MpvNet.Windows/Package/Images/Wide310x150Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
137
src/MpvNet.Windows/Package/Package.appxmanifest
Normal file
@@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="5664FrankSkare.mpv.net"
|
||||
Publisher="CN=6A1A1E69-736C-4C77-B310-7B6D38E32617"
|
||||
Version="6.0.3.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>mpv.net</DisplayName>
|
||||
<PublisherDisplayName>Frank Skare</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="mpv.net"
|
||||
Description="mpv.net is a modern media player based on the popular mpv player."
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.appExecutionAlias">
|
||||
<uap3:AppExecutionAlias>
|
||||
<desktop:ExecutionAlias Alias="MpvNet.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
</uap3:Extension>
|
||||
|
||||
<uap:Extension Category="windows.fileTypeAssociation">
|
||||
<uap:FileTypeAssociation Name="videotypes">
|
||||
<uap:SupportedFileTypes>
|
||||
<uap:FileType>.264</uap:FileType>
|
||||
<uap:FileType>.265</uap:FileType>
|
||||
<uap:FileType>.asf</uap:FileType>
|
||||
<uap:FileType>.avc</uap:FileType>
|
||||
<uap:FileType>.avi</uap:FileType>
|
||||
<uap:FileType>.avs</uap:FileType>
|
||||
<uap:FileType>.dav</uap:FileType>
|
||||
<uap:FileType>.flv</uap:FileType>
|
||||
<uap:FileType>.h264</uap:FileType>
|
||||
<uap:FileType>.h265</uap:FileType>
|
||||
<uap:FileType>.hevc</uap:FileType>
|
||||
<uap:FileType>.m2t</uap:FileType>
|
||||
<uap:FileType>.m2ts</uap:FileType>
|
||||
<uap:FileType>.m2v</uap:FileType>
|
||||
<uap:FileType>.m4v</uap:FileType>
|
||||
<uap:FileType>.mkv</uap:FileType>
|
||||
<uap:FileType>.mov</uap:FileType>
|
||||
<uap:FileType>.mp4</uap:FileType>
|
||||
<uap:FileType>.mpeg</uap:FileType>
|
||||
<uap:FileType>.mpg</uap:FileType>
|
||||
<uap:FileType>.mpv</uap:FileType>
|
||||
<uap:FileType>.mts</uap:FileType>
|
||||
<uap:FileType>.ts</uap:FileType>
|
||||
<uap:FileType>.vob</uap:FileType>
|
||||
<uap:FileType>.vpy</uap:FileType>
|
||||
<uap:FileType>.webm</uap:FileType>
|
||||
<uap:FileType>.wmv</uap:FileType>
|
||||
<uap:FileType>.y4m</uap:FileType>
|
||||
</uap:SupportedFileTypes>
|
||||
</uap:FileTypeAssociation>
|
||||
</uap:Extension>
|
||||
|
||||
<uap:Extension Category="windows.fileTypeAssociation">
|
||||
<uap:FileTypeAssociation Name="audiotypes">
|
||||
<uap:SupportedFileTypes>
|
||||
<uap:FileType>.aac</uap:FileType>
|
||||
<uap:FileType>.ac3</uap:FileType>
|
||||
<uap:FileType>.dts</uap:FileType>
|
||||
<uap:FileType>.dtshd</uap:FileType>
|
||||
<uap:FileType>.dtshr</uap:FileType>
|
||||
<uap:FileType>.dtsma</uap:FileType>
|
||||
<uap:FileType>.eac3</uap:FileType>
|
||||
<uap:FileType>.flac</uap:FileType>
|
||||
<uap:FileType>.m4a</uap:FileType>
|
||||
<uap:FileType>.mka</uap:FileType>
|
||||
<uap:FileType>.mp2</uap:FileType>
|
||||
<uap:FileType>.mp3</uap:FileType>
|
||||
<uap:FileType>.mpa</uap:FileType>
|
||||
<uap:FileType>.mpc</uap:FileType>
|
||||
<uap:FileType>.ogg</uap:FileType>
|
||||
<uap:FileType>.opus</uap:FileType>
|
||||
<uap:FileType>.thd</uap:FileType>
|
||||
<uap:FileType>.w64</uap:FileType>
|
||||
<uap:FileType>.wav</uap:FileType>
|
||||
</uap:SupportedFileTypes>
|
||||
</uap:FileTypeAssociation>
|
||||
</uap:Extension>
|
||||
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="ytdl" />
|
||||
</uap:Extension>
|
||||
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="rtsp" />
|
||||
</uap:Extension>
|
||||
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="srt" />
|
||||
</uap:Extension>
|
||||
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="srtp" />
|
||||
</uap:Extension>
|
||||
</Extensions>
|
||||
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
78
src/MpvNet.Windows/Package/mpv.net.package.wapproj
Normal file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '15.0'">
|
||||
<VisualStudioVersion>15.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>81daee3a-76ff-4494-9384-d28a651d70bb</ProjectGuid>
|
||||
<TargetPlatformVersion>10.0.22000.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.14393.0</TargetPlatformMinVersion>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
|
||||
<EntryPointProjectUniqueName>..\mpv.net.csproj</EntryPointProjectUniqueName>
|
||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
||||
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
||||
<AppxBundlePlatforms>x64</AppxBundlePlatforms>
|
||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="..\bin\MediaInfo.dll">
|
||||
<Link>mpv.net\MediaInfo.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\bin\Microsoft.Management.Infrastructure.dll">
|
||||
<Link>mpv.net\Microsoft.Management.Infrastructure.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\bin\libmpv-2.dll">
|
||||
<Link>mpv.net\libmpv-2.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\bin\mpvnet.com">
|
||||
<Link>mpv.net\mpvnet.com</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Images\SplashScreen.scale-200.png" />
|
||||
<Content Include="Images\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Images\StoreLogo.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-200.png" />
|
||||
<None Include="Package.StoreAssociation.xml" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.19041.8" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\mpv.net.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
105
src/MpvNet.Windows/Program.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
|
||||
using System.Windows.Forms;
|
||||
using System.Threading;
|
||||
|
||||
using MpvNet.Windows.Native;
|
||||
using MpvNet.Help;
|
||||
using MpvNet.Windows.UI;
|
||||
using MpvNet.Windows.Help;
|
||||
|
||||
namespace MpvNet.Windows;
|
||||
|
||||
static class Program
|
||||
{
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
try
|
||||
{
|
||||
RegistryHelp.ProductName = AppInfo.Product;
|
||||
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e) => Terminal.WriteError(e.ExceptionObject);
|
||||
Application.ThreadException += (sender, e) => Terminal.WriteError(e.Exception);
|
||||
|
||||
if (App.IsTerminalAttached)
|
||||
WinApi.AttachConsole(-1 /*ATTACH_PARENT_PROCESS*/);
|
||||
|
||||
string[] args = Environment.GetCommandLineArgs().Skip(1).ToArray();
|
||||
|
||||
if (args.Length > 0 && args[0] == "--register-file-associations")
|
||||
{
|
||||
FileAssociation.Register(args[1], args.Skip(1).ToArray());
|
||||
return;
|
||||
}
|
||||
|
||||
App.Init();
|
||||
Theme.Init();
|
||||
Mutex mutex = new Mutex(true, StringHelp.GetMD5Hash(App.ConfPath), out bool isFirst);
|
||||
|
||||
if (Control.ModifierKeys.HasFlag(Keys.Shift))
|
||||
App.ProcessInstance = "multi";
|
||||
|
||||
if ((App.ProcessInstance == "single" || App.ProcessInstance == "queue") && !isFirst)
|
||||
{
|
||||
List<string> args2 = new List<string> { App.ProcessInstance };
|
||||
|
||||
foreach (string arg in args)
|
||||
{
|
||||
if (!arg.StartsWith("--") && (arg == "-" || arg.Contains("://") ||
|
||||
arg.Contains(":\\") || arg.StartsWith("\\\\")))
|
||||
|
||||
args2.Add(arg);
|
||||
else if (arg == "--queue")
|
||||
args2[0] = "queue";
|
||||
else if (arg.StartsWith("--command="))
|
||||
{
|
||||
args2[0] = "command";
|
||||
args2.Add(arg[10..]);
|
||||
}
|
||||
}
|
||||
|
||||
Process[] procs = Process.GetProcessesByName("mpvnet");
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
foreach (Process proc in procs)
|
||||
{
|
||||
if (proc.MainWindowHandle != IntPtr.Zero)
|
||||
{
|
||||
WinApi.AllowSetForegroundWindow(proc.Id);
|
||||
var data = new WinApi.CopyDataStruct();
|
||||
data.lpData = string.Join("\n", args2.ToArray());
|
||||
data.cbData = data.lpData.Length * 2 + 1;
|
||||
WinApi.SendMessage(proc.MainWindowHandle, 0x004A /*WM_COPYDATA*/, IntPtr.Zero, ref data);
|
||||
mutex.Dispose();
|
||||
|
||||
if (App.IsTerminalAttached)
|
||||
WinApi.FreeConsole();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
|
||||
mutex.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
Application.Run(new WinForms.MainForm());
|
||||
|
||||
if (App.IsTerminalAttached)
|
||||
WinApi.FreeConsole();
|
||||
|
||||
mutex.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Terminal.WriteError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
118
src/MpvNet.Windows/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,118 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace MpvNet.Windows.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[DebuggerNonUserCode()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MpvNet.Windows.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to
|
||||
///[setting]
|
||||
///name = hwdec
|
||||
///file = mpv
|
||||
///default = no
|
||||
///filter = Video
|
||||
///url = https://mpv.io/manual/master/#options-hwdec
|
||||
///help = Specify the hardware video decoding API that should be used if possible. Whether hardware decoding is actually done depends on the video codec. If hardware decoding is not possible, mpv will fall back on software decoding.\n\nFor more information visit:
|
||||
///
|
||||
///option = no always use software decoding
|
||||
///option = auto enable best hw decoder
|
||||
///option = yes exact [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
internal static string editor_conf {
|
||||
get {
|
||||
return ResourceManager.GetString("editor_conf", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to
|
||||
///[dark]
|
||||
///
|
||||
///heading = #3C8CC8
|
||||
///foreground = #DDDDDD
|
||||
///foreground2 = #AAAAAA
|
||||
///background = #323232
|
||||
///highlight = #404040
|
||||
///
|
||||
///menu-foreground = #DDDDDD
|
||||
///menu-background = #323232
|
||||
///menu-highlight = #505050
|
||||
///
|
||||
///
|
||||
///[light]
|
||||
///
|
||||
///heading = #0068B2
|
||||
///foreground = #000000
|
||||
///foreground2 = #4C4C4C
|
||||
///background = #F7F7F7
|
||||
///highlight = #DFDFDF
|
||||
///
|
||||
///menu-foreground = #000000
|
||||
///menu-background = #DFDFDF
|
||||
///menu-highlight = #BFBFBF
|
||||
///.
|
||||
/// </summary>
|
||||
internal static string theme {
|
||||
get {
|
||||
return ResourceManager.GetString("theme", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/MpvNet.Windows/Properties/Resources.resx
Normal file
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="editor_conf" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\editor_conf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
|
||||
</data>
|
||||
<data name="theme" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\theme.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
|
||||
</data>
|
||||
</root>
|
||||
21
src/MpvNet.Windows/Release.ps1
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
$tmpDir = 'D:\Work'
|
||||
$exePath = $PSScriptRoot + '\bin\mpvnet.exe'
|
||||
$versionInfo = [Diagnostics.FileVersionInfo]::GetVersionInfo($exePath)
|
||||
$7z = 'C:\Program Files\7-Zip\7z.exe'
|
||||
|
||||
$targetDir = $tmpDir + "\mpvnet-$($versionInfo.FileVersion)-beta"
|
||||
Copy-Item $PSScriptRoot\bin $targetDir -Recurse -Exclude System.Management.Automation.xml
|
||||
|
||||
$folders = 'Debug', 'Release', 'x64', 'x86', 'Arm'
|
||||
|
||||
foreach ($folder in $folders) {
|
||||
Remove-Item (Join-Path $targetDir $folder) -Recurse -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
& $7z a -tzip -mx9 "$targetDir.zip" -r "$targetDir\*"
|
||||
|
||||
if ($LastExitCode)
|
||||
{ throw $LastExitCode }
|
||||
|
||||
Write-Host 'successfully finished' -ForegroundColor Green
|
||||
617
src/MpvNet.Windows/Resources/editor_conf.txt
Normal file
@@ -0,0 +1,617 @@
|
||||
|
||||
name = process-instance
|
||||
file = mpvnet
|
||||
default = single
|
||||
directory = General
|
||||
help = Defines if more then one mpv.net process is allowed. (mpv.net option)\n\nMulti can alternatively be enabled by pressing the SHIFT key.
|
||||
option = multi Create a new process everytime the shell starts mpv.net
|
||||
option = single Force a single process everytime the shell starts mpv.net
|
||||
option = queue Force a single process and add files to playlist
|
||||
|
||||
name = recent-count
|
||||
file = mpvnet
|
||||
directory = General
|
||||
help = <int> Amount of recent files to be remembered. Default: 15 (mpv.net option)
|
||||
|
||||
name = media-info
|
||||
file = mpvnet
|
||||
default = yes
|
||||
directory = General
|
||||
help = Usage of the media info library instead of mpv to access media information. (mpv.net option)
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = video-file-extensions
|
||||
file = mpvnet
|
||||
directory = General
|
||||
width = 500
|
||||
help = Video file extensions used to create file associations and used by the auto-load-folder feature. (mpv.net option)
|
||||
|
||||
name = audio-file-extensions
|
||||
file = mpvnet
|
||||
directory = General
|
||||
width = 500
|
||||
help = Audio file extensions used to create file associations and used by the auto-load-folder feature. (mpv.net option)
|
||||
|
||||
name = image-file-extensions
|
||||
file = mpvnet
|
||||
directory = General
|
||||
width = 500
|
||||
help = Image file extensions used to create file associations and used by the auto-load-folder feature. (mpv.net option)
|
||||
|
||||
name = debug-mode
|
||||
file = mpvnet
|
||||
default = no
|
||||
directory = General
|
||||
help = Enable this only when a developer asks for it. (mpv.net option)
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = vo
|
||||
file = mpv
|
||||
default = gpu
|
||||
directory = Video
|
||||
help = Video output drivers to be used.\n\nFor more information visit:
|
||||
url = https://mpv.io/manual/master/#video-output-drivers-vo
|
||||
option = gpu General purpose, customizable, GPU-accelerated video output driver. It supports extended scaling methods, dithering, color management, custom shaders, HDR, and more.
|
||||
option = gpu-next Experimental video renderer based on libplacebo. This supports almost the same set of features as --vo=gpu.
|
||||
option = direct3d Video output driver that uses the Direct3D interface.
|
||||
|
||||
name = hwdec
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Video
|
||||
url = https://mpv.io/manual/master/#options-hwdec
|
||||
help = Specify the hardware video decoding API that should be used if possible. Whether hardware decoding is actually done depends on the video codec. If hardware decoding is not possible, mpv will fall back on software decoding.\n\nFor more information visit:
|
||||
option = no always use software decoding
|
||||
option = auto enable best hw decoder
|
||||
option = yes exactly the same as auto
|
||||
option = auto-copy enable best hw decoder with copy-back
|
||||
option = auto-safe enable any whitelisted hw decoder
|
||||
option = dxva2 requires vo=gpu with gpu-context=d3d11, gpu-context=angle or gpu-context=dxinterop (Windows only)
|
||||
option = dxva2-copy copies video back to system RAM (Windows only)
|
||||
option = d3d11va requires vo=gpu with gpu-context=d3d11 or gpu-context=angle (Windows 8+ only)
|
||||
option = d3d11va-copy copies video back to system RAM (Windows 8+ only)
|
||||
option = cuda requires vo=gpu (Any platform CUDA is available)
|
||||
option = cuda-copy copies video back to system RAM (Any platform CUDA is available)
|
||||
option = nvdec requires vo=gpu (Any platform CUDA is available)
|
||||
option = nvdec-copy copies video back to system RAM (Any platform CUDA is available)
|
||||
|
||||
name = gpu-api
|
||||
file = mpv
|
||||
default = auto
|
||||
directory = Video
|
||||
help = Controls which type of graphics APIs will be accepted. Auto uses d3d11, it should only be changed in case of problems, Vulkan is not recommended.
|
||||
option = auto Use any available API
|
||||
option = d3d11 Allow only gpu-context=d3d11
|
||||
option = opengl Allow only OpenGL (requires OpenGL 2.1+ or GLES 2.0+)
|
||||
option = vulkan Allow only Vulkan
|
||||
|
||||
name = gpu-context
|
||||
file = mpv
|
||||
default = auto
|
||||
directory = Video
|
||||
option = auto auto-select
|
||||
option = d3d11 Win32, with native Direct3D 11 rendering.
|
||||
option = angle Direct3D11 through the OpenGL ES translation layer ANGLE. This supports almost everything the win backend does (if the ANGLE build is new enough).
|
||||
option = win Win32/WGL
|
||||
option = dxinterop (experimental) Win32, using WGL for rendering and Direct3D 9Ex for presentation. Works on Nvidia and AMD. Newer Intel chips with the latest drivers may also work.
|
||||
option = winvk VK_KHR_win32_surface
|
||||
|
||||
name = video-sync
|
||||
file = mpv
|
||||
default = audio
|
||||
directory = Video
|
||||
help = How the player synchronizes audio and video.\n\nFor more information visit:
|
||||
url = https://mpv.io/manual/master/#options-video-sync
|
||||
option = audio
|
||||
option = display-resample
|
||||
option = display-resample-vdrop
|
||||
option = display-resample-desync
|
||||
option = display-vdrop
|
||||
option = display-adrop
|
||||
option = display-desync
|
||||
option = desync
|
||||
|
||||
name = scale
|
||||
file = mpv
|
||||
default = bilinear
|
||||
directory = Video
|
||||
help = The GPU renderer filter function to use when upscaling video. There are some more filters, but most are not as useful. For a complete list, pass help as value, e.g.: mpv --scale=help
|
||||
option = bilinear Bilinear hardware texture filtering (fastest, very low quality).
|
||||
option = spline36 Mid quality and speed. This is the default when using gpu-hq.
|
||||
option = lanczos Lanczos scaling. Provides mid quality and speed. Generally worse than spline36, but it results in a slightly sharper image which is good for some content types. The number of taps can be controlled with scale-radius, but is best left unchanged. (This filter is an alias for sinc-windowed sinc)
|
||||
option = ewa_lanczos Elliptic weighted average Lanczos scaling. Also known as Jinc. Relatively slow, but very good quality. The radius can be controlled with scale-radius. Increasing the radius makes the filter sharper but adds more ringing. (This filter is an alias for jinc-windowed jinc)
|
||||
option = ewa_lanczossharp A slightly sharpened version of ewa_lanczos, preconfigured to use an ideal radius and parameter. If your hardware can run it, this is probably what you should use by default.
|
||||
option = mitchell Mitchell-Netravali. The B and C parameters can be set with scale-param1 and scale-param2. This filter is very good at downscaling (see dscale).
|
||||
option = oversample A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as "smoothmotion" (see tscale).
|
||||
|
||||
name = cscale
|
||||
file = mpv
|
||||
default = bilinear
|
||||
directory = Video
|
||||
help = As scale, but for interpolating chroma information. If the image is not subsampled, this option is ignored entirely.
|
||||
option = bilinear Bilinear hardware texture filtering (fastest, very low quality).
|
||||
option = spline36 Mid quality and speed. This is the default when using gpu-hq.
|
||||
option = lanczos Lanczos scaling. Provides mid quality and speed. Generally worse than spline36, but it results in a slightly sharper image which is good for some content types. The number of taps can be controlled with scale-radius, but is best left unchanged. (This filter is an alias for sinc-windowed sinc)
|
||||
option = ewa_lanczos Elliptic weighted average Lanczos scaling. Also known as Jinc. Relatively slow, but very good quality. The radius can be controlled with scale-radius. Increasing the radius makes the filter sharper but adds more ringing. (This filter is an alias for jinc-windowed jinc)
|
||||
option = ewa_lanczossharp A slightly sharpened version of ewa_lanczos, preconfigured to use an ideal radius and parameter. If your hardware can run it, this is probably what you should use by default.
|
||||
option = mitchell Mitchell-Netravali. The B and C parameters can be set with scale-param1 and scale-param2. This filter is very good at downscaling (see dscale).
|
||||
option = oversample A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as "smoothmotion" (see tscale).
|
||||
|
||||
name = dscale
|
||||
file = mpv
|
||||
default =
|
||||
directory = Video
|
||||
help = Like scale, but apply these filters on downscaling instead. \nIf no option is selected, it will keep the same with the upscaler.
|
||||
option = bilinear Bilinear hardware texture filtering (fastest, very low quality).
|
||||
option = spline36 Mid quality and speed. This is the default when using gpu-hq.
|
||||
option = lanczos Lanczos scaling. Provides mid quality and speed. Generally worse than spline36, but it results in a slightly sharper image which is good for some content types. The number of taps can be controlled with scale-radius, but is best left unchanged. (This filter is an alias for sinc-windowed sinc)
|
||||
option = ewa_lanczos Elliptic weighted average Lanczos scaling. Also known as Jinc. Relatively slow, but very good quality. The radius can be controlled with scale-radius. Increasing the radius makes the filter sharper but adds more ringing. (This filter is an alias for jinc-windowed jinc)
|
||||
option = ewa_lanczossharp A slightly sharpened version of ewa_lanczos, preconfigured to use an ideal radius and parameter. If your hardware can run it, this is probably what you should use by default.
|
||||
option = mitchell Mitchell-Netravali. The B and C parameters can be set with scale-param1 and scale-param2. This filter is very good at downscaling (see dscale).
|
||||
option = oversample A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as "smoothmotion" (see tscale).
|
||||
|
||||
name = dither-depth
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Video
|
||||
help = Set dither target depth to N. Note that the depth of the connected video display device cannot be detected. Often, LCD panels will do dithering on their own, which conflicts with this option and leads to ugly output.
|
||||
option = no Disable any dithering done by mpv.
|
||||
option = auto Automatic selection. If output bit depth cannot be detected, 8 bits per component are assumed.
|
||||
option = 8 Dither to 8 bit output.
|
||||
option = 10 Dither to 10 bit output.
|
||||
|
||||
name = correct-downscaling
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Video
|
||||
help = When using convolution based filters, extend the filter size when downscaling. Increases quality, but reduces performance while downscaling.\n\nThis will perform slightly sub-optimally for anamorphic video (but still better than without it) since it will extend the size to match only the milder of the scale factors between the axes.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = sigmoid-upscaling
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Video
|
||||
help = When upscaling, use a sigmoidal color transform to avoid emphasizing ringing artifacts. This also implies linear-scaling.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = deband
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Video
|
||||
help = Enable the debanding algorithm. This greatly reduces the amount of visible banding, blocking and other quantization artifacts, at the expense of very slightly blurring some of the finest details. In practice, it's virtually always an improvement - the only reason to disable it would be for performance.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = d3d11va-zero-copy
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Video
|
||||
help = By default, when using hardware decoding with --gpu-api=d3d11, the video image will be copied (GPU-to-GPU) from the decoder surface to a shader resource. Set this option to avoid that copy by sampling directly from the decoder image. This may increase performance and reduce power usage, but can cause the image to be sampled incorrectly on the bottom and right edges due to padding, and may invoke driver bugs, since Direct3D 11 technically does not allow sampling from a decoder surface (though most drivers support it.)
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = hdr-compute-peak
|
||||
file = mpv
|
||||
default = auto
|
||||
directory = Video
|
||||
help = Compute the HDR peak and frame average brightness per-frame instead of relying on tagged metadata. These values are averaged over local regions as well as over several frames to prevent the value from jittering around too much. This option basically gives you dynamic, per-scene tone mapping. Requires compute shaders, which is a fairly recent OpenGL feature, and will probably also perform horribly on some drivers, so enable at your own risk. The special value auto (default) will enable HDR peak computation automatically if compute shaders and SSBOs are supported.
|
||||
option = auto
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = allow-delayed-peak-detect
|
||||
file = mpv
|
||||
default = yes
|
||||
directory = Video
|
||||
help = When using --hdr-compute-peak, allow delaying the detected peak by a frame when beneficial for performance. In particular, this is required to avoid an unnecessary FBO indirection when no advanced rendering is required otherwise. Has no effect if there already is an indirect pass, such as when advanced scaling is enabled. (Only affects --vo=gpu-next, note that --vo=gpu always delays the peak.)
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = volume
|
||||
file = mpv
|
||||
directory = Audio
|
||||
help = Set the startup volume. 0 means silence, 100 means no volume reduction or amplification. Negative values can be passed for compatibility, but are treated as 0. Since mpv 0.18.1, this always controls the internal mixer (aka "softvol"). Default: 100
|
||||
|
||||
name = remember-volume
|
||||
file = mpvnet
|
||||
default = yes
|
||||
directory = Audio
|
||||
help = Save volume and mute on exit and restore it on start. (mpv.net option)
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = alang
|
||||
file = mpv
|
||||
directory = Audio
|
||||
type = string
|
||||
help = Specify a priority list of audio languages to use. Different container formats employ different language codes. DVDs use ISO 639-1 two-letter language codes, Matroska, MPEG-TS and NUT use ISO 639-2 three-letter language codes, while OGM uses a free-form identifier. See also aid.\n\nExamples\n\nmpv dvd://1 alang=hu,en chooses the Hungarian language track on a DVD and falls back on English if Hungarian is not available.\n\nmpv alang=jpn example.mkv plays a Matroska file with Japanese audio.
|
||||
|
||||
name = audio-file-auto
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Audio
|
||||
help = Load additional audio files matching the video filename. The parameter specifies how external audio files are matched.
|
||||
option = no Don't automatically load external audio files.
|
||||
option = exact Load the media filename with audio file extension.
|
||||
option = fuzzy Load all audio files containing media filename.
|
||||
option = all Load all audio files in the current and audio-file-paths directories.
|
||||
|
||||
name = audio-device
|
||||
file = mpv
|
||||
directory = Audio
|
||||
type = string
|
||||
url = https://mpv.io/manual/master/#options-audio-device
|
||||
help = <name> Use the given audio device. This consists of the audio output name, e.g. alsa, followed by /, followed by the audio output specific device name. The default value for this option is auto, which tries every audio output in preference order with the default device.
|
||||
|
||||
name = slang
|
||||
file = mpv
|
||||
directory = Subtitle
|
||||
type = string
|
||||
help = Specify a priority list of subtitle languages to use. Different container formats employ different language codes. DVDs use ISO 639-1 two letter language codes, Matroska uses ISO 639-2 three letter language codes while OGM uses a free-form identifier. See also sid.
|
||||
|
||||
name = sub-auto
|
||||
file = mpv
|
||||
default = exact
|
||||
directory = Subtitle
|
||||
help = Load additional subtitle files matching the video filename. The parameter specifies how external subtitle files are matched. exact is enabled by default.
|
||||
option = no Don't automatically load external subtitle files.
|
||||
option = exact Load the media filename with subtitle file extension.
|
||||
option = fuzzy Load all subs containing media filename.
|
||||
option = all Load all subs in the current and sub-file-paths directories.
|
||||
|
||||
name = sub-font
|
||||
file = mpv
|
||||
directory = Subtitle
|
||||
type = string
|
||||
help = Specify font to use for subtitles that do not themselves specify a particular font. The default is sans-serif.
|
||||
|
||||
name = sub-font-size
|
||||
file = mpv
|
||||
directory = Subtitle
|
||||
help = Specify the sub font size. The unit is the size in scaled pixels at a window height of 720. The actual pixel size is scaled with the window height: if the window height is larger or smaller than 720, the actual size of the text increases or decreases as well. Default: 55
|
||||
|
||||
name = sub-color
|
||||
file = mpv
|
||||
type = color
|
||||
directory = Subtitle
|
||||
url = https://mpv.io/manual/master/#options-sub-color
|
||||
help = Specify the color used for unstyled text subtitles.\n\nA usage description and examples can be found in the manual at:
|
||||
|
||||
name = sub-border-color
|
||||
file = mpv
|
||||
type = color
|
||||
directory = Subtitle
|
||||
help = See sub-color. Color used for the sub font border. Ignored when sub-back-color is specified (or more exactly: when that option is not set to completely transparent).
|
||||
|
||||
name = sub-back-color
|
||||
file = mpv
|
||||
type = color
|
||||
directory = Subtitle
|
||||
help = See sub-color. Color used for sub text background. You can use sub-shadow-offset to change its size relative to the text.
|
||||
|
||||
name = fullscreen
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Screen
|
||||
help = Start the player in fullscreen mode.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = border
|
||||
file = mpv
|
||||
default = yes
|
||||
directory = Screen
|
||||
help = Show window with decoration (titlebar, border).
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = screen
|
||||
file = mpv
|
||||
directory = Screen
|
||||
help = <0-32> In multi-monitor configurations (i.e. a single desktop that spans across multiple displays), this option tells mpv which screen to display the video on.
|
||||
|
||||
name = osd-playing-msg
|
||||
file = mpv
|
||||
width = 300
|
||||
directory = Screen
|
||||
type = string
|
||||
help = Show a message on OSD when playback starts. The string is expanded for properties, e.g. osd-playing-msg='file: ${filename}' will show the message file: followed by a space and the currently played filename. For more information visit:
|
||||
url = https://mpv.io/manual/master/#property-expansion
|
||||
|
||||
name = osd-font-size
|
||||
file = mpv
|
||||
directory = Screen
|
||||
help = Specify the OSD font size. See sub-font-size for details. Default: 55
|
||||
|
||||
name = osd-duration
|
||||
file = mpv
|
||||
directory = Screen
|
||||
help = Set the duration of the OSD messages in ms. Default: 1000
|
||||
|
||||
name = osd-scale-by-window
|
||||
file = mpv
|
||||
default = yes
|
||||
directory = Screen
|
||||
help = Whether to scale the OSD with the window size. If this is disabled, osd-font-size and other OSD options that use scaled pixels are always in actual pixels. The effect is that changing the window size won't change the OSD font size.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = autofit
|
||||
file = mpv
|
||||
directory = Screen
|
||||
help = <int> Initial window height in percent. Default: 60
|
||||
|
||||
name = autofit-image
|
||||
file = mpvnet
|
||||
directory = Screen
|
||||
help = <int> Initial window height in percent for image files. Default: 80
|
||||
|
||||
name = autofit-audio
|
||||
file = mpvnet
|
||||
directory = Screen
|
||||
help = <int> Initial window height in percent for audio files. Default: 70
|
||||
|
||||
name = autofit-smaller
|
||||
file = mpv
|
||||
directory = Screen
|
||||
help = <int> Minimum window height in percent. Default: 10
|
||||
|
||||
name = autofit-larger
|
||||
file = mpv
|
||||
directory = Screen
|
||||
help = <int> Maximum window height in percent. Default: 80
|
||||
|
||||
name = start-size
|
||||
file = mpvnet
|
||||
default = height-session
|
||||
directory = Screen
|
||||
help = Setting to remember the window size. (mpv.net option)
|
||||
option = width-session Window width is remembered in the current session
|
||||
option = width-always Window width is always remembered
|
||||
option = height-session Window height is remembered in the current session
|
||||
option = height-always Window height is always remembered
|
||||
option = video Window size is set to video resolution
|
||||
option = session Window size is remembered in the current session
|
||||
option = always Window size is always remembered
|
||||
|
||||
name = keepaspect-window
|
||||
file = mpv
|
||||
default = yes
|
||||
directory = Screen
|
||||
help = keepaspect-window will lock the window size to the video aspect. Default: yes
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = minimum-aspect-ratio
|
||||
file = mpvnet
|
||||
directory = Screen
|
||||
help = <float> Minimum aspect ratio of the window. Useful to force a wider window and therefore a larger OSC. (mpv.net option)
|
||||
|
||||
name = minimum-aspect-ratio-audio
|
||||
file = mpvnet
|
||||
directory = Screen
|
||||
help = Same as minimum-aspect-ratio but used for audio files.
|
||||
|
||||
name = remember-window-position
|
||||
file = mpvnet
|
||||
default = no
|
||||
directory = Screen
|
||||
help = Save the window position on exit. (mpv.net option)
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = snap-window
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Screen
|
||||
help = Snap the player window to screen edges.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = window-maximized
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Screen
|
||||
help = Start with a maximized window.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = start-threshold
|
||||
file = mpvnet
|
||||
directory = Screen
|
||||
help = Threshold in milliseconds to wait for libmpv returning the video resolution before the window is shown, otherwise default dimensions are used as defined by autofit and start-size. Default: 1500 (mpv.net option)
|
||||
|
||||
name = taskbar-progress
|
||||
file = mpv
|
||||
default = yes
|
||||
directory = Playback
|
||||
help = Show progress in taskbar.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = keep-open
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Playback
|
||||
option = yes If the current file ends, go to the next file, keep the last file open.
|
||||
option = no If the current file ends, go to the next file. If idle is set to no, the player exits after the last file.
|
||||
option = always Playback will never automatically advance to the next file.
|
||||
|
||||
name = keep-open-pause
|
||||
file = mpv
|
||||
default = yes
|
||||
directory = Playback
|
||||
help = If set to no, instead of pausing when keep-open is active, just stop at end of file and continue playing forward when you seek backwards until end where it stops again.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = idle
|
||||
file = mpv
|
||||
default = yes
|
||||
directory = Playback
|
||||
help = If set to no and keep-open is also set to no, the player exits after the last file ends.
|
||||
option = yes
|
||||
option = no
|
||||
option = once
|
||||
|
||||
name = loop-file
|
||||
file = mpv
|
||||
directory = Playback
|
||||
help = <N|inf|no> Loop a single file N times. inf means forever, no means normal playback.\n\nThe difference to loop-playlist is that this doesn't loop the playlist, just the file itself. If the playlist contains only a single file, the difference between the two option is that this option performs a seek on loop, instead of reloading the file. loop is an alias for this option.
|
||||
|
||||
name = save-position-on-quit
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Playback
|
||||
help = Always save the current playback position on quit. When this file is played again later, the player will seek to the old playback position on start. This does not happen if playback of a file is stopped in any other way than quitting. For example, going to the next file in the playlist will not save the position, and start playback at beginning the next time the file is played.\n\nThis behavior is disabled by default, but is always available when quitting the player with Shift+Q.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = watch-later-options
|
||||
file = mpv
|
||||
directory = Playback
|
||||
help = The options that are saved in "watch later" files if they have been changed since when mpv started. These values will be restored the next time the files are played. This is a string list option. For more information visit:
|
||||
url = https://mpv.io/manual/master/#options-watch-later-options
|
||||
|
||||
name = hr-seek
|
||||
file = mpv
|
||||
default = absolute
|
||||
directory = Playback
|
||||
help = Select when to use precise seeks that are not limited to keyframes. Such seeks require decoding video from the previous keyframe up to the target position and so can take some time depending on decoding performance. For some video formats, precise seeks are disabled. This option selects the default choice to use for seeks; it is possible to explicitly override that default in the definition of key bindings and in input commands.
|
||||
option = yes Use precise seeks whenever possible.
|
||||
option = no Never use precise seeks.
|
||||
option = absolute Use precise seeks if the seek is to an absolute position in the file, such as a chapter seek, but not for relative seeks like the default behavior of arrow keys.
|
||||
option = always Same as yes (for compatibility).
|
||||
|
||||
name = track-auto-selection
|
||||
file = mpv
|
||||
default = yes
|
||||
directory = Playback
|
||||
help = Enable the default track auto-selection. Enabling this will make the player select streams according to aid, alang, and others. If it is disabled, no tracks are selected. In addition, the player will not exit if no tracks are selected, and wait instead (this wait mode is similar to pausing, but the pause option is not set).\n\nThis is useful with lavfi-complex: you can start playback in this mode, and then set select tracks at runtime by setting the filter graph. Note that if lavfi-complex is set before playback is started, the referenced tracks are always selected.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = loop-playlist
|
||||
file = mpv
|
||||
directory = Playback
|
||||
help = <N|inf|force|no> Loops playback N times. A value of 1 plays it one time (default), 2 two times, etc. inf means forever. no is the same as 1 and disables looping. If several files are specified on command line, the entire playlist is looped. The force mode is like inf, but does not skip playlist entries which have been marked as failing. This means the player might waste CPU time trying to loop a file that doesn't exist. But it might be useful for playing webradios under very bad network conditions.
|
||||
|
||||
name = auto-load-folder
|
||||
file = mpvnet
|
||||
default = yes
|
||||
directory = Playback
|
||||
help = For single files automatically load the entire directory into the playlist. (mpv.net option)
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = input-ar-delay
|
||||
file = mpv
|
||||
directory = Input
|
||||
help = Delay in milliseconds before we start to autorepeat a key (0 to disable).
|
||||
|
||||
name = input-ar-rate
|
||||
file = mpv
|
||||
directory = Input
|
||||
help = Number of key presses to generate per second on autorepeat.
|
||||
|
||||
name = dark-mode
|
||||
file = mpvnet
|
||||
default = always
|
||||
directory = UI
|
||||
help = Changes between a light and dark theme.\nmpv.net must be restarted after a change.\nmpv.net specific option.
|
||||
option = always
|
||||
option = system Available on Windows 10 or higher
|
||||
option = never
|
||||
|
||||
name = dark-theme
|
||||
file = mpvnet
|
||||
directory = UI
|
||||
url = https://github.com/mpvnet-player/mpvnet/blob/master/docs/manual.md#color-theme
|
||||
help = Color theme used in dark mode.\nmpv.net must be restarted after a change.\nmpv.net specific option. Default: dark
|
||||
|
||||
name = light-theme
|
||||
file = mpvnet
|
||||
directory = UI
|
||||
url = https://github.com/mpvnet-player/mpvnet/blob/master/docs/manual.md#color-theme
|
||||
help = Color theme used in light mode.\nmpv.net must be restarted after a change.\nmpv.net specific option. Default: light
|
||||
|
||||
name = screenshot-directory
|
||||
file = mpv
|
||||
width = 500
|
||||
type = folder
|
||||
directory = Video/Screenshot
|
||||
help = Store screenshots in this directory. This path is joined with the filename generated by screenshot-template. If the template filename is already absolute, the directory is ignored.\n\nIf the directory does not exist, it is created on the first screenshot. If it is not a directory, an error is generated when trying to write a screenshot.
|
||||
|
||||
name = screenshot-format
|
||||
file = mpv
|
||||
default = jpg
|
||||
directory = Video/Screenshot
|
||||
help = Set the image file type used for saving screenshots.
|
||||
option = jpg
|
||||
option = png
|
||||
|
||||
name = screenshot-tag-colorspace
|
||||
file = mpv
|
||||
default = no
|
||||
directory = Video/Screenshot
|
||||
help = Tag screenshots with the appropriate colorspace. Note that not all formats are supported.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = screenshot-high-bit-depth
|
||||
file = mpv
|
||||
default = yes
|
||||
directory = Video/Screenshot
|
||||
help = If possible, write screenshots with a bit depth similar to the source video. This is interesting in particular for PNG, as this sometimes triggers writing 16 bit PNGs with huge file sizes. This will also include an unused alpha channel in the resulting files if 16 bit is used.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = screenshot-jpeg-source-chroma
|
||||
file = mpv
|
||||
default = yes
|
||||
directory = Video/Screenshot
|
||||
help = Write JPEG files with the same chroma subsampling as the video. If disabled, the libjpeg default is used.
|
||||
option = yes
|
||||
option = no
|
||||
|
||||
name = screenshot-template
|
||||
file = mpv
|
||||
directory = Video/Screenshot
|
||||
type = string
|
||||
help = Specify the filename template used to save screenshots. The template specifies the filename without file extension, and can contain format specifiers, which will be substituted when taking a screenshot. By default, the template is mpv-shot%n, which results in filenames like mpv-shot0012.png for example.\n\nFind the full documentation here:
|
||||
url = https://mpv.io/manual/master/#options-screenshot-template
|
||||
|
||||
name = screenshot-jpeg-quality
|
||||
file = mpv
|
||||
directory = Video/Screenshot
|
||||
help = <0-100> Set the JPEG quality level. Higher means better quality. The default is 90.
|
||||
|
||||
name = screenshot-png-compression
|
||||
file = mpv
|
||||
directory = Video/Screenshot
|
||||
help = <0-9> Set the PNG compression level. Higher means better compression. This will affect the file size of the written screenshot file and the time it takes to write a screenshot. Too high compression might occupy enough CPU time to interrupt playback. The default is 7.
|
||||
|
||||
name = screenshot-png-filter
|
||||
file = mpv
|
||||
directory = Video/Screenshot
|
||||
help = <0-5> Set the filter applied prior to PNG compression. 0 is none, 1 is 'sub', 2 is 'up', 3 is 'average', 4 is 'Paeth', and 5 is 'mixed'. This affects the level of compression that can be achieved. For most images, 'mixed' achieves the best compression ratio, hence it is the default.
|
||||
|
||||
name = cache
|
||||
file = mpv
|
||||
default = auto
|
||||
directory = Cache
|
||||
help = Decide whether to use network cache settings.
|
||||
url = https://mpv.io/manual/master/#options-cache
|
||||
option = yes
|
||||
option = no
|
||||
option = auto
|
||||
|
||||
name = demuxer-max-bytes
|
||||
file = mpv
|
||||
directory = Demuxer
|
||||
help = <bytesize> Controls how much the demuxer is allowed to buffer ahead.\nSuffixes such as KiB and MiB are supported.
|
||||
url = https://mpv.io/manual/master/#options-demuxer-max-bytes
|
||||
25
src/MpvNet.Windows/Resources/theme.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
[dark]
|
||||
|
||||
heading = #3C8CC8
|
||||
foreground = #DDDDDD
|
||||
foreground2 = #AAAAAA
|
||||
background = #323232
|
||||
highlight = #404040
|
||||
|
||||
menu-foreground = #DDDDDD
|
||||
menu-background = #323232
|
||||
menu-highlight = #505050
|
||||
|
||||
|
||||
[light]
|
||||
|
||||
heading = #0068B2
|
||||
foreground = #000000
|
||||
foreground2 = #4C4C4C
|
||||
background = #F7F7F7
|
||||
highlight = #DFDFDF
|
||||
|
||||
menu-foreground = #000000
|
||||
menu-background = #DFDFDF
|
||||
menu-highlight = #BFBFBF
|
||||
61
src/MpvNet.Windows/Settings.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
using System.Windows;
|
||||
|
||||
namespace MpvNet.Windows;
|
||||
|
||||
public abstract class Setting
|
||||
{
|
||||
public string? Default { get; set; }
|
||||
public string? File { get; set; }
|
||||
public string? Directory { get; set; }
|
||||
public string? Help { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? StartValue { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public string? URL { get; set; }
|
||||
public string? Value { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public ConfItem? ConfItem { get; set; }
|
||||
}
|
||||
|
||||
public class StringSetting : Setting
|
||||
{
|
||||
}
|
||||
|
||||
public class OptionSetting : Setting
|
||||
{
|
||||
public List<OptionSettingOption> Options { get; } = new List<OptionSettingOption>();
|
||||
}
|
||||
|
||||
public class OptionSettingOption
|
||||
{
|
||||
string? _text;
|
||||
|
||||
public string? Name { get; set; }
|
||||
public string? Help { get; set; }
|
||||
|
||||
public OptionSetting? OptionSetting { get; set; }
|
||||
|
||||
public string? Text
|
||||
{
|
||||
get => string.IsNullOrEmpty(_text) ? Name : _text;
|
||||
set => _text = value;
|
||||
}
|
||||
|
||||
public bool Checked
|
||||
{
|
||||
get => OptionSetting?.Value == Name;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
OptionSetting!.Value = Name;
|
||||
}
|
||||
}
|
||||
|
||||
public Visibility Visibility
|
||||
{
|
||||
get => string.IsNullOrEmpty(Help) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
}
|
||||
13
src/MpvNet.Windows/StringPair.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace MpvNet.Windows;
|
||||
|
||||
public class StringPair
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
|
||||
public StringPair(string name, string value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
22
src/MpvNet.Windows/UI/CommandPalette.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
using MpvNet.Windows.WPF.Controls;
|
||||
|
||||
namespace MpvNet.Windows.UI;
|
||||
|
||||
public class CommandPalette
|
||||
{
|
||||
public static CommandPaletteControl Instance { get; } = new CommandPaletteControl();
|
||||
|
||||
public static IEnumerable<CommandPaletteItem> GetItems()
|
||||
{
|
||||
return InputHelp.GetBindingsFromContent(App.InputConf.GetContent())
|
||||
.Where(i => i.Command != "")
|
||||
.Select(i => new CommandPaletteItem()
|
||||
{
|
||||
Text = i.Path,
|
||||
SecondaryText = i.Input,
|
||||
Action = () => Core.Command(i.Command),
|
||||
Binding = i
|
||||
});
|
||||
}
|
||||
}
|
||||
25
src/MpvNet.Windows/UI/CommandPaletteItem.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
namespace MpvNet.Windows.UI;
|
||||
|
||||
public class CommandPaletteItem
|
||||
{
|
||||
public CommandPaletteItem() { }
|
||||
|
||||
public CommandPaletteItem(string text, Action action)
|
||||
{
|
||||
Text = text;
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public CommandPaletteItem(string text, string secondaryText, Action action)
|
||||
{
|
||||
Text = text;
|
||||
Action = action;
|
||||
SecondaryText = secondaryText;
|
||||
}
|
||||
|
||||
public string Text { get; set; } = "";
|
||||
public string SecondaryText { get; set; } = "";
|
||||
public Action? Action { get; set; }
|
||||
public Binding? Binding { get; set; }
|
||||
}
|
||||
183
src/MpvNet.Windows/UI/GlobalHotkey.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using MpvNet.ExtensionMethod;
|
||||
|
||||
namespace MpvNet.Windows.UI;
|
||||
|
||||
class GlobalHotkey
|
||||
{
|
||||
public static Dictionary<int, string>? Commands { get; set; }
|
||||
static int ID;
|
||||
static IntPtr HWND;
|
||||
|
||||
public static void RegisterGlobalHotkeys(IntPtr hwnd)
|
||||
{
|
||||
HWND = hwnd;
|
||||
string path = Player.ConfigFolder + "global-input.conf";
|
||||
|
||||
if (!File.Exists(path))
|
||||
return;
|
||||
|
||||
foreach (string i in File.ReadAllLines(path))
|
||||
{
|
||||
string line = i.Trim();
|
||||
|
||||
if (line.StartsWith("#") || !line.Contains(' '))
|
||||
continue;
|
||||
|
||||
ProcessGlobalHotkeyLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessGlobalHotkeyLine(string line)
|
||||
{
|
||||
string key = line[..line.IndexOf(" ")];
|
||||
string command = line[(line.IndexOf(" ") + 1)..];
|
||||
string[] parts = key.Split('+');
|
||||
KeyModifiers mod = KeyModifiers.None;
|
||||
int vk;
|
||||
|
||||
for (int i = 0; i < parts.Length - 1; i++)
|
||||
{
|
||||
string umod = parts[i].ToUpper();
|
||||
|
||||
if (umod == "ALT") mod |= KeyModifiers.Alt;
|
||||
if (umod == "CTRL") mod |= KeyModifiers.Ctrl;
|
||||
if (umod == "SHIFT") mod |= KeyModifiers.Shift;
|
||||
if (umod == "WIN") mod |= KeyModifiers.Win;
|
||||
}
|
||||
|
||||
key = parts[^1];
|
||||
|
||||
if (key.Length == 1)
|
||||
{
|
||||
short result = VkKeyScanEx(key[0], GetKeyboardLayout(0));
|
||||
|
||||
int hi = result >> 8;
|
||||
int lo = result & 0xFF;
|
||||
|
||||
vk = lo;
|
||||
|
||||
if ((hi & 1) == 1) mod |= KeyModifiers.Shift;
|
||||
if ((hi & 2) == 2) mod |= KeyModifiers.Ctrl;
|
||||
if ((hi & 4) == 4) mod |= KeyModifiers.Alt;
|
||||
}
|
||||
else
|
||||
vk = Mpv_to_VK(key);
|
||||
|
||||
Commands ??= new Dictionary<int, string>();
|
||||
|
||||
if (vk > 0)
|
||||
{
|
||||
Commands[ID] = command.Trim();
|
||||
bool success = RegisterHotKey(HWND, ID++, mod, vk);
|
||||
|
||||
if (!success)
|
||||
Terminal.WriteError(line + ": " + new Win32Exception().Message + "\n", "global-input.conf");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Execute(int id)
|
||||
{
|
||||
if (Commands!.ContainsKey(id))
|
||||
Player.Command(Commands[id]);
|
||||
}
|
||||
|
||||
static int Mpv_to_VK(string value)
|
||||
{
|
||||
return value.ToUpperEx() switch
|
||||
{
|
||||
"NEXT" => 0xB0,// VK_MEDIA_NEXT_TRACK
|
||||
"PREV" => 0xB1,// VK_MEDIA_PREV_TRACK
|
||||
"STOP" => 0xB2,// VK_MEDIA_STOP
|
||||
"PLAYPAUSE" => 0xB3,// VK_MEDIA_PLAY_PAUSE
|
||||
"SLEEP" => 0x5F,// VK_SLEEP
|
||||
"RIGHT" => 0x27,// VK_RIGHT
|
||||
"UP" => 0x26,// VK_UP
|
||||
"LEFT" => 0x25,// VK_LEFT
|
||||
"DOWN" => 0x28,// VK_DOWN
|
||||
"PGUP" => 0x21,// VK_PRIOR
|
||||
"PGDWN" => 0x22,// VK_NEXT
|
||||
"PAUSE" => 0x13,// VK_PAUSE
|
||||
"PRINT" => 0x2A,// VK_PRINT
|
||||
"HOME" => 0x24,// VK_HOME
|
||||
"INS" => 0x2D,// VK_INSERT
|
||||
"KP_INS" => 0x2D,// VK_INSERT
|
||||
"DEL" => 0x2E,// VK_DELETE
|
||||
"KP_DEL" => 0x2E,// VK_DELETE
|
||||
"END" => 0x23,// VK_END
|
||||
"F1" => 0x70,// VK_F1
|
||||
"F2" => 0x71,// VK_F2
|
||||
"F3" => 0x72,// VK_F3
|
||||
"F4" => 0x73,// VK_F4
|
||||
"F5" => 0x74,// VK_F5
|
||||
"F6" => 0x75,// VK_F6
|
||||
"F7" => 0x76,// VK_F7
|
||||
"F8" => 0x77,// VK_F8
|
||||
"F9" => 0x78,// VK_F9
|
||||
"F10" => 0x79,// VK_F10
|
||||
"F11" => 0x7A,// VK_F11
|
||||
"F12" => 0x7B,// VK_F12
|
||||
"F13" => 0x7C,// VK_F13
|
||||
"F14" => 0x7D,// VK_F14
|
||||
"F15" => 0x7E,// VK_F15
|
||||
"F16" => 0x7F,// VK_F16
|
||||
"F17" => 0x80,// VK_F17
|
||||
"F18" => 0x81,// VK_F18
|
||||
"F19" => 0x82,// VK_F19
|
||||
"F20" => 0x83,// VK_F20
|
||||
"F21" => 0x84,// VK_F21
|
||||
"F22" => 0x85,// VK_F22
|
||||
"F23" => 0x86,// VK_F23
|
||||
"F24" => 0x87,// VK_F24
|
||||
"ENTER" => 0x0D,// VK_RETURN
|
||||
"KP_ENTER" => 0x0D,// VK_RETURN
|
||||
"TAB" => 0x09,// VK_TAB
|
||||
"MENU" => 0x5D,// VK_APPS
|
||||
"CANCEL" => 0x03,// VK_CANCEL
|
||||
"BS" => 0x08,// VK_BACK
|
||||
"KP_DEC" => 0x6E,// VK_DECIMAL
|
||||
"ESC" => 0x1B,// VK_ESCAPE
|
||||
"KP0" => 0x60,// VK_NUMPAD0
|
||||
"KP1" => 0x61,// VK_NUMPAD1
|
||||
"KP2" => 0x62,// VK_NUMPAD2
|
||||
"KP3" => 0x63,// VK_NUMPAD3
|
||||
"KP4" => 0x64,// VK_NUMPAD4
|
||||
"KP5" => 0x65,// VK_NUMPAD5
|
||||
"KP6" => 0x66,// VK_NUMPAD6
|
||||
"KP7" => 0x67,// VK_NUMPAD7
|
||||
"KP8" => 0x68,// VK_NUMPAD8
|
||||
"KP9" => 0x69,// VK_NUMPAD9
|
||||
"FAVORITES" => 0xAB,// VK_BROWSER_FAVORITES
|
||||
"SEARCH" => 0xAA,// VK_BROWSER_SEARCH
|
||||
"MAIL" => 0xB4,// VK_LAUNCH_MAIL
|
||||
"VOLUME_UP" => 0xAF,// VK_VOLUME_UP
|
||||
"VOLUME_DOWN" => 0xAE,// VK_VOLUME_DOWN
|
||||
"MUTE" => 0xAD,// VK_VOLUME_MUTE
|
||||
"SPACE" => 0x20,// VK_SPACE
|
||||
"IDEOGRAPHIC_SPACE" => 0x20,// VK_SPACE
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
static extern short VkKeyScanEx(char ch, IntPtr dwhkl);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr GetKeyboardLayout(uint idThread);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers fsModifiers, int vk);
|
||||
|
||||
[Flags]
|
||||
enum KeyModifiers
|
||||
{
|
||||
None = 0,
|
||||
Alt = 1,
|
||||
Ctrl = 2,
|
||||
Shift = 4,
|
||||
Win = 8
|
||||
}
|
||||
}
|
||||
141
src/MpvNet.Windows/UI/Theme.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace MpvNet.Windows.UI;
|
||||
|
||||
public class Theme
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public Dictionary<string, string> Dictionary { get; } = new Dictionary<string, string>();
|
||||
|
||||
public static List<Theme>? DefaultThemes { get; set; }
|
||||
public static List<Theme>? CustomThemes { get; set; }
|
||||
|
||||
public static Theme? Current { get; set; }
|
||||
|
||||
public Brush? Background { get; set; }
|
||||
public Brush? Foreground { get; set; }
|
||||
public Brush? Foreground2 { get; set; }
|
||||
public Brush? Heading { get; set; }
|
||||
public Brush? MenuBackground { get; set; }
|
||||
public Brush? MenuHighlight { get; set; }
|
||||
|
||||
public Brush GetBrush(string key)
|
||||
{
|
||||
return new SolidColorBrush((Color)ColorConverter.ConvertFromString(Dictionary[key]));
|
||||
}
|
||||
|
||||
public Color GetColor(string key) => (Color)ColorConverter.ConvertFromString(Dictionary[key]);
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
string? themeContent = null;
|
||||
|
||||
if (File.Exists(Player.ConfigFolder + "theme.conf"))
|
||||
themeContent = File.ReadAllText(Player.ConfigFolder + "theme.conf");
|
||||
|
||||
Init(themeContent, Properties.Resources.theme, DarkMode ? App.DarkTheme : App.LightTheme);
|
||||
}
|
||||
|
||||
public static void Init(string? customContent, string defaultContent, string activeTheme)
|
||||
{
|
||||
Current = null;
|
||||
|
||||
DefaultThemes = Load(defaultContent);
|
||||
CustomThemes = Load(customContent);
|
||||
|
||||
foreach (Theme theme in CustomThemes)
|
||||
{
|
||||
if (theme.Name == activeTheme)
|
||||
{
|
||||
bool isKeyMissing = false;
|
||||
|
||||
foreach (string key in DefaultThemes[0].Dictionary.Keys)
|
||||
{
|
||||
if (!theme.Dictionary.ContainsKey(key))
|
||||
{
|
||||
isKeyMissing = true;
|
||||
Terminal.WriteError($"Theme '{activeTheme}' misses '{key}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isKeyMissing)
|
||||
Current = theme;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Current == null)
|
||||
foreach (Theme theme in DefaultThemes)
|
||||
if (theme.Name == activeTheme)
|
||||
Current = theme;
|
||||
|
||||
if (Current == null)
|
||||
Current = DefaultThemes[0];
|
||||
|
||||
Current.Background = Current.GetBrush("background");
|
||||
Current.Foreground = Current.GetBrush("foreground");
|
||||
Current.Foreground2 = Current.GetBrush("foreground2");
|
||||
Current.Heading = Current.GetBrush("heading");
|
||||
Current.MenuBackground = Current.GetBrush("menu-background");
|
||||
Current.MenuHighlight = Current.GetBrush("menu-highlight");
|
||||
}
|
||||
|
||||
static List<Theme> Load(string? content)
|
||||
{
|
||||
List<Theme> list = new List<Theme>();
|
||||
Theme? theme = null;
|
||||
|
||||
foreach (string currentLine in (content ?? "").Split(new[] { '\r', '\n' }))
|
||||
{
|
||||
string line = currentLine.Trim();
|
||||
|
||||
if (line.StartsWith("[") && line.EndsWith("]"))
|
||||
list.Add(theme = new Theme() { Name = line.Substring(1, line.Length - 2).Trim() });
|
||||
|
||||
if (line.Contains('=') && theme != null)
|
||||
{
|
||||
string left = line[..line.IndexOf("=")].Trim();
|
||||
theme.Dictionary[left] = line[(line.IndexOf("=") + 1)..].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void UpdateWpfColors()
|
||||
{
|
||||
var dic = Application.Current.Resources;
|
||||
|
||||
dic.Remove("BorderColor");
|
||||
dic.Add("BorderColor", Current!.GetColor("menu-highlight"));
|
||||
|
||||
dic.Remove("RegionColor");
|
||||
dic.Add("RegionColor", Current.GetColor("menu-background"));
|
||||
|
||||
dic.Remove("SecondaryRegionColor");
|
||||
dic.Add("SecondaryRegionColor", Current.GetColor("menu-highlight"));
|
||||
|
||||
dic.Remove("PrimaryTextColor");
|
||||
dic.Add("PrimaryTextColor", Current.GetColor("menu-foreground"));
|
||||
|
||||
dic.Remove("HighlightColor");
|
||||
dic.Add("HighlightColor", Current.GetColor("highlight"));
|
||||
}
|
||||
|
||||
static bool DarkModeSystem
|
||||
{
|
||||
get
|
||||
{
|
||||
string key = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
|
||||
return (int)(Registry.GetValue(key, "AppsUseLightTheme", 1) ?? 1) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DarkMode => App.DarkMode == "system" && DarkModeSystem || App.DarkMode == "always";
|
||||
}
|
||||
11
src/MpvNet.Windows/UI/TreeNode.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
namespace MpvNet.Windows.UI;
|
||||
|
||||
public class TreeNode
|
||||
{
|
||||
readonly List<TreeNode> _children = new List<TreeNode>();
|
||||
|
||||
public IList<TreeNode> Children => _children;
|
||||
|
||||
public string Name { get; set; } = "";
|
||||
}
|
||||
20
src/MpvNet.Windows/WPF/BindingProxy.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
using System.Windows;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public class BindingProxy : Freezable
|
||||
{
|
||||
protected override Freezable CreateInstanceCore() => new BindingProxy();
|
||||
|
||||
public object Data
|
||||
{
|
||||
get { return GetValue(DataProperty); }
|
||||
set { SetValue(DataProperty, value); }
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for Data.
|
||||
// This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty DataProperty =
|
||||
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
|
||||
}
|
||||
162
src/MpvNet.Windows/WPF/ConfWindow.xaml
Normal file
@@ -0,0 +1,162 @@
|
||||
<Window
|
||||
x:Name="ConfWindow1"
|
||||
x:Class="MpvNet.Windows.WPF.ConfWindow"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:controls="clr-namespace:MpvNet.Windows.WPF.Controls"
|
||||
xmlns:wpf="clr-namespace:MpvNet.Windows.WPF"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="Config Editor"
|
||||
Height="550"
|
||||
Width="800"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Loaded="ConfWindow1_Loaded"
|
||||
>
|
||||
|
||||
<Window.Resources>
|
||||
<wpf:BindingProxy x:Key="BindingProxy" Data="{Binding}" />
|
||||
</Window.Resources>
|
||||
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="n" Modifiers="Ctrl" Command="{Binding ShowMpvNetSpecificSettingsCommand}"/>
|
||||
<KeyBinding Key="F5" Command="{Binding PreviewMpvConfFileCommand}"/>
|
||||
<KeyBinding Key="F6" Command="{Binding PreviewMpvNetConfFileCommand}"/>
|
||||
<KeyBinding Key="F1" Command="{Binding ShowMpvManualCommand}"/>
|
||||
<KeyBinding Key="F2" Command="{Binding ShowMpvNetManualCommand}"/>
|
||||
</Window.InputBindings>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="170" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:SearchControl
|
||||
x:Name="SearchControl"
|
||||
HintText="Find a setting"
|
||||
Margin="20,20,0,10"
|
||||
Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
|
||||
<ScrollViewer
|
||||
Name="MainScrollViewer"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
Margin="0,0,0,10"
|
||||
>
|
||||
|
||||
<StackPanel x:Name="MainStackPanel"></StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<TreeView
|
||||
x:Name="TreeView"
|
||||
ItemsSource="{Binding Nodes}"
|
||||
Margin="20,0,0,0"
|
||||
Grid.Row="1"
|
||||
BorderThickness="0"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
SelectedItemChanged="TreeView_SelectedItemChanged"
|
||||
>
|
||||
|
||||
<TreeView.Resources>
|
||||
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Background}" />
|
||||
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Background}" />
|
||||
</TreeView.Resources>
|
||||
|
||||
<TreeView.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type TreeViewItem}">
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
|
||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Foreground2}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Background2}" />
|
||||
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Foreground}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TreeView.ItemContainerStyle>
|
||||
|
||||
<TreeView.ItemTemplate>
|
||||
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</HierarchicalDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
|
||||
<TextBlock
|
||||
Name="MenuTextBlock"
|
||||
Text="Menu"
|
||||
Cursor="Hand"
|
||||
Foreground="LightGray"
|
||||
TextDecorations="Underline"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="20,5,10,10"
|
||||
Grid.Row="2"
|
||||
>
|
||||
|
||||
<TextBlock.ContextMenu>
|
||||
<ContextMenu Name="MainContextMenu">
|
||||
<MenuItem
|
||||
Header="Show mpv.net options"
|
||||
InputGestureText="Ctrl+n"
|
||||
Command="{Binding Data.ShowMpvNetSpecificSettingsCommand, Source={StaticResource BindingProxy}}"
|
||||
/>
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Header="Preview mpv.conf"
|
||||
InputGestureText="F5"
|
||||
Command="{Binding Data.PreviewMpvConfFileCommand, Source={StaticResource BindingProxy}}"
|
||||
/>
|
||||
<MenuItem
|
||||
Header="Preview mpvnet.conf"
|
||||
InputGestureText="F6"
|
||||
Command="{Binding Data.PreviewMpvNetConfFileCommand, Source={StaticResource BindingProxy}}"
|
||||
/>
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Header="Show mpv manual"
|
||||
InputGestureText="F1"
|
||||
Command="{Binding Data.ShowMpvManualCommand, Source={StaticResource BindingProxy}}"
|
||||
/>
|
||||
<MenuItem
|
||||
Header="Show mpv.net manual"
|
||||
InputGestureText="F2"
|
||||
Command="{Binding Data.ShowMpvNetManualCommand, Source={StaticResource BindingProxy}}"
|
||||
/>
|
||||
</ContextMenu>
|
||||
</TextBlock.ContextMenu>
|
||||
|
||||
<b:Interaction.Triggers>
|
||||
<b:EventTrigger EventName="MouseLeftButtonDown">
|
||||
<b:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=MenuTextBlock}"
|
||||
PropertyName="PlacementTarget"
|
||||
Value="{Binding ElementName=MenuTextBlock, Mode=OneWay}"/>
|
||||
<b:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=MenuTextBlock}"
|
||||
PropertyName="IsOpen"
|
||||
Value="True"/>
|
||||
</b:EventTrigger>
|
||||
</b:Interaction.Triggers>
|
||||
</TextBlock>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
482
src/MpvNet.Windows/WPF/ConfWindow.xaml.cs
Normal file
@@ -0,0 +1,482 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using MpvNet.Help;
|
||||
using MpvNet.Windows.UI;
|
||||
using MpvNet.Windows.WPF.Controls;
|
||||
using MpvNet.Windows.WPF.ViewModels;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public partial class ConfWindow : Window, INotifyPropertyChanged
|
||||
{
|
||||
List<Setting> Settings = Conf.LoadConf(Properties.Resources.editor_conf.TrimEnd());
|
||||
List<ConfItem> ConfItems = new List<ConfItem>();
|
||||
public ObservableCollection<string> FilterStrings { get; } = new();
|
||||
string InitialContent;
|
||||
string ThemeConf = GetThemeConf();
|
||||
string? _searchText;
|
||||
List<NodeViewModel>? _nodes;
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public ConfWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
LoadConf(Player.ConfPath);
|
||||
LoadConf(App.ConfPath);
|
||||
LoadSettings();
|
||||
InitialContent = GetCompareString();
|
||||
|
||||
if (string.IsNullOrEmpty(App.Settings.ConfigEditorSearch))
|
||||
SearchControl.Text = "General:";
|
||||
else
|
||||
SearchControl.Text = App.Settings.ConfigEditorSearch;
|
||||
|
||||
foreach (var node in Nodes)
|
||||
SelectNodeFromSearchText(node);
|
||||
|
||||
foreach (var node in Nodes)
|
||||
ExpandNode(node);
|
||||
}
|
||||
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
public string SearchText
|
||||
{
|
||||
get => _searchText ?? "";
|
||||
set
|
||||
{
|
||||
_searchText = value;
|
||||
SearchTextChanged();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public List<NodeViewModel> Nodes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_nodes == null)
|
||||
{
|
||||
var rootNode = new TreeNode();
|
||||
|
||||
foreach (Setting setting in Settings)
|
||||
AddNode(rootNode.Children, setting.Directory!);
|
||||
|
||||
_nodes = new NodeViewModel(rootNode).Children;
|
||||
}
|
||||
|
||||
return _nodes;
|
||||
}
|
||||
}
|
||||
|
||||
public static TreeNode? AddNode(IList<TreeNode> nodes, string path)
|
||||
{
|
||||
string[] parts = path.Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
for (int x = 0; x < parts.Length; x++)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (x < parts.Length - 1)
|
||||
{
|
||||
if (node.Name == parts[x])
|
||||
{
|
||||
found = true;
|
||||
nodes = node.Children;
|
||||
}
|
||||
}
|
||||
else if (x == parts.Length - 1 && node.Name == parts[x])
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
if (x == parts.Length - 1)
|
||||
{
|
||||
var item = new TreeNode() { Name = parts[x] };
|
||||
nodes?.Add(item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void LoadSettings()
|
||||
{
|
||||
foreach (Setting setting in Settings)
|
||||
{
|
||||
setting.StartValue = setting.Value;
|
||||
|
||||
if (!FilterStrings.Contains(setting.Directory!))
|
||||
FilterStrings.Add(setting.Directory!);
|
||||
|
||||
foreach (ConfItem confItem in ConfItems)
|
||||
{
|
||||
if (setting.Name == confItem.Name && confItem.Section == "" && !confItem.IsSectionItem)
|
||||
{
|
||||
setting.Value = confItem.Value.Trim('\'', '"');
|
||||
setting.StartValue = setting.Value;
|
||||
setting.ConfItem = confItem;
|
||||
confItem.SettingBase = setting;
|
||||
}
|
||||
}
|
||||
|
||||
switch (setting)
|
||||
{
|
||||
case StringSetting s:
|
||||
MainStackPanel.Children.Add(new StringSettingControl(s) { Visibility = Visibility.Collapsed });
|
||||
break;
|
||||
case OptionSetting s:
|
||||
MainStackPanel.Children.Add(new OptionSettingControl(s) { Visibility = Visibility.Collapsed });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static string GetThemeConf() => Theme.DarkMode + App.DarkTheme + App.LightTheme;
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
App.Settings.ConfigEditorSearch = SearchControl.Text;
|
||||
|
||||
if (InitialContent == GetCompareString())
|
||||
return;
|
||||
|
||||
File.WriteAllText(Player.ConfPath, GetContent("mpv"));
|
||||
File.WriteAllText(App.ConfPath, GetContent("mpvnet"));
|
||||
|
||||
foreach (Setting it in Settings)
|
||||
{
|
||||
if (it.Value != it.StartValue)
|
||||
{
|
||||
if (it.File == "mpv")
|
||||
{
|
||||
Player.ProcessProperty(it.Name, it.Value);
|
||||
Player.SetPropertyString(it.Name!, it.Value!);
|
||||
}
|
||||
else if (it.File == "mpvnet")
|
||||
App.ProcessProperty(it.Name ?? "", it.Value ?? "", true);
|
||||
}
|
||||
}
|
||||
|
||||
Theme.Init();
|
||||
Theme.UpdateWpfColors();
|
||||
|
||||
if (ThemeConf != GetThemeConf())
|
||||
MessageBox.Show("Changed theme settings require mpv.net being restarted.", "Info");
|
||||
}
|
||||
|
||||
bool _shown;
|
||||
|
||||
protected override void OnContentRendered(EventArgs e)
|
||||
{
|
||||
base.OnContentRendered(e);
|
||||
|
||||
if (_shown)
|
||||
return;
|
||||
|
||||
_shown = true;
|
||||
|
||||
Application.Current.Dispatcher.BeginInvoke(() => {
|
||||
SearchControl.SearchTextBox.SelectAll();
|
||||
},
|
||||
DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
string GetCompareString()
|
||||
{
|
||||
return string.Join("", Settings.Select(item => item.Name + item.Value).ToArray());
|
||||
}
|
||||
|
||||
void LoadConf(string file)
|
||||
{
|
||||
if (!File.Exists(file))
|
||||
return;
|
||||
|
||||
string comment = "";
|
||||
string section = "";
|
||||
|
||||
bool isSectionItem = false;
|
||||
|
||||
foreach (string currentLine in File.ReadAllLines(file))
|
||||
{
|
||||
string line = currentLine.Trim();
|
||||
|
||||
if (line == "")
|
||||
{
|
||||
comment += "\r\n";
|
||||
}
|
||||
else if (line.StartsWith("#"))
|
||||
{
|
||||
comment += line.Trim() + "\r\n";
|
||||
}
|
||||
else if (line.StartsWith("[") && line.Contains("]"))
|
||||
{
|
||||
if (!isSectionItem && comment != "" && comment != "\r\n")
|
||||
ConfItems.Add(new ConfItem() {
|
||||
Comment = comment, File = Path.GetFileNameWithoutExtension(file)});
|
||||
|
||||
section = line.Substring(0, line.IndexOf("]") + 1);
|
||||
comment = "";
|
||||
isSectionItem = true;
|
||||
}
|
||||
else if (line.Contains("="))
|
||||
{
|
||||
ConfItem item = new ConfItem();
|
||||
item.File = Path.GetFileNameWithoutExtension(file);
|
||||
item.IsSectionItem = isSectionItem;
|
||||
item.Comment = comment;
|
||||
comment = "";
|
||||
item.Section = section;
|
||||
section = "";
|
||||
|
||||
if (line.Contains("#") && !line.Contains("'") && !line.Contains("\""))
|
||||
{
|
||||
item.LineComment = line.Substring(line.IndexOf("#")).Trim();
|
||||
line = line.Substring(0, line.IndexOf("#")).Trim();
|
||||
}
|
||||
|
||||
int pos = line.IndexOf("=");
|
||||
string left = line.Substring(0, pos).Trim().ToLower();
|
||||
string right = line.Substring(pos + 1).Trim();
|
||||
|
||||
if (left == "fs")
|
||||
left = "fullscreen";
|
||||
|
||||
if (left == "loop")
|
||||
left = "loop-file";
|
||||
|
||||
item.Name = left;
|
||||
item.Value = right;
|
||||
ConfItems.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string GetContent(string filename)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
List<string> namesWritten = new List<string>();
|
||||
|
||||
foreach (ConfItem item in ConfItems)
|
||||
{
|
||||
if (filename != item.File || item.Section != "" || item.IsSectionItem)
|
||||
continue;
|
||||
|
||||
if (item.Comment != "")
|
||||
sb.Append(item.Comment);
|
||||
|
||||
if (item.SettingBase == null)
|
||||
{
|
||||
if (item.Name != "")
|
||||
{
|
||||
sb.Append(item.Name + " = " + item.Value);
|
||||
|
||||
if (item.LineComment != "")
|
||||
sb.Append(" " + item.LineComment);
|
||||
|
||||
sb.AppendLine();
|
||||
namesWritten.Add(item.Name);
|
||||
}
|
||||
}
|
||||
else if ((item.SettingBase.Value ?? "") != item.SettingBase.Default)
|
||||
{
|
||||
string? value;
|
||||
|
||||
if (item.SettingBase.Type == "string" ||
|
||||
item.SettingBase.Type == "folder" ||
|
||||
item.SettingBase.Type == "color")
|
||||
|
||||
value = "'" + item.SettingBase.Value + "'";
|
||||
else
|
||||
value = item.SettingBase.Value;
|
||||
|
||||
sb.Append(item.Name + " = " + value);
|
||||
|
||||
if (item.LineComment != "")
|
||||
sb.Append(" " + item.LineComment);
|
||||
|
||||
sb.AppendLine();
|
||||
namesWritten.Add(item.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!sb.ToString().Contains("# Editor"))
|
||||
sb.AppendLine("# Editor");
|
||||
|
||||
foreach (Setting setting in Settings)
|
||||
{
|
||||
if (filename != setting.File || namesWritten.Contains(setting.Name!))
|
||||
continue;
|
||||
|
||||
if ((setting.Value ?? "") != setting.Default)
|
||||
{
|
||||
string? value;
|
||||
|
||||
if (setting.Type == "string" ||
|
||||
setting.Type == "folder" ||
|
||||
setting.Type == "color")
|
||||
|
||||
value = "'" + setting.Value + "'";
|
||||
else
|
||||
value = setting.Value;
|
||||
|
||||
sb.AppendLine(setting.Name + " = " + value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ConfItem item in ConfItems)
|
||||
{
|
||||
if (filename != item.File || (item.Section == "" && !item.IsSectionItem))
|
||||
continue;
|
||||
|
||||
if (item.Section != "")
|
||||
{
|
||||
if (!sb.ToString().EndsWith("\r\n\r\n"))
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine(item.Section);
|
||||
}
|
||||
|
||||
if (item.Comment != "")
|
||||
sb.Append(item.Comment);
|
||||
|
||||
sb.Append(item.Name + " = " + item.Value);
|
||||
|
||||
if (item.LineComment != "")
|
||||
sb.Append(" " + item.LineComment);
|
||||
|
||||
sb.AppendLine();
|
||||
namesWritten.Add(item.Name);
|
||||
}
|
||||
|
||||
return "\r\n" + sb.ToString().Trim() + "\r\n";
|
||||
}
|
||||
|
||||
void SearchTextChanged()
|
||||
{
|
||||
string activeFilter = "";
|
||||
|
||||
foreach (string i in FilterStrings)
|
||||
if (SearchText == i + ":")
|
||||
activeFilter = i;
|
||||
|
||||
if (activeFilter == "")
|
||||
{
|
||||
foreach (UIElement i in MainStackPanel.Children)
|
||||
if ((i as ISettingControl)!.Contains(SearchText) && SearchText.Length > 1)
|
||||
i.Visibility = Visibility.Visible;
|
||||
else
|
||||
i.Visibility = Visibility.Collapsed;
|
||||
|
||||
foreach (var node in Nodes)
|
||||
UnselectNode(node);
|
||||
}
|
||||
else
|
||||
foreach (UIElement i in MainStackPanel.Children)
|
||||
if ((i as ISettingControl)!.Setting.Directory == activeFilter)
|
||||
i.Visibility = Visibility.Visible;
|
||||
else
|
||||
i.Visibility = Visibility.Collapsed;
|
||||
|
||||
MainScrollViewer.ScrollToTop();
|
||||
}
|
||||
|
||||
void ConfWindow1_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SearchControl.SearchTextBox.SelectAll();
|
||||
Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
|
||||
foreach (var i in MainStackPanel.Children.OfType<StringSettingControl>())
|
||||
i.Update();
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (e.Key == Key.Escape)
|
||||
Close();
|
||||
|
||||
if (e.Key == Key.F3 || e.Key == Key.F6 || (e.Key == Key.F && Keyboard.IsKeyDown(Key.LeftCtrl)))
|
||||
{
|
||||
Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
SearchControl.SearchTextBox.SelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string? name = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
|
||||
void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
var node = TreeView.SelectedItem as NodeViewModel;
|
||||
|
||||
if (node == null)
|
||||
return;
|
||||
|
||||
Application.Current.Dispatcher.BeginInvoke(() => {
|
||||
SearchText = node!.Path + ":";
|
||||
},
|
||||
DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
void SelectNodeFromSearchText(NodeViewModel node)
|
||||
{
|
||||
if (node.Path + ":" == SearchControl.Text)
|
||||
{
|
||||
node.IsSelected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var it in node.Children)
|
||||
SelectNodeFromSearchText(it);
|
||||
}
|
||||
|
||||
void UnselectNode(NodeViewModel node)
|
||||
{
|
||||
if (node.IsSelected)
|
||||
node.IsSelected = false;
|
||||
|
||||
foreach (var it in node.Children)
|
||||
SelectNodeFromSearchText(it);
|
||||
}
|
||||
|
||||
void ExpandNode(NodeViewModel node)
|
||||
{
|
||||
node.IsExpanded = true;
|
||||
|
||||
foreach (var it in node.Children)
|
||||
SelectNodeFromSearchText(it);
|
||||
}
|
||||
|
||||
[RelayCommand] void ShowMpvNetSpecificSettings() => SearchControl.Text = "mpv.net";
|
||||
|
||||
[RelayCommand] void PreviewMpvConfFile() => Msg.ShowInfo(GetContent("mpv"));
|
||||
|
||||
[RelayCommand] void PreviewMpvNetConfFile() => Msg.ShowInfo(GetContent("mpvnet"));
|
||||
|
||||
[RelayCommand] void ShowMpvManual() => ProcessHelp.ShellExecute("https://mpv.io/manual/master/");
|
||||
|
||||
[RelayCommand] void ShowMpvNetManual() => ProcessHelp.ShellExecute("https://github.com/mpvnet-player/mpv.net/blob/master/docs/manual.md");
|
||||
}
|
||||
126
src/MpvNet.Windows/WPF/Controls/CommandPaletteControl.xaml
Normal file
@@ -0,0 +1,126 @@
|
||||
<UserControl
|
||||
x:Class="MpvNet.Windows.WPF.Controls.CommandPaletteControl"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:controls="clr-namespace:MpvNet.Windows.WPF.Controls"
|
||||
mc:Ignorable="d"
|
||||
|
||||
FontSize="13"
|
||||
Loaded="OnLoaded"
|
||||
Background="#111111"
|
||||
>
|
||||
|
||||
<UserControl.InputBindings>
|
||||
<KeyBinding Gesture="Esc" Command="{Binding EscapeCommand}"/>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding ExecuteCommand}"/>
|
||||
</UserControl.InputBindings>
|
||||
|
||||
<Border Name="MainBorder"
|
||||
BorderThickness="1,0,1,1"
|
||||
CornerRadius="0,0,5,5"
|
||||
Padding="0,0,0,5"
|
||||
BorderBrush="{Binding Theme.MenuHighlight}"
|
||||
Background="{Binding Theme.Background}"
|
||||
SnapsToDevicePixels="True"
|
||||
>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border BorderBrush="{Binding Theme.Heading}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="3"
|
||||
Margin="7"
|
||||
>
|
||||
|
||||
<controls:SearchControl
|
||||
HintText="Search"
|
||||
x:Name="SearchControl"
|
||||
Grid.ColumnSpan="2"
|
||||
Padding="1,1,1,0"
|
||||
/>
|
||||
</Border>
|
||||
|
||||
<ListView Name="MainListView"
|
||||
Grid.Row="1"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
BorderThickness="0"
|
||||
MaxHeight="202"
|
||||
SizeChanged="MainListView_SizeChanged"
|
||||
MouseUp="MainListView_MouseUp"
|
||||
>
|
||||
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
|
||||
<Setter Property="Height" Value="25"></Setter>
|
||||
<Setter Property="BorderThickness" Value="0"></Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ListBoxItem}">
|
||||
<Border x:Name="BD"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="true">
|
||||
|
||||
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="BD" Value="{DynamicResource HighlightBrush}" />
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="Selector.IsSelectionActive" Value="False" />
|
||||
<Condition Property="IsSelected" Value="True" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="BD" Value="{DynamicResource BorderBrush}" />
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Style.Resources>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="CornerRadius" Value="3"/>
|
||||
</Style>
|
||||
</Style.Resources>
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{Binding Text}"></TextBlock>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Text="{Binding SecondaryText}"
|
||||
HorizontalAlignment="Right"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
151
src/MpvNet.Windows/WPF/Controls/CommandPaletteControl.xaml.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
using MpvNet.Windows.WinForms;
|
||||
|
||||
namespace MpvNet.Windows.WPF.Controls;
|
||||
|
||||
public partial class CommandPaletteControl : UserControl
|
||||
{
|
||||
public ICollectionView CollectionView { get; set; }
|
||||
public CollectionViewSource CollectionViewSource { get; }
|
||||
public ObservableCollection<CommandPaletteItem> Items { get; } = new ObservableCollection<CommandPaletteItem>();
|
||||
|
||||
public CommandPaletteControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
CollectionViewSource = new CollectionViewSource() { Source = Items };
|
||||
CollectionView = CollectionViewSource.View;
|
||||
CollectionView.Filter = new Predicate<object>(item => Filter((CommandPaletteItem)item));
|
||||
MainListView.ItemsSource = CollectionView;
|
||||
|
||||
SearchControl.SearchTextBox.PreviewKeyDown += SearchTextBox_PreviewKeyDown;
|
||||
SearchControl.SearchTextBox.TextChanged += SearchTextBox_TextChanged;
|
||||
SearchControl.HideClearButton = true;
|
||||
|
||||
if (Environment.OSVersion.Version < new Version(10, 0))
|
||||
MainBorder.CornerRadius = new CornerRadius(0);
|
||||
}
|
||||
|
||||
void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
CollectionView.Refresh();
|
||||
SelectFirst();
|
||||
}
|
||||
|
||||
void SearchTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Up:
|
||||
{
|
||||
int index = MainListView.SelectedIndex;
|
||||
index -= 1;
|
||||
|
||||
if (index < 0)
|
||||
index = 0;
|
||||
|
||||
MainListView.SelectedIndex = index;
|
||||
MainListView.ScrollIntoView(MainListView.SelectedItem);
|
||||
}
|
||||
break;
|
||||
case Key.Down:
|
||||
{
|
||||
int index = MainListView.SelectedIndex;
|
||||
|
||||
if (++index > MainListView.Items.Count - 1)
|
||||
index = MainListView.Items.Count - 1;
|
||||
|
||||
MainListView.SelectedIndex = index;
|
||||
MainListView.ScrollIntoView(MainListView.SelectedItem);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MainListView_SizeChanged(object sender, SizeChangedEventArgs e) => AdjustHeight();
|
||||
|
||||
void MainListView_MouseUp(object sender, MouseButtonEventArgs e) => ExecuteInternal();
|
||||
|
||||
[RelayCommand]
|
||||
void Escape(object param) => MainForm.Instance?.HideCommandPalette();
|
||||
|
||||
[RelayCommand]
|
||||
void Execute() => ExecuteInternal();
|
||||
|
||||
void OnLoaded(object sender, RoutedEventArgs e) => Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
|
||||
public Theme Theme => Theme.Current!;
|
||||
|
||||
bool Filter(CommandPaletteItem item)
|
||||
{
|
||||
string filter = SearchControl.SearchTextBox.Text.ToLower();
|
||||
|
||||
if (item.Binding != null)
|
||||
{
|
||||
// TODO: CommandItem.Alias
|
||||
//if (item.CommandItem.Alias.ContainsEx(filter))
|
||||
// return true;
|
||||
|
||||
if (filter.Length == 1)
|
||||
return item.Binding.Input.ToLower()
|
||||
.Replace("ctrl+", "")
|
||||
.Replace("shift+", "")
|
||||
.Replace("alt+", "") == filter.ToLower();
|
||||
|
||||
if (item.Binding.Command.ToLower().Contains(filter))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (filter == "" || item.Text.ToLower().Contains(filter) ||
|
||||
item.SecondaryText.ToLower().Contains(filter))
|
||||
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SelectFirst()
|
||||
{
|
||||
if (MainListView.Items.Count > 0)
|
||||
{
|
||||
MainListView.SelectedIndex = 0;
|
||||
MainListView.ScrollIntoView(MainListView.SelectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
void ExecuteInternal()
|
||||
{
|
||||
if (MainListView.SelectedItem != null)
|
||||
{
|
||||
CommandPaletteItem? item = MainListView.SelectedItem as CommandPaletteItem;
|
||||
MainForm.Instance?.HideCommandPalette();
|
||||
item?.Action?.Invoke();
|
||||
//MainForm.Instance.Voodoo(); //TODO: Voodoo
|
||||
}
|
||||
}
|
||||
|
||||
public void SetItems(IEnumerable<CommandPaletteItem> items)
|
||||
{
|
||||
Items.Clear();
|
||||
|
||||
foreach (var i in items)
|
||||
Items.Add(i);
|
||||
}
|
||||
|
||||
public void AdjustHeight()
|
||||
{
|
||||
double actualHeight = SearchControl.ActualHeight + MainListView.ActualHeight + 5 + 16;
|
||||
int dpi = MainForm.GetDpi(MainForm.Instance!.Handle);
|
||||
MainForm.Instance.CommandPaletteHost.Height = (int)(actualHeight / 96.0 * dpi);
|
||||
}
|
||||
}
|
||||
27
src/MpvNet.Windows/WPF/Controls/HyperlinkEx.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
using MpvNet.Help;
|
||||
|
||||
// TODO: change namespace to MpvNet.Windows.WPF.Controls
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public class HyperlinkEx : Hyperlink
|
||||
{
|
||||
void HyperLinkEx_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
ProcessHelp.ShellExecute(e.Uri.AbsoluteUri);
|
||||
}
|
||||
|
||||
public void SetURL(string? url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return;
|
||||
|
||||
NavigateUri = new Uri(url);
|
||||
RequestNavigate += HyperLinkEx_RequestNavigate;
|
||||
Inlines.Clear();
|
||||
Inlines.Add(url);
|
||||
}
|
||||
}
|
||||
74
src/MpvNet.Windows/WPF/Controls/OptionSettingControl.xaml
Normal file
@@ -0,0 +1,74 @@
|
||||
<UserControl
|
||||
x:Name="OptionSettingControl1"
|
||||
x:Class="MpvNet.Windows.WPF.OptionSettingControl"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MpvNet.Windows.WPF"
|
||||
mc:Ignorable="d"
|
||||
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
|
||||
<Grid Margin="20,0">
|
||||
<StackPanel>
|
||||
<TextBox
|
||||
x:Name="TitleTextBox"
|
||||
FontSize="24"
|
||||
Margin="0,10"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Foreground="{Binding Theme.Heading}"
|
||||
Background="{Binding Theme.Background}"
|
||||
/>
|
||||
|
||||
<ItemsControl x:Name="ItemsControl">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<RadioButton
|
||||
x:Name="RadioButton"
|
||||
VerticalContentAlignment="Center"
|
||||
IsChecked="{Binding Checked}"
|
||||
GroupName="{Binding OptionSetting.Name}"
|
||||
Content="{Binding Text}"
|
||||
FontSize="16"
|
||||
FontWeight="Normal"
|
||||
VerticalAlignment="Top"
|
||||
Foreground="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Foreground}"
|
||||
/>
|
||||
|
||||
<TextBox
|
||||
x:Name="ItemHelpTextBox"
|
||||
TextWrapping="WrapWithOverflow"
|
||||
Text="{Binding Help}"
|
||||
Visibility="{Binding Visibility}"
|
||||
Margin="10,0,0,0" BorderThickness="0"
|
||||
IsReadOnly="True" Padding="7,0,0,0"
|
||||
MinHeight="0"
|
||||
Foreground="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Foreground2}"
|
||||
Background="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Background}"
|
||||
/>
|
||||
|
||||
</WrapPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<TextBox
|
||||
x:Name="HelpTextBox"
|
||||
TextWrapping="WrapWithOverflow"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Margin="0,10,0,0"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
/>
|
||||
|
||||
<TextBlock x:Name="LinkTextBlock" Margin="0,10">
|
||||
<local:HyperlinkEx x:Name="Link"></local:HyperlinkEx>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
60
src/MpvNet.Windows/WPF/Controls/OptionSettingControl.xaml.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public partial class OptionSettingControl : UserControl, ISettingControl
|
||||
{
|
||||
OptionSetting OptionSetting;
|
||||
|
||||
public OptionSettingControl(OptionSetting optionSetting)
|
||||
{
|
||||
OptionSetting = optionSetting;
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
TitleTextBox.Text = optionSetting.Name;
|
||||
|
||||
if (string.IsNullOrEmpty(optionSetting.Help))
|
||||
HelpTextBox.Visibility = Visibility.Collapsed;
|
||||
|
||||
HelpTextBox.Text = optionSetting.Help;
|
||||
ItemsControl.ItemsSource = optionSetting.Options;
|
||||
|
||||
if (string.IsNullOrEmpty(optionSetting.URL))
|
||||
LinkTextBlock.Visibility = Visibility.Collapsed;
|
||||
|
||||
Link.SetURL(optionSetting.URL);
|
||||
}
|
||||
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
public Setting Setting => OptionSetting;
|
||||
|
||||
public bool Contains(string searchString) => ContainsInternal(searchString.ToLower());
|
||||
|
||||
public bool ContainsInternal(string search)
|
||||
{
|
||||
if (TitleTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
if (HelpTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
foreach (var i in OptionSetting.Options)
|
||||
{
|
||||
if (i.Text?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
if (i.Help?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
if (i.Name?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
90
src/MpvNet.Windows/WPF/Controls/SearchControl.xaml
Normal file
@@ -0,0 +1,90 @@
|
||||
<UserControl
|
||||
x:Class="MpvNet.Windows.WPF.Controls.SearchControl"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:controls="clr-namespace:MpvNet.Windows.WPF.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
>
|
||||
|
||||
<Grid Background="{Binding Theme.Background}">
|
||||
|
||||
<TextBlock
|
||||
Name="HintTextBlock"
|
||||
Padding="6,1"
|
||||
Text="Find a setting"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{Binding Theme.Foreground2}"
|
||||
Background="{Binding Theme.Background}"
|
||||
/>
|
||||
|
||||
<TextBox
|
||||
Name="SearchTextBox"
|
||||
Height="25"
|
||||
BorderThickness="2"
|
||||
Padding="2"
|
||||
Background="Transparent"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
CaretBrush="{Binding Theme.Foreground}"
|
||||
GotFocus="SearchTextBox_GotFocus"
|
||||
PreviewMouseUp="SearchTextBox_PreviewMouseUp"
|
||||
Text="{Binding RelativeSource={RelativeSource FindAncestor,
|
||||
AncestorType={x:Type controls:SearchControl}},
|
||||
Path=Text, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
|
||||
<Button
|
||||
Name="SearchClearButton"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Marlett"
|
||||
FontSize="10"
|
||||
Width="17"
|
||||
Height="17"
|
||||
Margin="2,0,4,0"
|
||||
Visibility="Hidden"
|
||||
Command="{Binding RelativeSource={RelativeSource FindAncestor,
|
||||
AncestorType={x:Type controls:SearchControl}}, Path=ClearCommand}"
|
||||
>r
|
||||
|
||||
<Button.Resources>
|
||||
<Style TargetType="{x:Type Border}">
|
||||
<Setter Property="CornerRadius" Value="3"/>
|
||||
</Style>
|
||||
</Button.Resources>
|
||||
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="{Binding Theme.Background}"/>
|
||||
<Setter Property="Foreground" Value="{Binding Theme.Foreground2}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
BorderThickness="1"
|
||||
BorderBrush="{TemplateBinding Foreground}"
|
||||
SnapsToDevicePixels="True"
|
||||
>
|
||||
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="{Binding Theme.Heading}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
72
src/MpvNet.Windows/WPF/Controls/SearchControl.xaml.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF.Controls;
|
||||
|
||||
public partial class SearchControl : UserControl
|
||||
{
|
||||
string? _hintText;
|
||||
bool _gotFocus;
|
||||
|
||||
public bool HideClearButton { get; set; }
|
||||
|
||||
public SearchControl() => InitializeComponent();
|
||||
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
public string HintText {
|
||||
get => _hintText ??= "";
|
||||
set {
|
||||
_hintText = value;
|
||||
UpdateControls();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
void Clear()
|
||||
{
|
||||
Text = "";
|
||||
Keyboard.Focus(SearchTextBox);
|
||||
}
|
||||
|
||||
void UpdateControls()
|
||||
{
|
||||
HintTextBlock.Text = Text == "" ? HintText : "";
|
||||
|
||||
if (Text == "" || HideClearButton)
|
||||
SearchClearButton.Visibility = Visibility.Hidden;
|
||||
else
|
||||
SearchClearButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty =
|
||||
DependencyProperty.Register("Text", typeof(string),
|
||||
typeof(SearchControl), new PropertyMetadata(OnCustomerChangedCallBack));
|
||||
|
||||
static void OnCustomerChangedCallBack(
|
||||
DependencyObject sender, DependencyPropertyChangedEventArgs e) =>
|
||||
(sender as SearchControl)?.UpdateControls();
|
||||
|
||||
void SearchTextBox_GotFocus(object sender, RoutedEventArgs e) => _gotFocus = true;
|
||||
|
||||
void SearchTextBox_PreviewMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (_gotFocus)
|
||||
{
|
||||
SearchTextBox?.SelectAll();
|
||||
_gotFocus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/MpvNet.Windows/WPF/Controls/StringSettingControl.xaml
Normal file
@@ -0,0 +1,68 @@
|
||||
<UserControl
|
||||
x:Name="StringSettingControl1"
|
||||
x:Class="MpvNet.Windows.WPF.StringSettingControl"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MpvNet.Windows.WPF"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
|
||||
<Grid Margin="20,0">
|
||||
<StackPanel>
|
||||
<TextBox
|
||||
x:Name="TitleTextBox"
|
||||
FontSize="24"
|
||||
Margin="0,10"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Foreground="{Binding Theme.Heading}"
|
||||
Background="{Binding Theme.Background}"
|
||||
/>
|
||||
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
x:Name="ValueTextBox"
|
||||
Text="{Binding Path=Text, ElementName=StringSettingControl1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="150"
|
||||
FontSize="13"
|
||||
Padding="2"
|
||||
HorizontalAlignment="Left"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
CaretBrush="{Binding Theme.Foreground}"
|
||||
TextChanged="ValueTextBox_TextChanged"
|
||||
/>
|
||||
|
||||
<Button
|
||||
x:Name="Button"
|
||||
Width="25"
|
||||
Height="25"
|
||||
Grid.Column="1"
|
||||
Margin="5,0,0,0"
|
||||
Click="Button_Click"
|
||||
>...
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBox
|
||||
x:Name="HelpTextBox"
|
||||
TextWrapping="WrapWithOverflow"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
/>
|
||||
|
||||
<TextBlock x:Name="LinkTextBlock" Margin="0,10">
|
||||
<local:HyperlinkEx x:Name="Link"></local:HyperlinkEx>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
128
src/MpvNet.Windows/WPF/Controls/StringSettingControl.xaml.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
using Forms = System.Windows.Forms;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public partial class StringSettingControl : UserControl, ISettingControl
|
||||
{
|
||||
StringSetting StringSetting;
|
||||
|
||||
public StringSettingControl(StringSetting stringSetting)
|
||||
{
|
||||
StringSetting = stringSetting;
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
TitleTextBox.Text = stringSetting.Name;
|
||||
HelpTextBox.Text = stringSetting.Help;
|
||||
ValueTextBox.Text = StringSetting.Value;
|
||||
|
||||
if (StringSetting.Width > 0)
|
||||
ValueTextBox.Width = StringSetting.Width;
|
||||
|
||||
if (StringSetting.Type != "folder" && StringSetting.Type != "color")
|
||||
Button.Visibility = Visibility.Hidden;
|
||||
|
||||
Link.SetURL(StringSetting.URL);
|
||||
|
||||
if (string.IsNullOrEmpty(stringSetting.URL))
|
||||
LinkTextBlock.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
public bool Contains(string search)
|
||||
{
|
||||
if (TitleTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
if (HelpTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
if (ValueTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Setting Setting => StringSetting;
|
||||
|
||||
public string? Text
|
||||
{
|
||||
get => StringSetting.Value;
|
||||
set => StringSetting.Value = value;
|
||||
}
|
||||
|
||||
void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
switch (StringSetting.Type)
|
||||
{
|
||||
case "folder":
|
||||
{
|
||||
var dialog = new Forms.FolderBrowserDialog { InitialDirectory = ValueTextBox.Text };
|
||||
|
||||
if (dialog.ShowDialog() == Forms.DialogResult.OK)
|
||||
ValueTextBox.Text = dialog.SelectedPath;
|
||||
}
|
||||
break;
|
||||
case "color":
|
||||
using (var dialog = new Forms.ColorDialog())
|
||||
{
|
||||
dialog.FullOpen = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ValueTextBox.Text))
|
||||
{
|
||||
Color col = GetColor(ValueTextBox.Text);
|
||||
dialog.Color = System.Drawing.Color.FromArgb(col.A, col.R, col.G, col.B);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (dialog.ShowDialog() == Forms.DialogResult.OK)
|
||||
ValueTextBox.Text = "#" + dialog.Color.ToArgb().ToString("X8");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ValueTextBox_TextChanged(object sender, TextChangedEventArgs e) => Update();
|
||||
|
||||
Color GetColor(string value)
|
||||
{
|
||||
if (value.Contains('/'))
|
||||
{
|
||||
string[] a = value.Split('/');
|
||||
|
||||
if (a.Length == 3)
|
||||
return Color.FromRgb(ToByte(a[0]), ToByte(a[1]), ToByte(a[2]));
|
||||
else if (a.Length == 4)
|
||||
return Color.FromArgb(ToByte(a[3]), ToByte(a[0]), ToByte(a[1]), ToByte(a[2]));
|
||||
}
|
||||
|
||||
return (Color)ColorConverter.ConvertFromString(value);
|
||||
|
||||
byte ToByte(string val) => Convert.ToByte(Convert.ToSingle(val, CultureInfo.InvariantCulture) * 255);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (StringSetting.Type == "color")
|
||||
{
|
||||
Color color = Colors.Transparent;
|
||||
|
||||
if (ValueTextBox.Text != "")
|
||||
try {
|
||||
color = GetColor(ValueTextBox.Text);
|
||||
} catch {}
|
||||
|
||||
ValueTextBox.Background = new SolidColorBrush(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools.Converter;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class BorderElement
|
||||
{
|
||||
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.RegisterAttached(
|
||||
"CornerRadius", typeof(CornerRadius), typeof(BorderElement), new FrameworkPropertyMetadata(default(CornerRadius), FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetCornerRadius(DependencyObject element, CornerRadius value) => element.SetValue(CornerRadiusProperty, value);
|
||||
|
||||
public static CornerRadius GetCornerRadius(DependencyObject element) => (CornerRadius) element.GetValue(CornerRadiusProperty);
|
||||
|
||||
public static readonly DependencyProperty CircularProperty = DependencyProperty.RegisterAttached(
|
||||
"Circular", typeof(bool), typeof(BorderElement), new PropertyMetadata(ValueBoxes.FalseBox, OnCircularChanged));
|
||||
|
||||
private static void OnCircularChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is Border border)
|
||||
{
|
||||
if ((bool) e.NewValue)
|
||||
{
|
||||
var binding = new MultiBinding
|
||||
{
|
||||
Converter = new BorderCircularConverter()
|
||||
};
|
||||
binding.Bindings.Add(new Binding(FrameworkElement.ActualWidthProperty.Name) { Source = border });
|
||||
binding.Bindings.Add(new Binding(FrameworkElement.ActualHeightProperty.Name) { Source = border });
|
||||
border.SetBinding(Border.CornerRadiusProperty, binding);
|
||||
}
|
||||
else
|
||||
{
|
||||
BindingOperations.ClearBinding(border, FrameworkElement.ActualWidthProperty);
|
||||
BindingOperations.ClearBinding(border, FrameworkElement.ActualHeightProperty);
|
||||
BindingOperations.ClearBinding(border, Border.CornerRadiusProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetCircular(DependencyObject element, bool value)
|
||||
=> element.SetValue(CircularProperty, ValueBoxes.BooleanBox(value));
|
||||
|
||||
public static bool GetCircular(DependencyObject element)
|
||||
=> (bool) element.GetValue(CircularProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class IconElement
|
||||
{
|
||||
public static readonly DependencyProperty GeometryProperty = DependencyProperty.RegisterAttached(
|
||||
"Geometry", typeof(Geometry), typeof(IconElement), new PropertyMetadata(default(Geometry)));
|
||||
|
||||
public static void SetGeometry(DependencyObject element, Geometry value)
|
||||
=> element.SetValue(GeometryProperty, value);
|
||||
|
||||
public static Geometry GetGeometry(DependencyObject element)
|
||||
=> (Geometry) element.GetValue(GeometryProperty);
|
||||
|
||||
public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached(
|
||||
"Width", typeof(double), typeof(IconElement), new PropertyMetadata(double.NaN));
|
||||
|
||||
public static void SetWidth(DependencyObject element, double value)
|
||||
=> element.SetValue(WidthProperty, value);
|
||||
|
||||
public static double GetWidth(DependencyObject element)
|
||||
=> (double) element.GetValue(WidthProperty);
|
||||
|
||||
public static readonly DependencyProperty HeightProperty = DependencyProperty.RegisterAttached(
|
||||
"Height", typeof(double), typeof(IconElement), new PropertyMetadata(double.NaN));
|
||||
|
||||
public static void SetHeight(DependencyObject element, double value)
|
||||
=> element.SetValue(HeightProperty, value);
|
||||
|
||||
public static double GetHeight(DependencyObject element)
|
||||
=> (double) element.GetValue(HeightProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Interop;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class MenuTopLineAttach
|
||||
{
|
||||
public static readonly DependencyProperty PopupProperty = DependencyProperty.RegisterAttached(
|
||||
"Popup", typeof(Popup), typeof(MenuTopLineAttach), new PropertyMetadata(default(Popup), OnPopupChanged));
|
||||
|
||||
private static void OnPopupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var topLine = (FrameworkElement)d;
|
||||
|
||||
if (e.NewValue is Popup)
|
||||
{
|
||||
Popup popup = e.NewValue as Popup;
|
||||
MenuItem menuItem = popup.TemplatedParent as MenuItem;
|
||||
SetTopLine(menuItem, topLine);
|
||||
menuItem.Loaded += MenuItem_Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MenuItem_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var menuItem = (FrameworkElement)sender;
|
||||
menuItem.Unloaded += MenuItem_Unloaded;
|
||||
var topLine = GetTopLine(menuItem);
|
||||
var popup = GetPopup(topLine);
|
||||
if (popup != null)
|
||||
{
|
||||
popup.Opened += Popup_Opened;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MenuItem_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var menuItem = (FrameworkElement)sender;
|
||||
menuItem.Unloaded -= MenuItem_Unloaded;
|
||||
var topLine = GetTopLine(menuItem);
|
||||
var popup = GetPopup(topLine);
|
||||
if (popup != null)
|
||||
{
|
||||
popup.Opened -= Popup_Opened;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Popup_Opened(object sender, EventArgs e)
|
||||
{
|
||||
var popup = (Popup)sender;
|
||||
if (popup.TemplatedParent is MenuItem menuItem)
|
||||
{
|
||||
var topLine = GetTopLine(menuItem);
|
||||
if (topLine == null) return;
|
||||
|
||||
topLine.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
topLine.Width = menuItem.ActualWidth;
|
||||
topLine.Margin = new Thickness();
|
||||
|
||||
var positionLeftTop = menuItem.PointToScreen(new Point());
|
||||
var positionRightBottom = menuItem.PointToScreen(new Point(menuItem.ActualWidth, menuItem.ActualHeight));
|
||||
ScreenHelper.FindMonitorRectsFromPoint(InteropMethods.GetCursorPos(), out Rect monitorRect, out var workAreaRect);
|
||||
var panel = VisualHelper.GetParent<Panel>(topLine);
|
||||
|
||||
if (positionLeftTop.X < 0)
|
||||
{
|
||||
|
||||
topLine.Margin = new Thickness(positionLeftTop.X - panel.Margin.Left, 0, 0, 0);
|
||||
}
|
||||
else if (positionLeftTop.X + panel.ActualWidth > workAreaRect.Right)
|
||||
{
|
||||
var overflowWidth = positionRightBottom.X - workAreaRect.Right;
|
||||
if (overflowWidth > 0)
|
||||
{
|
||||
topLine.Width -= overflowWidth + panel.Margin.Right;
|
||||
}
|
||||
topLine.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
topLine.Margin = new Thickness(positionLeftTop.X + panel.ActualWidth - workAreaRect.Right + panel.Margin.Right, 0, 0, 0);
|
||||
}
|
||||
|
||||
if (positionRightBottom.Y > workAreaRect.Bottom)
|
||||
{
|
||||
topLine.Width = 0;
|
||||
topLine.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
topLine.Margin = new Thickness();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetPopup(DependencyObject element, Popup value)
|
||||
=> element.SetValue(PopupProperty, value);
|
||||
|
||||
public static Popup GetPopup(DependencyObject element)
|
||||
=> (Popup)element.GetValue(PopupProperty);
|
||||
|
||||
internal static readonly DependencyProperty TopLineProperty = DependencyProperty.RegisterAttached(
|
||||
"TopLine", typeof(FrameworkElement), typeof(MenuTopLineAttach), new PropertyMetadata(default(FrameworkElement)));
|
||||
|
||||
internal static void SetTopLine(DependencyObject element, FrameworkElement value)
|
||||
=> element.SetValue(TopLineProperty, value);
|
||||
|
||||
internal static FrameworkElement GetTopLine(DependencyObject element)
|
||||
=> (FrameworkElement)element.GetValue(TopLineProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
using System.Windows;
|
||||
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class ScrollViewerAttach
|
||||
{
|
||||
public static readonly DependencyProperty AutoHideProperty = DependencyProperty.RegisterAttached(
|
||||
"AutoHide", typeof(bool), typeof(ScrollViewerAttach), new FrameworkPropertyMetadata(ValueBoxes.TrueBox, FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetAutoHide(DependencyObject element, bool value)
|
||||
=> element.SetValue(AutoHideProperty, value);
|
||||
|
||||
public static bool GetAutoHide(DependencyObject element)
|
||||
=> (bool) element.GetValue(AutoHideProperty);
|
||||
}
|
||||
}
|
||||
192
src/MpvNet.Windows/WPF/HandyControl/Controls/ScrollViewer.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class ScrollViewer : System.Windows.Controls.ScrollViewer
|
||||
{
|
||||
private double _totalVerticalOffset;
|
||||
|
||||
private double _totalHorizontalOffset;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
||||
"Orientation", typeof(Orientation), typeof(ScrollViewer), new PropertyMetadata(Orientation.Vertical));
|
||||
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => (Orientation) GetValue(OrientationProperty);
|
||||
set => SetValue(OrientationProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CanMouseWheelProperty = DependencyProperty.Register(
|
||||
"CanMouseWheel", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool CanMouseWheel
|
||||
{
|
||||
get => (bool) GetValue(CanMouseWheelProperty);
|
||||
set => SetValue(CanMouseWheelProperty, ValueBoxes.BooleanBox(value));
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
if (!CanMouseWheel) return;
|
||||
|
||||
if (!IsInertiaEnabled)
|
||||
{
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
base.OnMouseWheel(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
_totalHorizontalOffset = HorizontalOffset;
|
||||
CurrentHorizontalOffset = HorizontalOffset;
|
||||
_totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
|
||||
CurrentHorizontalOffset = _totalHorizontalOffset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
e.Handled = true;
|
||||
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalVerticalOffset = VerticalOffset;
|
||||
CurrentVerticalOffset = VerticalOffset;
|
||||
}
|
||||
_totalVerticalOffset = Math.Min(Math.Max(0, _totalVerticalOffset - e.Delta), ScrollableHeight);
|
||||
ScrollToVerticalOffsetWithAnimation(_totalVerticalOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalHorizontalOffset = HorizontalOffset;
|
||||
CurrentHorizontalOffset = HorizontalOffset;
|
||||
}
|
||||
_totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
|
||||
ScrollToHorizontalOffsetWithAnimation(_totalHorizontalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ScrollToTopInternal(double milliseconds = 500)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalVerticalOffset = VerticalOffset;
|
||||
CurrentVerticalOffset = VerticalOffset;
|
||||
}
|
||||
ScrollToVerticalOffsetWithAnimation(0, milliseconds);
|
||||
}
|
||||
|
||||
public void ScrollToVerticalOffsetWithAnimation(double offset, double milliseconds = 500)
|
||||
{
|
||||
var animation = AnimationHelper.CreateAnimation(offset, milliseconds);
|
||||
animation.EasingFunction = new CubicEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
};
|
||||
animation.FillBehavior = FillBehavior.Stop;
|
||||
animation.Completed += (s, e1) =>
|
||||
{
|
||||
CurrentVerticalOffset = offset;
|
||||
_isRunning = false;
|
||||
};
|
||||
_isRunning = true;
|
||||
|
||||
BeginAnimation(CurrentVerticalOffsetProperty, animation, HandoffBehavior.Compose);
|
||||
}
|
||||
|
||||
public void ScrollToHorizontalOffsetWithAnimation(double offset, double milliseconds = 500)
|
||||
{
|
||||
var animation = AnimationHelper.CreateAnimation(offset, milliseconds);
|
||||
animation.EasingFunction = new CubicEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
};
|
||||
animation.FillBehavior = FillBehavior.Stop;
|
||||
animation.Completed += (s, e1) =>
|
||||
{
|
||||
CurrentHorizontalOffset = offset;
|
||||
_isRunning = false;
|
||||
};
|
||||
_isRunning = true;
|
||||
|
||||
BeginAnimation(CurrentHorizontalOffsetProperty, animation, HandoffBehavior.Compose);
|
||||
}
|
||||
|
||||
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) =>
|
||||
IsPenetrating ? null : base.HitTestCore(hitTestParameters);
|
||||
|
||||
public static readonly DependencyProperty IsInertiaEnabledProperty = DependencyProperty.RegisterAttached(
|
||||
"IsInertiaEnabled", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public static void SetIsInertiaEnabled(DependencyObject element, bool value) => element.SetValue(IsInertiaEnabledProperty, ValueBoxes.BooleanBox(value));
|
||||
|
||||
public static bool GetIsInertiaEnabled(DependencyObject element) => (bool) element.GetValue(IsInertiaEnabledProperty);
|
||||
|
||||
public bool IsInertiaEnabled
|
||||
{
|
||||
get => (bool) GetValue(IsInertiaEnabledProperty);
|
||||
set => SetValue(IsInertiaEnabledProperty, ValueBoxes.BooleanBox(value));
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsPenetratingProperty = DependencyProperty.RegisterAttached(
|
||||
"IsPenetrating", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsPenetrating
|
||||
{
|
||||
get => (bool) GetValue(IsPenetratingProperty);
|
||||
set => SetValue(IsPenetratingProperty, ValueBoxes.BooleanBox(value));
|
||||
}
|
||||
|
||||
public static void SetIsPenetrating(DependencyObject element, bool value) => element.SetValue(IsPenetratingProperty, ValueBoxes.BooleanBox(value));
|
||||
|
||||
public static bool GetIsPenetrating(DependencyObject element) => (bool) element.GetValue(IsPenetratingProperty);
|
||||
|
||||
internal static readonly DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register(
|
||||
"CurrentVerticalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.Double0Box, OnCurrentVerticalOffsetChanged));
|
||||
|
||||
private static void OnCurrentVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ScrollViewer ctl && e.NewValue is double v)
|
||||
{
|
||||
ctl.ScrollToVerticalOffset(v);
|
||||
}
|
||||
}
|
||||
|
||||
internal double CurrentVerticalOffset
|
||||
{
|
||||
get => (double) GetValue(CurrentVerticalOffsetProperty);
|
||||
set => SetValue(CurrentVerticalOffsetProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty CurrentHorizontalOffsetProperty = DependencyProperty.Register(
|
||||
"CurrentHorizontalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.Double0Box, OnCurrentHorizontalOffsetChanged));
|
||||
|
||||
private static void OnCurrentHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ScrollViewer ctl && e.NewValue is double v)
|
||||
{
|
||||
ctl.ScrollToHorizontalOffset(v);
|
||||
}
|
||||
}
|
||||
|
||||
internal double CurrentHorizontalOffset
|
||||
{
|
||||
get => (double) GetValue(CurrentHorizontalOffsetProperty);
|
||||
set => SetValue(CurrentHorizontalOffsetProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/MpvNet.Windows/WPF/HandyControl/Controls/SimplePanel.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class SimplePanel : Panel
|
||||
{
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
var maxSize = new Size();
|
||||
|
||||
foreach (UIElement child in InternalChildren)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
child.Measure(constraint);
|
||||
maxSize.Width = Math.Max(maxSize.Width, child.DesiredSize.Width);
|
||||
maxSize.Height = Math.Max(maxSize.Height, child.DesiredSize.Height);
|
||||
}
|
||||
}
|
||||
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size arrangeSize)
|
||||
{
|
||||
foreach (UIElement child in InternalChildren)
|
||||
{
|
||||
child?.Arrange(new Rect(arrangeSize));
|
||||
}
|
||||
|
||||
return arrangeSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/MpvNet.Windows/WPF/HandyControl/Data/ValueBoxes.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
namespace HandyControl.Data
|
||||
{
|
||||
internal static class ValueBoxes
|
||||
{
|
||||
internal static object TrueBox = true;
|
||||
internal static object FalseBox = false;
|
||||
internal static object Double0Box = .0;
|
||||
internal static object Double01Box = .1;
|
||||
internal static object Double1Box = 1.0;
|
||||
internal static object Double10Box = 10.0;
|
||||
internal static object Double20Box = 20.0;
|
||||
internal static object Double100Box = 100.0;
|
||||
internal static object Double200Box = 200.0;
|
||||
internal static object Double300Box = 300.0;
|
||||
internal static object DoubleNeg1Box = -1.0;
|
||||
internal static object Int0Box = 0;
|
||||
internal static object Int1Box = 1;
|
||||
internal static object Int2Box = 2;
|
||||
internal static object Int5Box = 5;
|
||||
internal static object Int99Box = 99;
|
||||
internal static object BooleanBox(bool value) => value ? TrueBox : FalseBox;
|
||||
}
|
||||
}
|
||||
80
src/MpvNet.Windows/WPF/HandyControl/Tools/AnimationHelper.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Tools
|
||||
{
|
||||
public class AnimationHelper
|
||||
{
|
||||
public static ThicknessAnimation CreateAnimation(Thickness thickness = default, double milliseconds = 200)
|
||||
{
|
||||
return new ThicknessAnimation(thickness, new Duration(TimeSpan.FromMilliseconds(milliseconds)))
|
||||
{
|
||||
EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut }
|
||||
};
|
||||
}
|
||||
|
||||
public static DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200)
|
||||
{
|
||||
return new DoubleAnimation(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds)))
|
||||
{
|
||||
EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut }
|
||||
};
|
||||
}
|
||||
|
||||
internal static void DecomposeGeometryStr(string geometryStr, out double[] arr)
|
||||
{
|
||||
var collection = Regex.Matches(geometryStr, RegexPatterns.DigitsPattern);
|
||||
arr = new double[collection.Count];
|
||||
for (var i = 0; i < collection.Count; i++)
|
||||
{
|
||||
arr[i] = collection[i].Value.Value<double>();
|
||||
}
|
||||
}
|
||||
|
||||
internal static Geometry ComposeGeometry(string[] strings, double[] arr)
|
||||
{
|
||||
var builder = new StringBuilder(strings[0]);
|
||||
for (var i = 0; i < arr.Length; i++)
|
||||
{
|
||||
var s = strings[i + 1];
|
||||
var n = arr[i];
|
||||
if (!double.IsNaN(n))
|
||||
{
|
||||
builder.Append(n).Append(s);
|
||||
}
|
||||
}
|
||||
|
||||
return Geometry.Parse(builder.ToString());
|
||||
}
|
||||
|
||||
internal static Geometry InterpolateGeometry(double[] from, double[] to, double progress, string[] strings)
|
||||
{
|
||||
var accumulated = new double[to.Length];
|
||||
for (var i = 0; i < to.Length; i++)
|
||||
{
|
||||
var fromValue = from[i];
|
||||
accumulated[i] = fromValue + (to[i] - fromValue) * progress;
|
||||
}
|
||||
|
||||
return ComposeGeometry(strings, accumulated);
|
||||
}
|
||||
|
||||
internal static double[] InterpolateGeometryValue(double[] from, double[] to, double progress)
|
||||
{
|
||||
var accumulated = new double[to.Length];
|
||||
for (var i = 0; i < to.Length; i++)
|
||||
{
|
||||
var fromValue = from[i];
|
||||
accumulated[i] = fromValue + (to[i] - fromValue) * progress;
|
||||
}
|
||||
|
||||
return accumulated;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace HandyControl.Tools.Converter
|
||||
{
|
||||
public class BorderCircularConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length == 2 && values[0] is double width && values[1] is double height)
|
||||
{
|
||||
if (width < double.Epsilon || height < double.Epsilon)
|
||||
{
|
||||
return new CornerRadius();
|
||||
}
|
||||
|
||||
var min = Math.Min(width, height);
|
||||
return new CornerRadius(min / 2);
|
||||
}
|
||||
|
||||
return DependencyProperty.UnsetValue;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace HandyControl.Tools.Extension
|
||||
{
|
||||
public static class StringExtension
|
||||
{
|
||||
public static T Value<T>(this string input)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (T) TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(input);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static object Value(this string input, Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
return TypeDescriptor.GetConverter(type).ConvertFromString(input);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
using HandyControl.Tools.Interop;
|
||||
|
||||
namespace HandyControl.Tools
|
||||
{
|
||||
internal class ScreenHelper
|
||||
{
|
||||
internal static void FindMaximumSingleMonitorRectangle(Rect windowRect, out Rect screenSubRect, out Rect monitorRect)
|
||||
{
|
||||
var windowRect2 = new InteropValues.RECT(windowRect);
|
||||
FindMaximumSingleMonitorRectangle(windowRect2, out var rect, out var rect2);
|
||||
screenSubRect = new Rect(rect.Position, rect.Size);
|
||||
monitorRect = new Rect(rect2.Position, rect2.Size);
|
||||
}
|
||||
|
||||
private static void FindMaximumSingleMonitorRectangle(InteropValues.RECT windowRect, out InteropValues.RECT screenSubRect, out InteropValues.RECT monitorRect)
|
||||
{
|
||||
var rects = new List<InteropValues.RECT>();
|
||||
InteropMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
|
||||
delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref InteropValues.RECT rect, IntPtr lpData)
|
||||
{
|
||||
var monitorInfo = default(InteropValues.MONITORINFO);
|
||||
monitorInfo.cbSize = (uint) Marshal.SizeOf(typeof(InteropValues.MONITORINFO));
|
||||
InteropMethods.GetMonitorInfo(hMonitor, ref monitorInfo);
|
||||
rects.Add(monitorInfo.rcWork);
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
|
||||
var num = 0L;
|
||||
|
||||
screenSubRect = new InteropValues.RECT
|
||||
{
|
||||
Left = 0,
|
||||
Right = 0,
|
||||
Top = 0,
|
||||
Bottom = 0
|
||||
};
|
||||
|
||||
monitorRect = new InteropValues.RECT
|
||||
{
|
||||
Left = 0,
|
||||
Right = 0,
|
||||
Top = 0,
|
||||
Bottom = 0
|
||||
};
|
||||
|
||||
foreach (var current in rects)
|
||||
{
|
||||
var rect = current;
|
||||
InteropMethods.IntersectRect(out var rECT2, ref rect, ref windowRect);
|
||||
var num2 = (long) (rECT2.Width * rECT2.Height);
|
||||
if (num2 > num)
|
||||
{
|
||||
screenSubRect = rECT2;
|
||||
monitorRect = current;
|
||||
num = num2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void FindMonitorRectsFromPoint(Point point, out Rect monitorRect, out Rect workAreaRect)
|
||||
{
|
||||
var intPtr = InteropMethods.MonitorFromPoint(new InteropValues.POINT
|
||||
{
|
||||
X = (int) point.X,
|
||||
Y = (int) point.Y
|
||||
}, 2);
|
||||
|
||||
monitorRect = new Rect(0.0, 0.0, 0.0, 0.0);
|
||||
workAreaRect = new Rect(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
if (intPtr != IntPtr.Zero)
|
||||
{
|
||||
InteropValues.MONITORINFO monitorInfo = default;
|
||||
monitorInfo.cbSize = (uint) Marshal.SizeOf(typeof(InteropValues.MONITORINFO));
|
||||
InteropMethods.GetMonitorInfo(intPtr, ref monitorInfo);
|
||||
monitorRect = new Rect(monitorInfo.rcMonitor.Position, monitorInfo.rcMonitor.Size);
|
||||
workAreaRect = new Rect(monitorInfo.rcWork.Position, monitorInfo.rcWork.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Tools.Interop;
|
||||
|
||||
namespace HandyControl.Tools
|
||||
{
|
||||
public static class VisualHelper
|
||||
{
|
||||
internal static VisualStateGroup TryGetVisualStateGroup(DependencyObject d, string groupName)
|
||||
{
|
||||
var root = GetImplementationRoot(d);
|
||||
if (root == null) return null;
|
||||
|
||||
return VisualStateManager
|
||||
.GetVisualStateGroups(root)?
|
||||
.OfType<VisualStateGroup>()
|
||||
.FirstOrDefault(group => string.CompareOrdinal(groupName, group.Name) == 0);
|
||||
}
|
||||
|
||||
internal static FrameworkElement GetImplementationRoot(DependencyObject d) =>
|
||||
1 == VisualTreeHelper.GetChildrenCount(d)
|
||||
? VisualTreeHelper.GetChild(d, 0) as FrameworkElement
|
||||
: null;
|
||||
|
||||
public static T GetChild<T>(DependencyObject d) where T : DependencyObject
|
||||
{
|
||||
if (d == null) return default;
|
||||
if (d is T t) return t;
|
||||
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(d); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(d, i);
|
||||
|
||||
var result = GetChild<T>(child);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static T GetParent<T>(DependencyObject d) where T : DependencyObject
|
||||
{
|
||||
if (d == null)
|
||||
return default;
|
||||
|
||||
if (d is T)
|
||||
return d as T;
|
||||
|
||||
if (d is Window)
|
||||
return null;
|
||||
|
||||
return GetParent<T>(VisualTreeHelper.GetParent(d));
|
||||
}
|
||||
|
||||
public static IntPtr GetHandle(this Visual visual) => (PresentationSource.FromVisual(visual) as HwndSource)?.Handle ?? IntPtr.Zero;
|
||||
|
||||
internal static void HitTestVisibleElements(Visual visual, HitTestResultCallback resultCallback, HitTestParameters parameters) =>
|
||||
VisualTreeHelper.HitTest(visual, ExcludeNonVisualElements, resultCallback, parameters);
|
||||
|
||||
private static HitTestFilterBehavior ExcludeNonVisualElements(DependencyObject potentialHitTestTarget)
|
||||
{
|
||||
if (!(potentialHitTestTarget is Visual)) return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
|
||||
|
||||
if (!(potentialHitTestTarget is UIElement uIElement) || uIElement.IsVisible && uIElement.IsEnabled)
|
||||
return HitTestFilterBehavior.Continue;
|
||||
|
||||
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
|
||||
}
|
||||
|
||||
internal static bool ModifyStyle(IntPtr hWnd, int styleToRemove, int styleToAdd)
|
||||
{
|
||||
var windowLong = InteropMethods.GetWindowLong(hWnd, InteropValues.GWL.STYLE);
|
||||
var num = (windowLong & ~styleToRemove) | styleToAdd;
|
||||
if (num == windowLong) return false;
|
||||
InteropMethods.SetWindowLong(hWnd, InteropValues.GWL.STYLE, num);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal sealed class BitmapHandle : WpfSafeHandle
|
||||
{
|
||||
[SecurityCritical]
|
||||
private BitmapHandle() : this(true)
|
||||
{
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
private BitmapHandle(bool ownsHandle) : base(ownsHandle, CommonHandles.GDI)
|
||||
{
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return InteropMethods.DeleteObject(handle);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
internal HandleRef MakeHandleRef(object obj)
|
||||
{
|
||||
return new HandleRef(obj, handle);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
internal static BitmapHandle CreateFromHandle(IntPtr hbitmap, bool ownsHandle = true)
|
||||
{
|
||||
return new BitmapHandle(ownsHandle)
|
||||
{
|
||||
handle = hbitmap,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal static class CommonHandles
|
||||
{
|
||||
public static readonly int Icon = HandleCollector.RegisterType(nameof(Icon), 20, 500);
|
||||
|
||||
public static readonly int HDC = HandleCollector.RegisterType(nameof(HDC), 100, 2);
|
||||
|
||||
public static readonly int GDI = HandleCollector.RegisterType(nameof(GDI), 50, 500);
|
||||
|
||||
public static readonly int Kernel = HandleCollector.RegisterType(nameof(Kernel), 0, 1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// reference from https://referencesource.microsoft.com/#WindowsBase/Shared/MS/Win32/HandleCollector.cs,d0f99220d8e1b708
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal static class HandleCollector
|
||||
{
|
||||
private static HandleType[] HandleTypes;
|
||||
private static int HandleTypeCount;
|
||||
|
||||
private static readonly object HandleMutex = new object();
|
||||
|
||||
internal static IntPtr Add(IntPtr handle, int type)
|
||||
{
|
||||
HandleTypes[type - 1].Add();
|
||||
return handle;
|
||||
}
|
||||
|
||||
[System.Security.SecuritySafeCritical]
|
||||
internal static SafeHandle Add(SafeHandle handle, int type)
|
||||
{
|
||||
HandleTypes[type - 1].Add();
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal static void Add(int type)
|
||||
{
|
||||
HandleTypes[type - 1].Add();
|
||||
}
|
||||
|
||||
internal static int RegisterType(string typeName, int expense, int initialThreshold)
|
||||
{
|
||||
lock (HandleMutex)
|
||||
{
|
||||
if (HandleTypeCount == 0 || HandleTypeCount == HandleTypes.Length)
|
||||
{
|
||||
HandleType[] newTypes = new HandleType[HandleTypeCount + 10];
|
||||
if (HandleTypes != null)
|
||||
{
|
||||
Array.Copy(HandleTypes, 0, newTypes, 0, HandleTypeCount);
|
||||
}
|
||||
HandleTypes = newTypes;
|
||||
}
|
||||
|
||||
HandleTypes[HandleTypeCount++] = new HandleType(expense, initialThreshold);
|
||||
return HandleTypeCount;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IntPtr Remove(IntPtr handle, int type)
|
||||
{
|
||||
HandleTypes[type - 1].Remove();
|
||||
return handle;
|
||||
}
|
||||
|
||||
[System.Security.SecuritySafeCritical]
|
||||
internal static SafeHandle Remove(SafeHandle handle, int type)
|
||||
{
|
||||
HandleTypes[type - 1].Remove();
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal static void Remove(int type)
|
||||
{
|
||||
HandleTypes[type - 1].Remove();
|
||||
}
|
||||
|
||||
private class HandleType
|
||||
{
|
||||
private readonly int _initialThreshHold;
|
||||
private int _threshHold;
|
||||
private int _handleCount;
|
||||
private readonly int _deltaPercent;
|
||||
|
||||
internal HandleType(int expense, int initialThreshHold)
|
||||
{
|
||||
_initialThreshHold = initialThreshHold;
|
||||
_threshHold = initialThreshHold;
|
||||
_deltaPercent = 100 - expense;
|
||||
}
|
||||
|
||||
internal void Add()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
_handleCount++;
|
||||
var performCollect = NeedCollection();
|
||||
|
||||
if (!performCollect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GC.Collect();
|
||||
|
||||
var sleep = (100 - _deltaPercent) / 4;
|
||||
System.Threading.Thread.Sleep(sleep);
|
||||
}
|
||||
|
||||
private bool NeedCollection()
|
||||
{
|
||||
|
||||
if (_handleCount > _threshHold)
|
||||
{
|
||||
_threshHold = _handleCount + _handleCount * _deltaPercent / 100;
|
||||
return true;
|
||||
}
|
||||
|
||||
var oldThreshHold = 100 * _threshHold / (100 + _deltaPercent);
|
||||
if (oldThreshHold >= _initialThreshHold && _handleCount < (int) (oldThreshHold * .9F))
|
||||
{
|
||||
_threshHold = oldThreshHold;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void Remove()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
_handleCount--;
|
||||
|
||||
_handleCount = Math.Max(0, _handleCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Security;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal abstract class WpfSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private readonly int _collectorId;
|
||||
|
||||
[SecurityCritical]
|
||||
protected WpfSafeHandle(bool ownsHandle, int collectorId) : base(ownsHandle)
|
||||
{
|
||||
HandleCollector.Add(collectorId);
|
||||
_collectorId = collectorId;
|
||||
}
|
||||
|
||||
[SecurityCritical, SecuritySafeCritical]
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
HandleCollector.Remove(_collectorId);
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,683 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Security;
|
||||
using System.Security.Permissions;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal class InteropMethods
|
||||
{
|
||||
#region common
|
||||
|
||||
internal const int E_FAIL = unchecked((int) 0x80004005);
|
||||
|
||||
internal static readonly IntPtr HRGN_NONE = new IntPtr(-1);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, out InteropValues.TBBUTTON lpBuffer,
|
||||
int dwSize, out int lpNumberOfBytesRead);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true)]
|
||||
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, out InteropValues.RECT lpBuffer,
|
||||
int dwSize, out int lpNumberOfBytesRead);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true)]
|
||||
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, out InteropValues.TRAYDATA lpBuffer,
|
||||
int dwSize, out int lpNumberOfBytesRead);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto)]
|
||||
internal static extern uint SendMessage(IntPtr hWnd, uint Msg, uint wParam, IntPtr lParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern bool AttachThreadInput(in uint currentForegroundWindowThreadId,
|
||||
in uint thisWindowThreadId, bool isAttach);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr OpenProcess(InteropValues.ProcessAccess dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, int dwSize,
|
||||
InteropValues.AllocationType flAllocationType, InteropValues.MemoryProtection flProtect);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern int CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, InteropValues.FreeType dwFreeType);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
internal static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass,
|
||||
string lpszWindow);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern int GetWindowRect(IntPtr hwnd, out InteropValues.RECT lpRect);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto)]
|
||||
internal static extern bool GetCursorPos(out InteropValues.POINT pt);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr GetDesktopWindow();
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddClipboardFormatListener(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern bool EnableMenuItem(IntPtr hMenu, int UIDEnabledItem, int uEnable);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern bool InsertMenu(IntPtr hMenu, int wPosition, int wFlags, int wIDNewItem, string lpNewItem);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, ExactSpelling = true, EntryPoint = "DestroyMenu", CharSet = CharSet.Auto)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern bool IntDestroyMenu(HandleRef hMenu);
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true, ExactSpelling = true, EntryPoint = nameof(GetDC),
|
||||
CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr IntGetDC(HandleRef hWnd);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static IntPtr GetDC(HandleRef hWnd)
|
||||
{
|
||||
var hDc = IntGetDC(hWnd);
|
||||
if (hDc == IntPtr.Zero) throw new Win32Exception();
|
||||
|
||||
return HandleCollector.Add(hDc, CommonHandles.HDC);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.User32, ExactSpelling = true, EntryPoint = nameof(ReleaseDC), CharSet = CharSet.Auto)]
|
||||
internal static extern int IntReleaseDC(HandleRef hWnd, HandleRef hDC);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static int ReleaseDC(HandleRef hWnd, HandleRef hDC)
|
||||
{
|
||||
HandleCollector.Remove((IntPtr) hDC, CommonHandles.HDC);
|
||||
return IntReleaseDC(hWnd, hDC);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
|
||||
internal static extern int GetDeviceCaps(HandleRef hDC, int nIndex);
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern int GetSystemMetrics(InteropValues.SM nIndex);
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.User32, EntryPoint = nameof(DestroyIcon), CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern bool IntDestroyIcon(IntPtr hIcon);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static bool DestroyIcon(IntPtr hIcon)
|
||||
{
|
||||
var result = IntDestroyIcon(hIcon);
|
||||
return result;
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, EntryPoint = nameof(DeleteObject), CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern bool IntDeleteObject(IntPtr hObject);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static bool DeleteObject(IntPtr hObject)
|
||||
{
|
||||
var result = IntDeleteObject(hObject);
|
||||
return result;
|
||||
}
|
||||
|
||||
//[SecurityCritical]
|
||||
//internal static BitmapHandle CreateDIBSection(HandleRef hdc, ref InteropValues.BITMAPINFO bitmapInfo, int iUsage,
|
||||
// ref IntPtr ppvBits, SafeFileMappingHandle hSection, int dwOffset)
|
||||
//{
|
||||
// hSection ??= new SafeFileMappingHandle(IntPtr.Zero);
|
||||
|
||||
// var hBitmap = PrivateCreateDIBSection(hdc, ref bitmapInfo, iUsage, ref ppvBits, hSection, dwOffset);
|
||||
// return hBitmap;
|
||||
//}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, EntryPoint = "CloseHandle", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern bool IntCloseHandle(HandleRef handle);
|
||||
|
||||
//[SecurityCritical]
|
||||
//[SuppressUnmanagedCodeSecurity]
|
||||
//[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto,
|
||||
// EntryPoint = nameof(CreateDIBSection))]
|
||||
//private static extern BitmapHandle PrivateCreateDIBSection(HandleRef hdc, ref InteropValues.BITMAPINFO bitmapInfo, int iUsage,
|
||||
// ref IntPtr ppvBits, SafeFileMappingHandle hSection, int dwOffset);
|
||||
|
||||
//[SecurityCritical]
|
||||
//[SuppressUnmanagedCodeSecurity]
|
||||
//[DllImport(InteropValues.ExternDll.User32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto,
|
||||
// EntryPoint = nameof(CreateIconIndirect))]
|
||||
//private static extern IconHandle PrivateCreateIconIndirect([In] [MarshalAs(UnmanagedType.LPStruct)]
|
||||
// InteropValues.ICONINFO iconInfo);
|
||||
|
||||
//[SecurityCritical]
|
||||
//internal static IconHandle CreateIconIndirect([In] [MarshalAs(UnmanagedType.LPStruct)]
|
||||
// InteropValues.ICONINFO iconInfo)
|
||||
//{
|
||||
// var hIcon = PrivateCreateIconIndirect(iconInfo);
|
||||
// return hIcon;
|
||||
//}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto,
|
||||
EntryPoint = nameof(CreateBitmap))]
|
||||
private static extern BitmapHandle PrivateCreateBitmap(int width, int height, int planes, int bitsPerPixel,
|
||||
byte[] lpvBits);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static BitmapHandle CreateBitmap(int width, int height, int planes, int bitsPerPixel, byte[] lpvBits)
|
||||
{
|
||||
var hBitmap = PrivateCreateBitmap(width, height, planes, bitsPerPixel, lpvBits);
|
||||
return hBitmap;
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, EntryPoint = "GetModuleFileName", CharSet = CharSet.Unicode,
|
||||
SetLastError = true)]
|
||||
private static extern int IntGetModuleFileName(HandleRef hModule, StringBuilder buffer, int length);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static string GetModuleFileName(HandleRef hModule)
|
||||
{
|
||||
var buffer = new StringBuilder(InteropValues.Win32Constant.MAX_PATH);
|
||||
while (true)
|
||||
{
|
||||
var size = IntGetModuleFileName(hModule, buffer, buffer.Capacity);
|
||||
if (size == 0) throw new Win32Exception();
|
||||
|
||||
if (size == buffer.Capacity)
|
||||
{
|
||||
buffer.EnsureCapacity(buffer.Capacity * 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
//[SecurityCritical]
|
||||
//[SuppressUnmanagedCodeSecurity]
|
||||
//[DllImport(InteropValues.ExternDll.Shell32, CharSet = CharSet.Auto, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
//internal static extern int ExtractIconEx(string szExeFileName, int nIconIndex, out IconHandle phiconLarge,
|
||||
// out IconHandle phiconSmall, int nIcons);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Shell32, CharSet = CharSet.Auto)]
|
||||
internal static extern int Shell_NotifyIcon(int message, InteropValues.NOTIFYICONDATA pnid);
|
||||
|
||||
[SecurityCritical]
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateWindowExW")]
|
||||
internal static extern IntPtr CreateWindowEx(
|
||||
int dwExStyle,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpClassName,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpWindowName,
|
||||
int dwStyle,
|
||||
int x,
|
||||
int y,
|
||||
int nWidth,
|
||||
int nHeight,
|
||||
IntPtr hWndParent,
|
||||
IntPtr hMenu,
|
||||
IntPtr hInstance,
|
||||
IntPtr lpParam);
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)]
|
||||
internal static extern short RegisterClass(InteropValues.WNDCLASS4ICON wc);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr DefWindowProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)]
|
||||
internal static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern IntPtr SetWindowsHookEx(int idHook, InteropValues.HookProc lpfn, IntPtr hMod, uint dwThreadId);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
internal static extern IntPtr GetWindowDC(IntPtr window);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true)]
|
||||
internal static extern uint GetPixel(IntPtr dc, int x, int y);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
internal static extern int ReleaseDC(IntPtr window, IntPtr dc);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
|
||||
internal static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr GetDC(IntPtr ptr);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetWindowPlacement(IntPtr hwnd, InteropValues.WINDOWPLACEMENT lpwndpl);
|
||||
|
||||
internal static InteropValues.WINDOWPLACEMENT GetWindowPlacement(IntPtr hwnd)
|
||||
{
|
||||
InteropValues.WINDOWPLACEMENT wINDOWPLACEMENT = new InteropValues.WINDOWPLACEMENT();
|
||||
if (GetWindowPlacement(hwnd, wINDOWPLACEMENT))
|
||||
{
|
||||
return wINDOWPLACEMENT;
|
||||
}
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
internal static int GetXLParam(int lParam) => LoWord(lParam);
|
||||
|
||||
internal static int GetYLParam(int lParam) => HiWord(lParam);
|
||||
|
||||
internal static int HiWord(int value) => (short) (value >> 16);
|
||||
|
||||
internal static int LoWord(int value) => (short) (value & 65535);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EnumThreadWindows(uint dwThreadId, InteropValues.EnumWindowsProc lpfn, IntPtr lParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteDC(IntPtr hdc);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true)]
|
||||
internal static extern IntPtr CreateCompatibleDC(IntPtr hdc);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, ExactSpelling = true, SetLastError = true)]
|
||||
internal static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr SendMessage(IntPtr hWnd, int nMsg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr MonitorFromPoint(InteropValues.POINT pt, int flags);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr GetWindow(IntPtr hwnd, int nCmd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool IsWindowVisible(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool IsIconic(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool IsZoomed(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, ExactSpelling = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int flags);
|
||||
|
||||
internal static System.Windows.Point GetCursorPos()
|
||||
{
|
||||
var result = default(System.Windows.Point);
|
||||
if (GetCursorPos(out var point))
|
||||
{
|
||||
result.X = point.X;
|
||||
result.Y = point.Y;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
internal static int GetWindowLong(IntPtr hWnd, InteropValues.GWL nIndex) => GetWindowLong(hWnd, (int) nIndex);
|
||||
|
||||
internal static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
return SetWindowLongPtr32(hWnd, nIndex, dwNewLong);
|
||||
}
|
||||
return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
|
||||
}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, EntryPoint = "SetWindowLong")]
|
||||
internal static extern IntPtr SetWindowLongPtr32(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, EntryPoint = "SetWindowLongPtr")]
|
||||
internal static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode)]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
|
||||
internal static IntPtr SetWindowLongPtr(IntPtr hWnd, InteropValues.GWLP nIndex, IntPtr dwNewLong)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
{
|
||||
return SetWindowLongPtr(hWnd, (int) nIndex, dwNewLong);
|
||||
}
|
||||
return new IntPtr(SetWindowLong(hWnd, (int) nIndex, dwNewLong.ToInt32()));
|
||||
}
|
||||
|
||||
internal static int SetWindowLong(IntPtr hWnd, InteropValues.GWL nIndex, int dwNewLong) => SetWindowLong(hWnd, (int) nIndex, dwNewLong);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode)]
|
||||
internal static extern ushort RegisterClass(ref InteropValues.WNDCLASS lpWndClass);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32)]
|
||||
internal static extern uint GetCurrentThreadId();
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr CreateWindowEx(int dwExStyle, IntPtr classAtom, string lpWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DestroyWindow(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool UnregisterClass(IntPtr classAtom, IntPtr hInstance);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, InteropValues.RedrawWindowFlags flags);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, InteropValues.EnumMonitorsDelegate lpfnEnum, IntPtr dwData);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool IntersectRect(out InteropValues.RECT lprcDst, [In] ref InteropValues.RECT lprcSrc1, [In] ref InteropValues.RECT lprcSrc2);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetMonitorInfo(IntPtr hMonitor, ref InteropValues.MONITORINFO monitorInfo);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true)]
|
||||
internal static extern IntPtr CreateDIBSection(IntPtr hdc, ref InteropValues.BITMAPINFO pbmi, uint iUsage, out IntPtr ppvBits, IntPtr hSection, uint dwOffset);
|
||||
|
||||
internal static int GET_SC_WPARAM(IntPtr wParam) => (int) wParam & 65520;
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr ChildWindowFromPointEx(IntPtr hwndParent, InteropValues.POINT pt, int uFlags);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32)]
|
||||
internal static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int width, int height);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32)]
|
||||
internal static extern bool BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern bool EnableWindow(IntPtr hWnd, bool enable);
|
||||
|
||||
internal static object? PtrToStructure(IntPtr lparam, Type cls) => Marshal.PtrToStructure(lparam, cls);
|
||||
|
||||
internal static void PtrToStructure(IntPtr lparam, object data) => Marshal.PtrToStructure(lparam, data);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Shell32, CallingConvention = CallingConvention.StdCall)]
|
||||
internal static extern uint SHAppBarMessage(int dwMessage, ref InteropValues.APPBARDATA pData);
|
||||
|
||||
[SecurityCritical]
|
||||
[DllImport(InteropValues.ExternDll.DwmApi, EntryPoint = "DwmGetColorizationColor", PreserveSig = true)]
|
||||
internal static extern int DwmGetColorizationColor(out uint pcrColorization, out bool pfOpaqueBlend);
|
||||
|
||||
#endregion
|
||||
|
||||
internal class Gdip
|
||||
{
|
||||
private const string ThreadDataSlotName = "system.drawing.threaddata";
|
||||
|
||||
private static IntPtr InitToken;
|
||||
|
||||
private static bool Initialized => InitToken != IntPtr.Zero;
|
||||
|
||||
internal const int
|
||||
Ok = 0,
|
||||
GenericError = 1,
|
||||
InvalidParameter = 2,
|
||||
OutOfMemory = 3,
|
||||
ObjectBusy = 4,
|
||||
InsufficientBuffer = 5,
|
||||
NotImplemented = 6,
|
||||
Win32Error = 7,
|
||||
WrongState = 8,
|
||||
Aborted = 9,
|
||||
FileNotFound = 10,
|
||||
ValueOverflow = 11,
|
||||
AccessDenied = 12,
|
||||
UnknownImageFormat = 13,
|
||||
FontFamilyNotFound = 14,
|
||||
FontStyleNotFound = 15,
|
||||
NotTrueTypeFont = 16,
|
||||
UnsupportedGdiplusVersion = 17,
|
||||
GdiplusNotInitialized = 18,
|
||||
PropertyNotFound = 19,
|
||||
PropertyNotSupported = 20,
|
||||
E_UNEXPECTED = unchecked((int) 0x8000FFFF);
|
||||
|
||||
static Gdip()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct StartupInput
|
||||
{
|
||||
private int GdiplusVersion;
|
||||
|
||||
private readonly IntPtr DebugEventCallback;
|
||||
|
||||
private bool SuppressBackgroundThread;
|
||||
|
||||
private bool SuppressExternalCodecs;
|
||||
|
||||
public static StartupInput GetDefault()
|
||||
{
|
||||
var result = new StartupInput
|
||||
{
|
||||
GdiplusVersion = 1,
|
||||
SuppressBackgroundThread = false,
|
||||
SuppressExternalCodecs = false
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private readonly struct StartupOutput
|
||||
{
|
||||
private readonly IntPtr hook;
|
||||
|
||||
private readonly IntPtr unhook;
|
||||
}
|
||||
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
[ResourceConsumption(ResourceScope.AppDomain, ResourceScope.AppDomain)]
|
||||
private static void Initialize()
|
||||
{
|
||||
var input = StartupInput.GetDefault();
|
||||
|
||||
var status = GdiplusStartup(out InitToken, ref input, out StartupOutput output);
|
||||
|
||||
if (status != Ok)
|
||||
{
|
||||
throw StatusException(status);
|
||||
}
|
||||
|
||||
var currentDomain = AppDomain.CurrentDomain;
|
||||
currentDomain.ProcessExit += OnProcessExit!;
|
||||
|
||||
if (!currentDomain.IsDefaultAppDomain())
|
||||
{
|
||||
currentDomain.DomainUnload += OnProcessExit!;
|
||||
}
|
||||
}
|
||||
|
||||
[ResourceExposure(ResourceScope.AppDomain)]
|
||||
[ResourceConsumption(ResourceScope.AppDomain)]
|
||||
private static void OnProcessExit(object sender, EventArgs e) => Shutdown();
|
||||
|
||||
[ResourceExposure(ResourceScope.AppDomain)]
|
||||
[ResourceConsumption(ResourceScope.AppDomain)]
|
||||
private static void Shutdown()
|
||||
{
|
||||
if (Initialized)
|
||||
{
|
||||
ClearThreadData();
|
||||
// unhook our shutdown handlers as we do not need to shut down more than once
|
||||
var currentDomain = AppDomain.CurrentDomain;
|
||||
currentDomain.ProcessExit -= OnProcessExit!;
|
||||
if (!currentDomain.IsDefaultAppDomain())
|
||||
{
|
||||
currentDomain.DomainUnload -= OnProcessExit!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ClearThreadData()
|
||||
{
|
||||
var slot = Thread.GetNamedDataSlot(ThreadDataSlotName);
|
||||
Thread.SetData(slot, null);
|
||||
}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipImageGetFrameDimensionsCount(HandleRef image, out int count);
|
||||
|
||||
internal static Exception StatusException(int status)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipImageGetFrameDimensionsList(HandleRef image, IntPtr buffer, int count);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipImageGetFrameCount(HandleRef image, ref Guid dimensionId, int[] count);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetPropertyItemSize(HandleRef image, int propid, out int size);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetPropertyItem(HandleRef image, int propid, int size, IntPtr buffer);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.Machine)]
|
||||
internal static extern int GdipCreateHBITMAPFromBitmap(HandleRef nativeBitmap, out IntPtr hbitmap, int argbBackground);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipImageSelectActiveFrame(HandleRef image, ref Guid dimensionId, int frameIndex);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.Machine)]
|
||||
internal static extern int GdipCreateBitmapFromFile(string filename, out IntPtr bitmap);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipImageForceValidation(HandleRef image);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, EntryPoint = "GdipDisposeImage", CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
private static extern int IntGdipDisposeImage(HandleRef image);
|
||||
|
||||
internal static int GdipDisposeImage(HandleRef image)
|
||||
{
|
||||
if (!Initialized) return Ok;
|
||||
var result = IntGdipDisposeImage(image);
|
||||
return result;
|
||||
}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.Process)]
|
||||
private static extern int GdiplusStartup(out IntPtr token, ref StartupInput input, out StartupOutput output);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetImageRawFormat(HandleRef image, ref Guid format);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref InteropValues.WINCOMPATTRDATA data);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.Machine)]
|
||||
internal static extern int GdipCreateBitmapFromStream(InteropValues.IStream stream, out IntPtr bitmap);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.Machine)]
|
||||
internal static extern int GdipCreateBitmapFromHBITMAP(HandleRef hbitmap, HandleRef hpalette, out IntPtr bitmap);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetImageEncodersSize(out int numEncoders, out int size);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetImageDecodersSize(out int numDecoders, out int size);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetImageDecoders(int numDecoders, int size, IntPtr decoders);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetImageEncoders(int numEncoders, int size, IntPtr encoders);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipSaveImageToStream(HandleRef image, InteropValues.IStream stream, ref Guid classId, HandleRef encoderParams);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.NTdll)]
|
||||
internal static extern int RtlGetVersion(out InteropValues.RTL_OSVERSIONINFOEX lpVersionInformation);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,975 @@
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal class InteropValues
|
||||
{
|
||||
internal static class ExternDll
|
||||
{
|
||||
public const string
|
||||
User32 = "user32.dll",
|
||||
Gdi32 = "gdi32.dll",
|
||||
GdiPlus = "gdiplus.dll",
|
||||
Kernel32 = "kernel32.dll",
|
||||
Shell32 = "shell32.dll",
|
||||
MsImg = "msimg32.dll",
|
||||
NTdll = "ntdll.dll",
|
||||
DwmApi = "dwmapi.dll";
|
||||
}
|
||||
|
||||
internal delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
internal delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);
|
||||
|
||||
internal delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
||||
|
||||
internal const int
|
||||
BITSPIXEL = 12,
|
||||
PLANES = 14,
|
||||
BI_RGB = 0,
|
||||
DIB_RGB_COLORS = 0,
|
||||
E_FAIL = unchecked((int) 0x80004005),
|
||||
NIF_MESSAGE = 0x00000001,
|
||||
NIF_ICON = 0x00000002,
|
||||
NIF_TIP = 0x00000004,
|
||||
NIF_INFO = 0x00000010,
|
||||
NIM_ADD = 0x00000000,
|
||||
NIM_MODIFY = 0x00000001,
|
||||
NIM_DELETE = 0x00000002,
|
||||
NIIF_NONE = 0x00000000,
|
||||
NIIF_INFO = 0x00000001,
|
||||
NIIF_WARNING = 0x00000002,
|
||||
NIIF_ERROR = 0x00000003,
|
||||
WM_ACTIVATE = 0x0006,
|
||||
WM_QUIT = 0x0012,
|
||||
WM_GETMINMAXINFO = 0x0024,
|
||||
WM_WINDOWPOSCHANGING = 0x0046,
|
||||
WM_WINDOWPOSCHANGED = 0x0047,
|
||||
WM_SETICON = 0x0080,
|
||||
WM_NCCREATE = 0x0081,
|
||||
WM_NCDESTROY = 0x0082,
|
||||
WM_NCHITTEST = 0x0084,
|
||||
WM_NCACTIVATE = 0x0086,
|
||||
WM_NCRBUTTONDOWN = 0x00A4,
|
||||
WM_NCRBUTTONUP = 0x00A5,
|
||||
WM_NCRBUTTONDBLCLK = 0x00A6,
|
||||
WM_NCUAHDRAWCAPTION = 0x00AE,
|
||||
WM_NCUAHDRAWFRAME = 0x00AF,
|
||||
WM_KEYDOWN = 0x0100,
|
||||
WM_KEYUP = 0x0101,
|
||||
WM_SYSKEYDOWN = 0x0104,
|
||||
WM_SYSKEYUP = 0x0105,
|
||||
WM_SYSCOMMAND = 0x112,
|
||||
WM_MOUSEMOVE = 0x0200,
|
||||
WM_LBUTTONUP = 0x0202,
|
||||
WM_LBUTTONDBLCLK = 0x0203,
|
||||
WM_RBUTTONUP = 0x0205,
|
||||
WM_ENTERSIZEMOVE = 0x0231,
|
||||
WM_EXITSIZEMOVE = 0x0232,
|
||||
WM_CLIPBOARDUPDATE = 0x031D,
|
||||
WM_USER = 0x0400,
|
||||
WS_VISIBLE = 0x10000000,
|
||||
MF_BYCOMMAND = 0x00000000,
|
||||
MF_BYPOSITION = 0x400,
|
||||
MF_GRAYED = 0x00000001,
|
||||
MF_SEPARATOR = 0x800,
|
||||
TB_GETBUTTON = WM_USER + 23,
|
||||
TB_BUTTONCOUNT = WM_USER + 24,
|
||||
TB_GETITEMRECT = WM_USER + 29,
|
||||
VERTRES = 10,
|
||||
DESKTOPVERTRES = 117,
|
||||
LOGPIXELSX = 88,
|
||||
LOGPIXELSY = 90,
|
||||
SC_CLOSE = 0xF060,
|
||||
SC_SIZE = 0xF000,
|
||||
SC_MOVE = 0xF010,
|
||||
SC_MINIMIZE = 0xF020,
|
||||
SC_MAXIMIZE = 0xF030,
|
||||
SC_RESTORE = 0xF120,
|
||||
SRCCOPY = 0x00CC0020,
|
||||
MONITOR_DEFAULTTONEAREST = 0x00000002;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
internal class NOTIFYICONDATA
|
||||
{
|
||||
public int cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA));
|
||||
public IntPtr hWnd;
|
||||
public int uID;
|
||||
public int uFlags;
|
||||
public int uCallbackMessage;
|
||||
public IntPtr hIcon;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string szTip = string.Empty;
|
||||
public int dwState = 0x01;
|
||||
public int dwStateMask = 0x01;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
||||
public string szInfo = string.Empty;
|
||||
public int uTimeoutOrVersion;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
|
||||
public string szInfoTitle = string.Empty;
|
||||
public int dwInfoFlags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct TBBUTTON
|
||||
{
|
||||
public int iBitmap;
|
||||
public int idCommand;
|
||||
public IntPtr fsStateStylePadding;
|
||||
public IntPtr dwData;
|
||||
public IntPtr iString;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AllocationType
|
||||
{
|
||||
Commit = 0x1000,
|
||||
Reserve = 0x2000,
|
||||
Decommit = 0x4000,
|
||||
Release = 0x8000,
|
||||
Reset = 0x80000,
|
||||
Physical = 0x400000,
|
||||
TopDown = 0x100000,
|
||||
WriteWatch = 0x200000,
|
||||
LargePages = 0x20000000
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum MemoryProtection
|
||||
{
|
||||
Execute = 0x10,
|
||||
ExecuteRead = 0x20,
|
||||
ExecuteReadWrite = 0x40,
|
||||
ExecuteWriteCopy = 0x80,
|
||||
NoAccess = 0x01,
|
||||
ReadOnly = 0x02,
|
||||
ReadWrite = 0x04,
|
||||
WriteCopy = 0x08,
|
||||
GuardModifierflag = 0x100,
|
||||
NoCacheModifierflag = 0x200,
|
||||
WriteCombineModifierflag = 0x400
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct TRAYDATA
|
||||
{
|
||||
public IntPtr hwnd;
|
||||
public uint uID;
|
||||
public uint uCallbackMessage;
|
||||
public uint bReserved0;
|
||||
public uint bReserved1;
|
||||
public IntPtr hIcon;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum FreeType
|
||||
{
|
||||
Decommit = 0x4000,
|
||||
Release = 0x8000,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct POINT
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
|
||||
public POINT(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum HookType
|
||||
{
|
||||
WH_KEYBOARD_LL = 13,
|
||||
WH_MOUSE_LL = 14
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct MOUSEHOOKSTRUCT
|
||||
{
|
||||
public POINT pt;
|
||||
public IntPtr hwnd;
|
||||
public uint wHitTestCode;
|
||||
public IntPtr dwExtraInfo;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum ProcessAccess
|
||||
{
|
||||
AllAccess = CreateThread | DuplicateHandle | QueryInformation | SetInformation | Terminate | VMOperation | VMRead | VMWrite | Synchronize,
|
||||
CreateThread = 0x2,
|
||||
DuplicateHandle = 0x40,
|
||||
QueryInformation = 0x400,
|
||||
SetInformation = 0x200,
|
||||
Terminate = 0x1,
|
||||
VMOperation = 0x8,
|
||||
VMRead = 0x10,
|
||||
VMWrite = 0x20,
|
||||
Synchronize = 0x100000
|
||||
}
|
||||
|
||||
[Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
internal struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
|
||||
public RECT(int left, int top, int right, int bottom)
|
||||
{
|
||||
Left = left;
|
||||
Top = top;
|
||||
Right = right;
|
||||
Bottom = bottom;
|
||||
}
|
||||
|
||||
public RECT(Rect rect)
|
||||
{
|
||||
Left = (int) rect.Left;
|
||||
Top = (int) rect.Top;
|
||||
Right = (int) rect.Right;
|
||||
Bottom = (int) rect.Bottom;
|
||||
}
|
||||
|
||||
public readonly Point Position => new Point(Left, Top);
|
||||
public readonly Size Size => new Size(Width, Height);
|
||||
|
||||
public int Height
|
||||
{
|
||||
readonly get => Bottom - Top;
|
||||
set => Bottom = Top + value;
|
||||
}
|
||||
|
||||
public int Width
|
||||
{
|
||||
readonly get => Right - Left;
|
||||
set => Right = Left + value;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum GWL
|
||||
{
|
||||
STYLE = -16,
|
||||
EXSTYLE = -20
|
||||
}
|
||||
|
||||
internal enum GWLP
|
||||
{
|
||||
WNDPROC = -4,
|
||||
HINSTANCE = -6,
|
||||
HWNDPARENT = -8,
|
||||
USERDATA = -21,
|
||||
ID = -12
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum RedrawWindowFlags : uint
|
||||
{
|
||||
Invalidate = 1u,
|
||||
InternalPaint = 2u,
|
||||
Erase = 4u,
|
||||
Validate = 8u,
|
||||
NoInternalPaint = 16u,
|
||||
NoErase = 32u,
|
||||
NoChildren = 64u,
|
||||
AllChildren = 128u,
|
||||
UpdateNow = 256u,
|
||||
EraseNow = 512u,
|
||||
Frame = 1024u,
|
||||
NoFrame = 2048u
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class WINDOWPOS
|
||||
{
|
||||
public IntPtr hwnd;
|
||||
public IntPtr hwndInsertAfter;
|
||||
public int x;
|
||||
public int y;
|
||||
public int cx;
|
||||
public int cy;
|
||||
public uint flags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class WINDOWPLACEMENT
|
||||
{
|
||||
public int length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
|
||||
public int flags;
|
||||
public int showCmd;
|
||||
public POINT ptMinPosition;
|
||||
public POINT ptMaxPosition;
|
||||
public RECT rcNormalPosition;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
internal struct SIZE
|
||||
{
|
||||
[ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")]
|
||||
public int cx;
|
||||
[ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")]
|
||||
public int cy;
|
||||
}
|
||||
|
||||
internal struct MONITORINFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public RECT rcMonitor;
|
||||
public RECT rcWork;
|
||||
public uint dwFlags;
|
||||
}
|
||||
|
||||
internal enum SM
|
||||
{
|
||||
CXSCREEN = 0,
|
||||
CYSCREEN = 1,
|
||||
CXVSCROLL = 2,
|
||||
CYHSCROLL = 3,
|
||||
CYCAPTION = 4,
|
||||
CXBORDER = 5,
|
||||
CYBORDER = 6,
|
||||
CXFIXEDFRAME = 7,
|
||||
CYFIXEDFRAME = 8,
|
||||
CYVTHUMB = 9,
|
||||
CXHTHUMB = 10,
|
||||
CXICON = 11,
|
||||
CYICON = 12,
|
||||
CXCURSOR = 13,
|
||||
CYCURSOR = 14,
|
||||
CYMENU = 15,
|
||||
CXFULLSCREEN = 16,
|
||||
CYFULLSCREEN = 17,
|
||||
CYKANJIWINDOW = 18,
|
||||
MOUSEPRESENT = 19,
|
||||
CYVSCROLL = 20,
|
||||
CXHSCROLL = 21,
|
||||
DEBUG = 22,
|
||||
SWAPBUTTON = 23,
|
||||
CXMIN = 28,
|
||||
CYMIN = 29,
|
||||
CXSIZE = 30,
|
||||
CYSIZE = 31,
|
||||
CXFRAME = 32,
|
||||
CXSIZEFRAME = CXFRAME,
|
||||
CYFRAME = 33,
|
||||
CYSIZEFRAME = CYFRAME,
|
||||
CXMINTRACK = 34,
|
||||
CYMINTRACK = 35,
|
||||
CXDOUBLECLK = 36,
|
||||
CYDOUBLECLK = 37,
|
||||
CXICONSPACING = 38,
|
||||
CYICONSPACING = 39,
|
||||
MENUDROPALIGNMENT = 40,
|
||||
PENWINDOWS = 41,
|
||||
DBCSENABLED = 42,
|
||||
CMOUSEBUTTONS = 43,
|
||||
SECURE = 44,
|
||||
CXEDGE = 45,
|
||||
CYEDGE = 46,
|
||||
CXMINSPACING = 47,
|
||||
CYMINSPACING = 48,
|
||||
CXSMICON = 49,
|
||||
CYSMICON = 50,
|
||||
CYSMCAPTION = 51,
|
||||
CXSMSIZE = 52,
|
||||
CYSMSIZE = 53,
|
||||
CXMENUSIZE = 54,
|
||||
CYMENUSIZE = 55,
|
||||
ARRANGE = 56,
|
||||
CXMINIMIZED = 57,
|
||||
CYMINIMIZED = 58,
|
||||
CXMAXTRACK = 59,
|
||||
CYMAXTRACK = 60,
|
||||
CXMAXIMIZED = 61,
|
||||
CYMAXIMIZED = 62,
|
||||
NETWORK = 63,
|
||||
CLEANBOOT = 67,
|
||||
CXDRAG = 68,
|
||||
CYDRAG = 69,
|
||||
SHOWSOUNDS = 70,
|
||||
CXMENUCHECK = 71,
|
||||
CYMENUCHECK = 72,
|
||||
SLOWMACHINE = 73,
|
||||
MIDEASTENABLED = 74,
|
||||
MOUSEWHEELPRESENT = 75,
|
||||
XVIRTUALSCREEN = 76,
|
||||
YVIRTUALSCREEN = 77,
|
||||
CXVIRTUALSCREEN = 78,
|
||||
CYVIRTUALSCREEN = 79,
|
||||
CMONITORS = 80,
|
||||
SAMEDISPLAYFORMAT = 81,
|
||||
IMMENABLED = 82,
|
||||
CXFOCUSBORDER = 83,
|
||||
CYFOCUSBORDER = 84,
|
||||
TABLETPC = 86,
|
||||
MEDIACENTER = 87,
|
||||
REMOTESESSION = 0x1000,
|
||||
REMOTECONTROL = 0x2001
|
||||
}
|
||||
|
||||
internal enum CacheSlot
|
||||
{
|
||||
DpiX,
|
||||
|
||||
FocusBorderWidth,
|
||||
FocusBorderHeight,
|
||||
HighContrast,
|
||||
MouseVanish,
|
||||
|
||||
DropShadow,
|
||||
FlatMenu,
|
||||
WorkAreaInternal,
|
||||
WorkArea,
|
||||
|
||||
IconMetrics,
|
||||
|
||||
KeyboardCues,
|
||||
KeyboardDelay,
|
||||
KeyboardPreference,
|
||||
KeyboardSpeed,
|
||||
SnapToDefaultButton,
|
||||
WheelScrollLines,
|
||||
MouseHoverTime,
|
||||
MouseHoverHeight,
|
||||
MouseHoverWidth,
|
||||
|
||||
MenuDropAlignment,
|
||||
MenuFade,
|
||||
MenuShowDelay,
|
||||
|
||||
ComboBoxAnimation,
|
||||
ClientAreaAnimation,
|
||||
CursorShadow,
|
||||
GradientCaptions,
|
||||
HotTracking,
|
||||
ListBoxSmoothScrolling,
|
||||
MenuAnimation,
|
||||
SelectionFade,
|
||||
StylusHotTracking,
|
||||
ToolTipAnimation,
|
||||
ToolTipFade,
|
||||
UIEffects,
|
||||
|
||||
MinimizeAnimation,
|
||||
Border,
|
||||
CaretWidth,
|
||||
ForegroundFlashCount,
|
||||
DragFullWindows,
|
||||
NonClientMetrics,
|
||||
|
||||
ThinHorizontalBorderHeight,
|
||||
ThinVerticalBorderWidth,
|
||||
CursorWidth,
|
||||
CursorHeight,
|
||||
ThickHorizontalBorderHeight,
|
||||
ThickVerticalBorderWidth,
|
||||
MinimumHorizontalDragDistance,
|
||||
MinimumVerticalDragDistance,
|
||||
FixedFrameHorizontalBorderHeight,
|
||||
FixedFrameVerticalBorderWidth,
|
||||
FocusHorizontalBorderHeight,
|
||||
FocusVerticalBorderWidth,
|
||||
FullPrimaryScreenWidth,
|
||||
FullPrimaryScreenHeight,
|
||||
HorizontalScrollBarButtonWidth,
|
||||
HorizontalScrollBarHeight,
|
||||
HorizontalScrollBarThumbWidth,
|
||||
IconWidth,
|
||||
IconHeight,
|
||||
IconGridWidth,
|
||||
IconGridHeight,
|
||||
MaximizedPrimaryScreenWidth,
|
||||
MaximizedPrimaryScreenHeight,
|
||||
MaximumWindowTrackWidth,
|
||||
MaximumWindowTrackHeight,
|
||||
MenuCheckmarkWidth,
|
||||
MenuCheckmarkHeight,
|
||||
MenuButtonWidth,
|
||||
MenuButtonHeight,
|
||||
MinimumWindowWidth,
|
||||
MinimumWindowHeight,
|
||||
MinimizedWindowWidth,
|
||||
MinimizedWindowHeight,
|
||||
MinimizedGridWidth,
|
||||
MinimizedGridHeight,
|
||||
MinimumWindowTrackWidth,
|
||||
MinimumWindowTrackHeight,
|
||||
PrimaryScreenWidth,
|
||||
PrimaryScreenHeight,
|
||||
WindowCaptionButtonWidth,
|
||||
WindowCaptionButtonHeight,
|
||||
ResizeFrameHorizontalBorderHeight,
|
||||
ResizeFrameVerticalBorderWidth,
|
||||
SmallIconWidth,
|
||||
SmallIconHeight,
|
||||
SmallWindowCaptionButtonWidth,
|
||||
SmallWindowCaptionButtonHeight,
|
||||
VirtualScreenWidth,
|
||||
VirtualScreenHeight,
|
||||
VerticalScrollBarWidth,
|
||||
VerticalScrollBarButtonHeight,
|
||||
WindowCaptionHeight,
|
||||
KanjiWindowHeight,
|
||||
MenuBarHeight,
|
||||
VerticalScrollBarThumbHeight,
|
||||
IsImmEnabled,
|
||||
IsMediaCenter,
|
||||
IsMenuDropRightAligned,
|
||||
IsMiddleEastEnabled,
|
||||
IsMousePresent,
|
||||
IsMouseWheelPresent,
|
||||
IsPenWindows,
|
||||
IsRemotelyControlled,
|
||||
IsRemoteSession,
|
||||
ShowSounds,
|
||||
IsSlowMachine,
|
||||
SwapButtons,
|
||||
IsTabletPC,
|
||||
VirtualScreenLeft,
|
||||
VirtualScreenTop,
|
||||
|
||||
PowerLineStatus,
|
||||
|
||||
IsGlassEnabled,
|
||||
UxThemeName,
|
||||
UxThemeColor,
|
||||
WindowCornerRadius,
|
||||
WindowGlassColor,
|
||||
WindowGlassBrush,
|
||||
WindowNonClientFrameThickness,
|
||||
WindowResizeBorderThickness,
|
||||
|
||||
NumSlots
|
||||
}
|
||||
|
||||
internal static class Win32Constant
|
||||
{
|
||||
internal const int MAX_PATH = 260;
|
||||
internal const int INFOTIPSIZE = 1024;
|
||||
internal const int TRUE = 1;
|
||||
internal const int FALSE = 0;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct WNDCLASS
|
||||
{
|
||||
public uint style;
|
||||
public Delegate lpfnWndProc;
|
||||
public int cbClsExtra;
|
||||
public int cbWndExtra;
|
||||
public IntPtr hInstance;
|
||||
public IntPtr hIcon;
|
||||
public IntPtr hCursor;
|
||||
public IntPtr hbrBackground;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string lpszMenuName;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string lpszClassName;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
internal class WNDCLASS4ICON
|
||||
{
|
||||
public int style;
|
||||
public WndProc? lpfnWndProc;
|
||||
public int cbClsExtra;
|
||||
public int cbWndExtra;
|
||||
public IntPtr hInstance;
|
||||
public IntPtr hIcon;
|
||||
public IntPtr hCursor;
|
||||
public IntPtr hbrBackground;
|
||||
public string? lpszMenuName;
|
||||
public string? lpszClassName;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 2)]
|
||||
internal struct BITMAPINFO
|
||||
{
|
||||
public int biSize;
|
||||
|
||||
public int biWidth;
|
||||
|
||||
public int biHeight;
|
||||
|
||||
public short biPlanes;
|
||||
|
||||
public short biBitCount;
|
||||
|
||||
public int biCompression;
|
||||
|
||||
public int biSizeImage;
|
||||
|
||||
public int biXPelsPerMeter;
|
||||
|
||||
public int biYPelsPerMeter;
|
||||
|
||||
public int biClrUsed;
|
||||
|
||||
public int biClrImportant;
|
||||
|
||||
public BITMAPINFO(int width, int height, short bpp)
|
||||
{
|
||||
biSize = SizeOf();
|
||||
biWidth = width;
|
||||
biHeight = height;
|
||||
biPlanes = 1;
|
||||
biBitCount = bpp;
|
||||
biCompression = 0;
|
||||
biSizeImage = 0;
|
||||
biXPelsPerMeter = 0;
|
||||
biYPelsPerMeter = 0;
|
||||
biClrUsed = 0;
|
||||
biClrImportant = 0;
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
private static int SizeOf()
|
||||
{
|
||||
return Marshal.SizeOf(typeof(BITMAPINFO));
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class ICONINFO
|
||||
{
|
||||
public bool fIcon = false;
|
||||
public int xHotspot = 0;
|
||||
public int yHotspot = 0;
|
||||
public BitmapHandle? hbmMask = null;
|
||||
public BitmapHandle? hbmColor = null;
|
||||
}
|
||||
|
||||
internal enum WINDOWCOMPOSITIONATTRIB
|
||||
{
|
||||
WCA_ACCENT_POLICY = 19
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct WINCOMPATTRDATA
|
||||
{
|
||||
public WINDOWCOMPOSITIONATTRIB Attribute;
|
||||
public IntPtr Data;
|
||||
public int DataSize;
|
||||
}
|
||||
|
||||
internal enum ACCENTSTATE
|
||||
{
|
||||
ACCENT_DISABLED = 0,
|
||||
ACCENT_ENABLE_GRADIENT = 1,
|
||||
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
|
||||
ACCENT_ENABLE_BLURBEHIND = 3,
|
||||
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4,
|
||||
ACCENT_INVALID_STATE = 5
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ACCENTPOLICY
|
||||
{
|
||||
public ACCENTSTATE AccentState;
|
||||
public int AccentFlags;
|
||||
public uint GradientColor;
|
||||
public int AnimationId;
|
||||
}
|
||||
|
||||
[ComImport, Guid("0000000C-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface IStream
|
||||
{
|
||||
int Read([In] IntPtr buf, [In] int len);
|
||||
|
||||
int Write([In] IntPtr buf, [In] int len);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.I8)]
|
||||
long Seek([In, MarshalAs(UnmanagedType.I8)] long dlibMove, [In] int dwOrigin);
|
||||
|
||||
void SetSize([In, MarshalAs(UnmanagedType.I8)] long libNewSize);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.I8)]
|
||||
long CopyTo([In, MarshalAs(UnmanagedType.Interface)] IStream pstm, [In, MarshalAs(UnmanagedType.I8)] long cb, [Out, MarshalAs(UnmanagedType.LPArray)] long[] pcbRead);
|
||||
|
||||
void Commit([In] int grfCommitFlags);
|
||||
|
||||
void Revert();
|
||||
|
||||
void LockRegion([In, MarshalAs(UnmanagedType.I8)] long libOffset, [In, MarshalAs(UnmanagedType.I8)] long cb, [In] int dwLockType);
|
||||
|
||||
void UnlockRegion([In, MarshalAs(UnmanagedType.I8)] long libOffset, [In, MarshalAs(UnmanagedType.I8)] long cb, [In] int dwLockType);
|
||||
|
||||
void Stat([In] IntPtr pStatstg, [In] int grfStatFlag);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
IStream Clone();
|
||||
}
|
||||
|
||||
internal class StreamConsts
|
||||
{
|
||||
public const int LOCK_WRITE = 0x1;
|
||||
public const int LOCK_EXCLUSIVE = 0x2;
|
||||
public const int LOCK_ONLYONCE = 0x4;
|
||||
public const int STATFLAG_DEFAULT = 0x0;
|
||||
public const int STATFLAG_NONAME = 0x1;
|
||||
public const int STATFLAG_NOOPEN = 0x2;
|
||||
public const int STGC_DEFAULT = 0x0;
|
||||
public const int STGC_OVERWRITE = 0x1;
|
||||
public const int STGC_ONLYIFCURRENT = 0x2;
|
||||
public const int STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE = 0x4;
|
||||
public const int STREAM_SEEK_SET = 0x0;
|
||||
public const int STREAM_SEEK_CUR = 0x1;
|
||||
public const int STREAM_SEEK_END = 0x2;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||
internal class ImageCodecInfoPrivate
|
||||
{
|
||||
[MarshalAs(UnmanagedType.Struct)]
|
||||
public Guid Clsid;
|
||||
[MarshalAs(UnmanagedType.Struct)]
|
||||
public Guid FormatID;
|
||||
|
||||
public IntPtr CodecName = IntPtr.Zero;
|
||||
public IntPtr DllName = IntPtr.Zero;
|
||||
public IntPtr FormatDescription = IntPtr.Zero;
|
||||
public IntPtr FilenameExtension = IntPtr.Zero;
|
||||
public IntPtr MimeType = IntPtr.Zero;
|
||||
|
||||
public int Flags;
|
||||
public int Version;
|
||||
public int SigCount;
|
||||
public int SigSize;
|
||||
|
||||
public IntPtr SigPattern = IntPtr.Zero;
|
||||
public IntPtr SigMask = IntPtr.Zero;
|
||||
}
|
||||
|
||||
internal class ComStreamFromDataStream : IStream
|
||||
{
|
||||
protected Stream DataStream;
|
||||
|
||||
// to support seeking ahead of the stream length...
|
||||
private long _virtualPosition = -1;
|
||||
|
||||
internal ComStreamFromDataStream(Stream dataStream)
|
||||
{
|
||||
this.DataStream = dataStream ?? throw new ArgumentNullException(nameof(dataStream));
|
||||
}
|
||||
|
||||
private void ActualizeVirtualPosition()
|
||||
{
|
||||
if (_virtualPosition == -1) return;
|
||||
|
||||
if (_virtualPosition > DataStream.Length)
|
||||
DataStream.SetLength(_virtualPosition);
|
||||
|
||||
DataStream.Position = _virtualPosition;
|
||||
|
||||
_virtualPosition = -1;
|
||||
}
|
||||
|
||||
public virtual IStream Clone()
|
||||
{
|
||||
NotImplemented();
|
||||
return null!;
|
||||
}
|
||||
|
||||
public virtual void Commit(int grfCommitFlags)
|
||||
{
|
||||
DataStream.Flush();
|
||||
ActualizeVirtualPosition();
|
||||
}
|
||||
|
||||
public virtual long CopyTo(IStream pstm, long cb, long[] pcbRead)
|
||||
{
|
||||
const int bufsize = 4096; // one page
|
||||
var buffer = Marshal.AllocHGlobal(bufsize);
|
||||
if (buffer == IntPtr.Zero) throw new OutOfMemoryException();
|
||||
long written = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (written < cb)
|
||||
{
|
||||
var toRead = bufsize;
|
||||
if (written + toRead > cb) toRead = (int) (cb - written);
|
||||
var read = Read(buffer, toRead);
|
||||
if (read == 0) break;
|
||||
if (pstm.Write(buffer, read) != read)
|
||||
{
|
||||
throw EFail("Wrote an incorrect number of bytes");
|
||||
}
|
||||
written += read;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
if (pcbRead != null && pcbRead.Length > 0)
|
||||
{
|
||||
pcbRead[0] = written;
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
public virtual Stream GetDataStream() => DataStream;
|
||||
|
||||
public virtual void LockRegion(long libOffset, long cb, int dwLockType)
|
||||
{
|
||||
}
|
||||
|
||||
protected static ExternalException EFail(string msg) => throw new ExternalException(msg, E_FAIL);
|
||||
|
||||
protected static void NotImplemented() => throw new NotImplementedException();
|
||||
|
||||
public virtual int Read(IntPtr buf, int length)
|
||||
{
|
||||
var buffer = new byte[length];
|
||||
var count = Read(buffer, length);
|
||||
Marshal.Copy(buffer, 0, buf, length);
|
||||
return count;
|
||||
}
|
||||
|
||||
public virtual int Read(byte[] buffer, int length)
|
||||
{
|
||||
ActualizeVirtualPosition();
|
||||
return DataStream.Read(buffer, 0, length);
|
||||
}
|
||||
|
||||
public virtual void Revert() => NotImplemented();
|
||||
|
||||
public virtual long Seek(long offset, int origin)
|
||||
{
|
||||
var pos = _virtualPosition;
|
||||
if (_virtualPosition == -1)
|
||||
{
|
||||
pos = DataStream.Position;
|
||||
}
|
||||
var len = DataStream.Length;
|
||||
|
||||
switch (origin)
|
||||
{
|
||||
case StreamConsts.STREAM_SEEK_SET:
|
||||
if (offset <= len)
|
||||
{
|
||||
DataStream.Position = offset;
|
||||
_virtualPosition = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_virtualPosition = offset;
|
||||
}
|
||||
break;
|
||||
case StreamConsts.STREAM_SEEK_END:
|
||||
if (offset <= 0)
|
||||
{
|
||||
DataStream.Position = len + offset;
|
||||
_virtualPosition = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_virtualPosition = len + offset;
|
||||
}
|
||||
break;
|
||||
case StreamConsts.STREAM_SEEK_CUR:
|
||||
if (offset + pos <= len)
|
||||
{
|
||||
DataStream.Position = pos + offset;
|
||||
_virtualPosition = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_virtualPosition = offset + pos;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return _virtualPosition != -1 ? _virtualPosition : DataStream.Position;
|
||||
}
|
||||
|
||||
public virtual void SetSize(long value) => DataStream.SetLength(value);
|
||||
|
||||
public virtual void Stat(IntPtr pstatstg, int grfStatFlag) => NotImplemented();
|
||||
|
||||
public virtual void UnlockRegion(long libOffset, long cb, int dwLockType)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual int Write(IntPtr buf, int length)
|
||||
{
|
||||
var buffer = new byte[length];
|
||||
Marshal.Copy(buf, buffer, 0, length);
|
||||
return Write(buffer, length);
|
||||
}
|
||||
|
||||
public virtual int Write(byte[] buffer, int length)
|
||||
{
|
||||
ActualizeVirtualPosition();
|
||||
DataStream.Write(buffer, 0, length);
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class MINMAXINFO
|
||||
{
|
||||
public POINT ptReserved;
|
||||
public POINT ptMaxSize;
|
||||
public POINT ptMaxPosition;
|
||||
public POINT ptMinTrackSize;
|
||||
public POINT ptMaxTrackSize;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct APPBARDATA
|
||||
{
|
||||
public int cbSize;
|
||||
public IntPtr hWnd;
|
||||
public uint uCallbackMessage;
|
||||
public uint uEdge;
|
||||
public RECT rc;
|
||||
public int lParam;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct RTL_OSVERSIONINFOEX
|
||||
{
|
||||
internal uint dwOSVersionInfoSize;
|
||||
internal uint dwMajorVersion;
|
||||
internal uint dwMinorVersion;
|
||||
internal uint dwBuildNumber;
|
||||
internal uint dwPlatformId;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
internal string szCSDVersion;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "<Pending>")]
|
||||
public enum WindowPositionFlags
|
||||
{
|
||||
SWP_ASYNCWINDOWPOS = 0x4000,
|
||||
SWP_DEFERERASE = 0x2000,
|
||||
SWP_DRAWFRAME = 0x0020,
|
||||
SWP_FRAMECHANGED = 0x0020,
|
||||
SWP_HIDEWINDOW = 0x0080,
|
||||
SWP_NOACTIVATE = 0x0010,
|
||||
SWP_NOCOPYBITS = 0x0100,
|
||||
SWP_NOMOVE = 0x0002,
|
||||
SWP_NOOWNERZORDER = 0x0200,
|
||||
SWP_NOREDRAW = 0x0008,
|
||||
SWP_NOREPOSITION = 0x0200,
|
||||
SWP_NOSENDCHANGING = 0x0400,
|
||||
SWP_NOSIZE = 0x0001,
|
||||
SWP_NOZORDER = 0x0004,
|
||||
SWP_SHOWWINDOW = 0x0040
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src/MpvNet.Windows/WPF/HandyControl/Tools/RegexPatterns.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
namespace HandyControl.Tools
|
||||
{
|
||||
public sealed class RegexPatterns
|
||||
{
|
||||
public const string MailPattern =
|
||||
@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$";
|
||||
|
||||
public const string PhonePattern = @"^((13[0-9])|(15[^4,\d])|(18[0,5-9]))\d{8}$";
|
||||
|
||||
public const string IpPattern =
|
||||
@"^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string IpAPattern =
|
||||
@"^(12[0-6]|1[0-1]\d|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string IpBPattern =
|
||||
@"^(19[0-1]|12[8-9]|1[3-8]\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string IpCPattern =
|
||||
@"^(19[2-9]|22[0-3]|2[0-1]\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string IpDPattern =
|
||||
@"^(22[4-9]|23\d\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string IpEPattern =
|
||||
@"^(25[0-5]|24\d\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string ChinesePattern = @"^[\u4e00-\u9fa5]$";
|
||||
|
||||
public const string UrlPattern =
|
||||
@"((http|ftp|https)://)(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\&%_\./-~-]*)?";
|
||||
|
||||
public const string NumberPattern = @"^\d+$";
|
||||
|
||||
public const string DigitsPattern = @"[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?";
|
||||
|
||||
public const string PIntPattern = @"^[1-9]\d*$";
|
||||
|
||||
public const string NIntPattern = @"^-[1-9]\d*$ ";
|
||||
|
||||
public const string IntPattern = @"^-?[1-9]\d*|0$";
|
||||
|
||||
public const string NnIntPattern = @"^[1-9]\d*|0$";
|
||||
|
||||
public const string NpIntPattern = @"^-[1-9]\d*|0$";
|
||||
|
||||
public const string PDoublePattern = @"^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$";
|
||||
|
||||
public const string NDoublePattern = @"^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$";
|
||||
|
||||
public const string DoublePattern = @"^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$";
|
||||
|
||||
public const string NnDoublePattern = @"^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$";
|
||||
|
||||
public const string NpDoublePattern = @"^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$";
|
||||
|
||||
public object GetValue(string propertyName) => GetType().GetField(propertyName).GetValue(null);
|
||||
}
|
||||
}
|
||||
8
src/MpvNet.Windows/WPF/ISettingControl.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
interface ISettingControl
|
||||
{
|
||||
bool Contains(string searchString);
|
||||
Setting Setting { get; }
|
||||
}
|
||||
128
src/MpvNet.Windows/WPF/InputWindow.xaml
Normal file
@@ -0,0 +1,128 @@
|
||||
<Window
|
||||
x:Class="MpvNet.Windows.WPF.InputWindow"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:controls="clr-namespace:MpvNet.Windows.WPF.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="Input Editor"
|
||||
Height="500"
|
||||
Width="800"
|
||||
FontSize="13"
|
||||
ShowInTaskbar="False"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
Loaded="Window_Loaded"
|
||||
Closed="Window_Closed"
|
||||
>
|
||||
|
||||
<Window.Resources>
|
||||
<Style x:Key="DataGridFontCentering" TargetType="{x:Type DataGridCell}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridCell}">
|
||||
<Grid Background="{TemplateBinding Background}">
|
||||
<ContentPresenter VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HeaderStyle" TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="{DynamicResource RegionBrush}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}" />
|
||||
<Setter Property="MinHeight" Value="22" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}"/>
|
||||
<Setter Property="Padding" Value="1"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border x:Name="border"
|
||||
BorderBrush="{DynamicResource PrimaryTextBrush}"
|
||||
BorderThickness="0"
|
||||
Background="{DynamicResource RegionBrush}">
|
||||
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextElement.FontWeight="Normal">
|
||||
</ContentPresenter>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" TargetName="border" Value="{DynamicResource HighlightBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="border" Value="#2C628B"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" TargetName="border" Value="0.25"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<controls:SearchControl
|
||||
HintText="Type ? to get help."
|
||||
x:Name="SearchControl"
|
||||
Width="300"
|
||||
Margin="0,20,0,20"
|
||||
Grid.ColumnSpan="2"
|
||||
/>
|
||||
|
||||
<DataGrid
|
||||
Name="DataGrid"
|
||||
Grid.Row="1"
|
||||
CommandManager.PreviewCanExecute="DataGrid_PreviewCanExecute"
|
||||
AutoGenerateColumns="False"
|
||||
ColumnHeaderStyle="{StaticResource HeaderStyle}"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
RowBackground="{Binding Theme.Background}"
|
||||
HorizontalGridLinesBrush="{Binding Theme.Foreground}"
|
||||
VerticalGridLinesBrush="{Binding Theme.Foreground}"
|
||||
CellStyle="{StaticResource DataGridFontCentering}"
|
||||
>
|
||||
|
||||
<DataGrid.Resources>
|
||||
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Setter Property="Background" Value="{Binding DataContext.Theme.Background, ElementName=DataGrid}" />
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Path}" MaxWidth="325"/>
|
||||
|
||||
<DataGridTemplateColumn Header="Input">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Button MinHeight="20" Click="ButtonClick">
|
||||
<TextBlock Text="{Binding Input}"></TextBlock>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Command" Binding="{Binding Command}" MaxWidth="325" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</Window>
|
||||
145
src/MpvNet.Windows/WPF/InputWindow.xaml.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public partial class InputWindow : Window
|
||||
{
|
||||
ICollectionView CollectionView;
|
||||
string StartupContent;
|
||||
public List<Binding> Bindings { get; }
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
public InputWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
|
||||
if (App.InputConf.HasMenu)
|
||||
Bindings = InputHelp.Parse(App.InputConf.Content);
|
||||
else
|
||||
Bindings = InputHelp.GetEditorBindings(App.InputConf.Content);
|
||||
|
||||
StartupContent = InputHelp.ConvertToString(Bindings);
|
||||
SearchControl.SearchTextBox.TextChanged += SearchTextBox_TextChanged;
|
||||
DataGrid.SelectionMode = DataGridSelectionMode.Single;
|
||||
CollectionViewSource collectionViewSource = new CollectionViewSource() { Source = Bindings };
|
||||
CollectionView = collectionViewSource.View;
|
||||
CollectionView.Filter = new Predicate<object>(item => Filter((Binding)item));
|
||||
DataGrid.ItemsSource = CollectionView;
|
||||
}
|
||||
|
||||
void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
CollectionView.Refresh();
|
||||
|
||||
if (SearchControl.SearchTextBox.Text == "?")
|
||||
{
|
||||
SearchControl.SearchTextBox.Text = "";
|
||||
|
||||
Msg.ShowInfo("Filtering" + BR2 +
|
||||
"Reduce the filter scope with:" + BR2 +
|
||||
"i input" + BR2 +
|
||||
"m menu" + BR2 +
|
||||
"c command" + BR2 +
|
||||
"If only one character is entered input search is performed.");
|
||||
}
|
||||
}
|
||||
|
||||
bool Filter(Binding item)
|
||||
{
|
||||
if (item.Command == "")
|
||||
return false;
|
||||
|
||||
string searchText = SearchControl.SearchTextBox.Text.ToLower();
|
||||
|
||||
if (searchText == "" || searchText == "?")
|
||||
return true;
|
||||
|
||||
if (searchText.Length == 1)
|
||||
return item.Input.ToLower().Replace("ctrl+", "").Replace("shift+", "").Replace("alt+", "") == searchText.ToLower();
|
||||
else if (searchText.StartsWith("i ") || searchText.StartsWith("i:") || searchText.Length == 1)
|
||||
{
|
||||
if (searchText.Length > 1)
|
||||
searchText = searchText.Substring(2).Trim();
|
||||
|
||||
if (searchText.Length < 3)
|
||||
return item.Input.ToLower().Replace("ctrl+", "").Replace("shift+", "").Replace("alt+", "").Contains(searchText);
|
||||
else
|
||||
return item.Input.ToLower().Contains(searchText);
|
||||
}
|
||||
else if (searchText.StartsWith("m ") || searchText.StartsWith("m:"))
|
||||
return item.Path.ToLower().Contains(searchText.Substring(2).Trim());
|
||||
else if (searchText.StartsWith("c ") || searchText.StartsWith("c:"))
|
||||
return item.Command.ToLower().Contains(searchText.Substring(2).Trim());
|
||||
else if (item.Command.ToLower().Contains(searchText) ||
|
||||
item.Path.ToLower().Contains(searchText) ||
|
||||
item.Input.ToLower().Contains(searchText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Binding? item = ((Button)e.Source).DataContext as Binding;
|
||||
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
LearnWindow window = new LearnWindow();
|
||||
window.Owner = this;
|
||||
window.InputItem = item;
|
||||
window.ShowDialog();
|
||||
Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
}
|
||||
|
||||
void Window_Loaded(object sender, RoutedEventArgs e) => Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
|
||||
void Window_Closed(object sender, EventArgs e)
|
||||
{
|
||||
string newContent = InputHelp.ConvertToString(Bindings);
|
||||
|
||||
if (StartupContent == newContent)
|
||||
return;
|
||||
|
||||
if (App.InputConf.HasMenu)
|
||||
File.WriteAllText(App.InputConf.Path, App.InputConf.Content = newContent);
|
||||
else
|
||||
{
|
||||
newContent = InputHelp.ConvertToString(InputHelp.GetReducedBindings(Bindings));
|
||||
File.WriteAllText(App.InputConf.Path, App.InputConf.Content = newContent);
|
||||
}
|
||||
|
||||
Msg.ShowInfo("Changes will be available on next startup.");
|
||||
}
|
||||
|
||||
void DataGrid_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
DataGrid grid = (DataGrid)sender;
|
||||
|
||||
if (e.Command == DataGrid.DeleteCommand)
|
||||
if (Msg.ShowQuestion($"Confirm to delete: {(grid.SelectedItem as Binding)!.Input} ({(grid.SelectedItem as Binding)!.Path})") != MessageBoxResult.OK)
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (e.Key == Key.Escape)
|
||||
Close();
|
||||
|
||||
if (e.Key == Key.F3 || e.Key == Key.F6 || (e.Key == Key.F && Keyboard.IsKeyDown(Key.LeftCtrl)))
|
||||
{
|
||||
Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
SearchControl.SearchTextBox.SelectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/MpvNet.Windows/WPF/LearnWindow.xaml
Normal file
@@ -0,0 +1,54 @@
|
||||
<Window
|
||||
x:Class="MpvNet.Windows.LearnWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="Learn Input"
|
||||
Height="150"
|
||||
Width="350"
|
||||
FontSize="16"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize"
|
||||
Loaded="Window_Loaded"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
MouseWheel="Window_MouseWheel"
|
||||
MouseUp="Window_MouseUp"
|
||||
MouseDoubleClick="Window_MouseDoubleClick" PreviewKeyDown="Window_PreviewKeyDown"
|
||||
>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="40" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
x:Name="MenuTextBlock"
|
||||
Margin="0,10,0,0"
|
||||
Grid.ColumnSpan="2"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
/>
|
||||
|
||||
<TextBlock
|
||||
x:Name="KeyTextBlock"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Center"
|
||||
/>
|
||||
|
||||
<Button x:Name="ConfirmButton" Grid.Row="2" Margin="10,0,10,10" Click="ConfirmButton_Click">Confirm</Button>
|
||||
<Button x:Name="ClearButton" Grid.Row="2" Margin="0,0,10,10" Click="ClearButton_Click" Grid.Column="1">Clear</Button>
|
||||
</Grid>
|
||||
</Window>
|
||||
272
src/MpvNet.Windows/WPF/LearnWindow.xaml.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using MpvNet.Help;
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows;
|
||||
|
||||
public partial class LearnWindow : Window
|
||||
{
|
||||
public Binding? InputItem { get; set; }
|
||||
string NewKey = "";
|
||||
|
||||
uint MAPVK_VK_TO_VSC = 0;
|
||||
|
||||
int VK_MENU = 0x12;
|
||||
int VK_LMENU = 0xA4;
|
||||
int VK_RMENU = 0xA5;
|
||||
|
||||
int VK_CONTROL = 0x11;
|
||||
int VK_LCONTROL = 0xA2;
|
||||
int VK_RCONTROL = 0xA3;
|
||||
|
||||
public LearnWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
}
|
||||
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
static extern short GetKeyState(int keyCode);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern uint MapVirtualKey(uint uCode, uint uMapType);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState,
|
||||
StringBuilder pwszBuff, int cchBuff, uint wFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool GetKeyboardState(byte[] lpKeyState);
|
||||
|
||||
string ToUnicode(uint vk)
|
||||
{
|
||||
byte[] keys = new byte[256];
|
||||
|
||||
if (!GetKeyboardState(keys))
|
||||
return "";
|
||||
|
||||
if ((keys[VK_CONTROL] & 0x80) != 0 && (keys[VK_MENU] & 0x80) == 0)
|
||||
keys[VK_LCONTROL] = keys[VK_RCONTROL] = keys[VK_CONTROL] = 0;
|
||||
|
||||
uint scanCode = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
|
||||
|
||||
string ret = ToUnicode(vk, scanCode, keys);
|
||||
|
||||
if (ret.Length == 1 && ret[0] < 32)
|
||||
return "";
|
||||
|
||||
if (ret == "" && (keys[VK_MENU] & 0x80) != 0)
|
||||
{
|
||||
keys[VK_LMENU] = keys[VK_RMENU] = keys[VK_MENU] = 0;
|
||||
ret = ToUnicode(vk, scanCode, keys);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public string ToUnicode(uint vk, uint scanCode, byte[] keys)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(10);
|
||||
ToUnicode(vk, scanCode, keys, sb, sb.Capacity, 0);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.HWnd = hwnd;
|
||||
m.Msg = msg;
|
||||
m.WParam = wParam;
|
||||
m.LParam = lParam;
|
||||
ProcessKeyEventArgs(ref m);
|
||||
return m.Result;
|
||||
}
|
||||
|
||||
void OnKeyDown(uint vk)
|
||||
{
|
||||
Keys key = (Keys)vk;
|
||||
|
||||
if (key == Keys.ControlKey || key == Keys.ShiftKey ||
|
||||
key == Keys.Menu || key == Keys.None)
|
||||
|
||||
return;
|
||||
|
||||
string text = ToUnicode(vk);
|
||||
|
||||
if ((int)key > 111 && (int)key < 136)
|
||||
text = "F" + ((int)key - 111);
|
||||
|
||||
if ((int)key > 95 && (int)key < 106)
|
||||
text = "KP" + ((int)key - 96);
|
||||
|
||||
switch (text)
|
||||
{
|
||||
case "#": text = "SHARP"; break;
|
||||
case "´´": text = "´"; break;
|
||||
case "``": text = "`"; break;
|
||||
case "^^": text = "^"; break;
|
||||
}
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Left: text = "Left"; break;
|
||||
case Keys.Up: text = "Up"; break;
|
||||
case Keys.Right: text = "Right"; break;
|
||||
case Keys.Down: text = "Down"; break;
|
||||
case Keys.Space: text = "Space"; break;
|
||||
case Keys.Enter: text = "Enter"; break;
|
||||
case Keys.Tab: text = "Tab"; break;
|
||||
case Keys.Back: text = "BS"; break;
|
||||
case Keys.Delete: text = "Del"; break;
|
||||
case Keys.Insert: text = "Ins"; break;
|
||||
case Keys.Home: text = "Home"; break;
|
||||
case Keys.End: text = "End"; break;
|
||||
case Keys.PageUp: text = "PGUP"; break;
|
||||
case Keys.PageDown: text = "PGDWN"; break;
|
||||
case Keys.Escape: text = "Esc"; break;
|
||||
case Keys.Sleep: text = "Sleep"; break;
|
||||
case Keys.Cancel: text = "Cancel"; break;
|
||||
case Keys.PrintScreen: text = "Print"; break;
|
||||
case Keys.BrowserFavorites: text = "Favorites"; break;
|
||||
case Keys.BrowserSearch: text = "Search"; break;
|
||||
case Keys.BrowserHome: text = "Homepage"; break;
|
||||
case Keys.LaunchMail: text = "Mail"; break;
|
||||
case Keys.Play: text = "Play"; break;
|
||||
case Keys.Pause: text = "Pause"; break;
|
||||
case Keys.MediaPlayPause: text = "PlayPause"; break;
|
||||
case Keys.MediaStop: text = "Stop"; break;
|
||||
case Keys.MediaNextTrack: text = "Next"; break;
|
||||
case Keys.MediaPreviousTrack: text = "Prev"; break;
|
||||
|
||||
case Keys.VolumeUp:
|
||||
case Keys.VolumeDown:
|
||||
case Keys.VolumeMute:
|
||||
text = ""; break;
|
||||
}
|
||||
|
||||
bool isAlt = GetKeyState(18) < 0;
|
||||
bool isShift = GetKeyState(16) < 0;
|
||||
bool isCtrl = GetKeyState(17) < 0;
|
||||
|
||||
bool isLetter = (int)key > 64 && (int)key < 91;
|
||||
|
||||
if (isLetter && isShift)
|
||||
text = text.ToUpper();
|
||||
|
||||
string keyString = ToUnicode(vk);
|
||||
|
||||
if (isAlt && !isCtrl)
|
||||
text = "ALT+" + text;
|
||||
|
||||
if (isShift && keyString == "")
|
||||
text = "SHIFT+" + text;
|
||||
|
||||
if (isCtrl && !(keyString != "" && isCtrl && isAlt))
|
||||
text = "CTRL+" + text;
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
SetKey(text);
|
||||
}
|
||||
|
||||
void SetKey(string? key)
|
||||
{
|
||||
NewKey = key!;
|
||||
MenuTextBlock.Text = InputItem?.Path;
|
||||
KeyTextBlock.Text = key;
|
||||
}
|
||||
|
||||
void ProcessKeyEventArgs(ref Message m)
|
||||
{
|
||||
int WM_KEYDOWN = 0x100;
|
||||
int WM_SYSKEYDOWN = 0x104;
|
||||
int WM_APPCOMMAND = 0x319;
|
||||
|
||||
if (m.Msg == WM_KEYDOWN || m.Msg == WM_SYSKEYDOWN)
|
||||
OnKeyDown((uint)m.WParam.ToInt64());
|
||||
else if (m.Msg == WM_APPCOMMAND)
|
||||
{
|
||||
string? value = MpvHelp.WM_APPCOMMAND_to_mpv_key((int)(m.LParam.ToInt64() >> 16 & ~0xf000));
|
||||
|
||||
if (value != null)
|
||||
SetKey(value);
|
||||
}
|
||||
}
|
||||
|
||||
void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
|
||||
source.AddHook(new HwndSourceHook(WndProc));
|
||||
SetKey(InputItem?.Input);
|
||||
}
|
||||
|
||||
void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InputItem!.Input = NewKey;
|
||||
Close();
|
||||
}
|
||||
|
||||
void ClearButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InputItem!.Input = "";
|
||||
Close();
|
||||
}
|
||||
|
||||
void Window_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (e.Delta > 0)
|
||||
SetKey("WHEEL_UP");
|
||||
else
|
||||
SetKey("WHEEL_DOWN");
|
||||
}
|
||||
|
||||
void Window_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
switch (e.ChangedButton)
|
||||
{
|
||||
case MouseButton.Left:
|
||||
if (BlockMBTN_LEFT)
|
||||
BlockMBTN_LEFT = false;
|
||||
else
|
||||
SetKey("MBTN_LEFT");
|
||||
break;
|
||||
case MouseButton.Middle:
|
||||
SetKey("MBTN_MID");
|
||||
break;
|
||||
case MouseButton.XButton1:
|
||||
SetKey("MBTN_BACK");
|
||||
break;
|
||||
case MouseButton.XButton2:
|
||||
SetKey("MBTN_FORWARD");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool BlockMBTN_LEFT;
|
||||
|
||||
void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
SetKey("MBTN_LEFT_DBL");
|
||||
BlockMBTN_LEFT = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Window_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Tab)
|
||||
{
|
||||
OnKeyDown((uint)Keys.Tab);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/MpvNet.Windows/WPF/MenuHelp.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public class MenuHelp
|
||||
{
|
||||
public static MenuItem? Add(ItemCollection? items, string path)
|
||||
{
|
||||
string[] parts = path.Split(new[] { " > ", " | " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
for (int x = 0; x < parts.Length; x++)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
foreach (MenuItem i in items!.OfType<MenuItem>())
|
||||
{
|
||||
if (x < parts.Length - 1)
|
||||
{
|
||||
if ((string)i.Header == parts[x])
|
||||
{
|
||||
found = true;
|
||||
items = i.Items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
if (x == parts.Length - 1)
|
||||
{
|
||||
if (parts[x] == "-")
|
||||
items?.Add(new Separator());
|
||||
else
|
||||
{
|
||||
MenuItem item = new MenuItem() { Header = parts[x] };
|
||||
items?.Add(item);
|
||||
items = item.Items;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuItem item = new MenuItem() { Header = parts[x] };
|
||||
items?.Add(item);
|
||||
items = item.Items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
51
src/MpvNet.Windows/WPF/Msg.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
using Forms = System.Windows.Forms;
|
||||
|
||||
using MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public class Msg
|
||||
{
|
||||
public static void ShowInfo(object msg) => Show(msg, MessageBoxImage.Information);
|
||||
|
||||
public static void ShowError(object msg) => Show(msg, MessageBoxImage.Error);
|
||||
|
||||
public static void ShowWarning(object msg) => Show(msg, MessageBoxImage.Warning);
|
||||
|
||||
public static MessageBoxResult ShowQuestion(object msg,
|
||||
MessageBoxButton buttons = MessageBoxButton.OKCancel)
|
||||
{
|
||||
return Show(msg, MessageBoxImage.Question, buttons);
|
||||
}
|
||||
|
||||
public static void ShowException(Exception exception)
|
||||
{
|
||||
Show(exception.Message, MessageBoxImage.Error, MessageBoxButton.OK, exception.ToString());
|
||||
}
|
||||
|
||||
public static MessageBoxResult Show(
|
||||
object msg,
|
||||
MessageBoxImage img,
|
||||
MessageBoxButton buttons = MessageBoxButton.OK,
|
||||
string? details = null)
|
||||
{
|
||||
ApartmentState state = Thread.CurrentThread.GetApartmentState();
|
||||
|
||||
if (state == ApartmentState.STA)
|
||||
return fn();
|
||||
else
|
||||
return Application.Current.Dispatcher.Invoke(fn);
|
||||
|
||||
MessageBoxResult fn()
|
||||
{
|
||||
MessageBoxEx.DetailsText = details;
|
||||
|
||||
return MessageBoxEx.OpenMessageBox((msg + "").Trim(),
|
||||
Forms.Application.ProductName, buttons, img);
|
||||
}
|
||||
}
|
||||
}
|
||||
226
src/MpvNet.Windows/WPF/MsgBox/MessageBoxEx.xaml
Normal file
@@ -0,0 +1,226 @@
|
||||
<Window x:Class="MpvNet.Windows.WPF.MsgBox.MessageBoxEx"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="{Binding Path=MessageTitle}"
|
||||
MinHeight="100"
|
||||
MaxHeight="650"
|
||||
MinWidth="200"
|
||||
MaxWidth="{Binding Path=MaxFormWidth}"
|
||||
Background="{Binding Path=MessageBackground}"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ShowInTaskbar="False"
|
||||
WindowStyle="SingleBorderWindow"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="WidthAndHeight"
|
||||
SizeChanged="NotifiableWindow_SizeChanged"
|
||||
Loaded="Window_Loaded"
|
||||
Closing="Window_Closing"
|
||||
Height="Auto"
|
||||
Width="Auto"
|
||||
SourceInitialized="Window_SourceInitialized"
|
||||
>
|
||||
|
||||
<Window.Resources>
|
||||
<Style x:Key="GroupBoxExpanderToggleButtonStyle" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Grid SnapsToDevicePixels="False" Background="Transparent">
|
||||
<Ellipse HorizontalAlignment="Center" x:Name="circle" VerticalAlignment="Center" Width="15" Height="15"
|
||||
Fill="{DynamicResource ButtonNormalBackgroundFill}" Stroke="{Binding Path=MessageForeground}"/>
|
||||
<Ellipse Visibility="Hidden" HorizontalAlignment="Center" x:Name="shadow" VerticalAlignment="Center" Width="13" Height="13"
|
||||
Fill="{DynamicResource ExpanderShadowFill}"/>
|
||||
<Path SnapsToDevicePixels="false" x:Name="arrow" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
Stroke="{Binding Path=MessageForeground}" StrokeThickness="2" Data="M1,1 L4,4 7,1" />
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="true">
|
||||
<Setter Property="Data" TargetName="arrow" Value="M 1,4 L 4,1 L 7,4"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<SolidColorBrush x:Key="GroupBoxBorderBrush" Color="#D0D0BF"/>
|
||||
<SolidColorBrush x:Key="GroupBoxHeaderBrush" Color="White"/>
|
||||
<BorderGapMaskConverter x:Key="BorderGapMaskConverter"/>
|
||||
|
||||
<Style x:Key="linkCursor" TargetType="{x:Type TextBlock}" >
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True" >
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
</Window.Resources>
|
||||
|
||||
<Grid >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid x:Name="gridMsg" Grid.Row="0" Margin="10,10,0,10" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid x:Name="imgGrid" Grid.Column="0">
|
||||
<Image x:Name="imgMsgBoxIcon"
|
||||
Source="{Binding Path=MessageIcon}"
|
||||
Visibility="{Binding Path=ShowIcon}"
|
||||
Width="32" Height="32"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Top"
|
||||
MouseLeftButtonUp="ImgMsgBoxIcon_MouseLeftButtonUp"
|
||||
>
|
||||
</Image>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" VerticalAlignment="Center" >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox Grid.Row="0" x:Name="textboxMessage"
|
||||
TextWrapping="Wrap" BorderThickness="0" IsReadOnly="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Padding="0,0,10,0"
|
||||
Background="Transparent"
|
||||
Foreground="{Binding Path=MessageForeground}"
|
||||
Text="{Binding Path=Message}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="1" x:Name="tbUrl" Margin="0,0,0,10"
|
||||
Visibility="{Binding Path=ShowUrl}"
|
||||
Foreground="{Binding Path=UrlForeground}"
|
||||
TextWrapping="Wrap"
|
||||
TextDecorations="Underline"
|
||||
HorizontalAlignment="Center"
|
||||
MouseLeftButtonUp="TbUrl_MouseLeftButtonUp"
|
||||
Style="{StaticResource linkCursor}"
|
||||
/>
|
||||
|
||||
<CheckBox Grid.Row="2" Margin="0,0,0,10" HorizontalAlignment="Center"
|
||||
Visibility="{Binding Path=ShowCheckBox}"
|
||||
Content="{Binding Path=CheckBoxData.CheckBoxText}"
|
||||
IsChecked="{Binding Path=CheckBoxData.CheckBoxIsChecked}"
|
||||
Foreground="{Binding Path=MessageForeground}"></CheckBox>
|
||||
|
||||
<Border x:Name="stackButtons" Grid.Row="3" Padding="10"
|
||||
Background="{Binding Path=ButtonBackground}" >
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" >
|
||||
<Button x:Name="btnOK" Content=" OK " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Height="25"
|
||||
Visibility="{Binding Path=ShowOk}"
|
||||
IsDefault="{Binding Path=IsDefaultOK}"
|
||||
Click="BtnOK_Click" />
|
||||
<Button x:Name="btnYes" Content=" Yes " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowYes}"
|
||||
IsDefault="{Binding Path=IsDefaultYes}"
|
||||
Click="BtnYes_Click"/>
|
||||
<Button x:Name="btnNo" Content=" No " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowNo}"
|
||||
IsDefault="{Binding Path=IsDefaultNo}"
|
||||
Click="BtnNo_Click"/>
|
||||
<Button x:Name="btnAbort" Content=" Abort " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowAbort}"
|
||||
IsDefault="{Binding Path=IsDefaultAbort}"
|
||||
Click="BtnAbort_Click"/>
|
||||
<Button x:Name="btnRetry" Content=" Retry " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowRetry}"
|
||||
IsDefault="{Binding Path=IsDefaultRetry}"
|
||||
Click="BtnRetry_Click"/>
|
||||
<Button x:Name="btnIgnore" Content=" Ignore " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowIgnore}"
|
||||
IsDefault="{Binding Path=IsDefaultIgnore}"
|
||||
Click="BtnIgnore_Click"/>
|
||||
<Button x:Name="btnCancel" Content=" Cancel " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowCancel}"
|
||||
IsDefault="{Binding Path=IsDefaultCancel}"
|
||||
Click="BtnCancel_Click"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Expander Grid.Row="4" Header=" Details"
|
||||
IsExpanded="{Binding Path=Expanded}"
|
||||
Margin="3,8,0,0"
|
||||
Visibility="{Binding Path=ShowDetailsBtn}"
|
||||
>
|
||||
<Expander.Template>
|
||||
<ControlTemplate TargetType="{x:Type Expander}">
|
||||
<Grid SnapsToDevicePixels="true">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="6" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="6" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid SnapsToDevicePixels="False" Background="Transparent" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton Grid.Column="0" MinHeight="0" MinWidth="0" Name="HeaderToggle"
|
||||
Style="{StaticResource GroupBoxExpanderToggleButtonStyle}"
|
||||
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<ContentPresenter ContentSource="Header" RecognizesAccessKey="true"
|
||||
TextElement.Foreground="{Binding ElementName=textboxMessage, Path=Foreground}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
/>
|
||||
</Grid>
|
||||
<ContentPresenter x:Name="ExpandSite" Visibility="Collapsed" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsExpanded" Value="true">
|
||||
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="false">
|
||||
<Setter Property="Foreground" Value="{Binding Path=MessageForeground}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="true">
|
||||
<Setter Property="Foreground" Value="{Binding Path=MessageForeground}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Expander.Template>
|
||||
<TextBox x:Name="textboxDetails" Grid.Row="2" Margin="0,10,0,0" Text="{Binding Path=DetailsText}" Height="100"
|
||||
TextWrapping="Wrap" BorderThickness="0" IsReadOnly="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Padding="0,0,10,0"
|
||||
Background="Transparent"
|
||||
Foreground="{Binding Path=MessageForeground}"
|
||||
/>
|
||||
</Expander>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
893
src/MpvNet.Windows/WPF/MsgBox/MessageBoxEx.xaml.cs
Normal file
@@ -0,0 +1,893 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Media;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
using MpvNet.Help;
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public partial class MessageBoxEx : Window, INotifyPropertyChanged
|
||||
{
|
||||
#region INotifyPropertyChanged
|
||||
|
||||
private bool isModified = false;
|
||||
|
||||
public bool IsModified {
|
||||
get { return isModified; }
|
||||
set {
|
||||
if (value != isModified)
|
||||
{
|
||||
isModified = true;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (PropertyChanged != null)
|
||||
{
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
if (propertyName != "IsModified")
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion INotifyPropertyChanged
|
||||
|
||||
#region fields
|
||||
|
||||
private string? message;
|
||||
private string? messageTitle;
|
||||
private MessageBoxButton? buttons;
|
||||
private MessageBoxResult messageResult;
|
||||
private MessageBoxButtonEx? buttonsEx;
|
||||
private MessageBoxResultEx messageResultEx;
|
||||
private ImageSource? messageIcon;
|
||||
private MessageBoxImage msgBoxImage;
|
||||
private double buttonWidth = 0d;
|
||||
private bool expanded = false;
|
||||
private bool isDefaultOK;
|
||||
private bool isDefaultCancel;
|
||||
private bool isDefaultYes;
|
||||
private bool isDefaultNo;
|
||||
private bool isDefaultAbort;
|
||||
private bool isDefaultRetry;
|
||||
private bool isDefaultIgnore;
|
||||
|
||||
private bool usingExButtons = false;
|
||||
|
||||
#endregion fields
|
||||
|
||||
#region properties
|
||||
|
||||
public string Message {
|
||||
get { return message; }
|
||||
set {
|
||||
if (value != message)
|
||||
{
|
||||
message = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string MessageTitle {
|
||||
get { return messageTitle; }
|
||||
set {
|
||||
if (value != messageTitle)
|
||||
{
|
||||
messageTitle = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MessageBoxResult MessageResult { get { return this.messageResult; } set { this.messageResult = value; } }
|
||||
public MessageBoxResultEx MessageResultEx { get { return this.messageResultEx; } set { this.messageResultEx = value; } }
|
||||
|
||||
public MessageBoxButton? Buttons {
|
||||
get { return buttons; }
|
||||
set {
|
||||
if (value != buttons)
|
||||
{
|
||||
buttons = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged("ShowOk");
|
||||
NotifyPropertyChanged("ShowCancel");
|
||||
NotifyPropertyChanged("ShowYes");
|
||||
NotifyPropertyChanged("ShowNo");
|
||||
}
|
||||
}
|
||||
}
|
||||
public MessageBoxButtonEx? ButtonsEx {
|
||||
get { return buttonsEx; }
|
||||
set {
|
||||
if (value != buttonsEx)
|
||||
{
|
||||
buttonsEx = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged("ShowOk");
|
||||
NotifyPropertyChanged("ShowCancel");
|
||||
NotifyPropertyChanged("ShowYes");
|
||||
NotifyPropertyChanged("ShowNo");
|
||||
NotifyPropertyChanged("ShowAbort");
|
||||
NotifyPropertyChanged("ShowRetry");
|
||||
NotifyPropertyChanged("ShowIgnore");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Visibility ShowOk => (!usingExButtons && Buttons == MessageBoxButton.OK ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.OKCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.OK ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.OKCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowCancel => (!usingExButtons && Buttons == MessageBoxButton.OKCancel ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.OKCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.RetryCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowYes => (!usingExButtons && Buttons == MessageBoxButton.YesNo ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNo ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNoCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowNo => (!usingExButtons && Buttons == MessageBoxButton.YesNo ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNo ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNoCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowRetry => (usingExButtons && ButtonsEx == MessageBoxButtonEx.AbortRetryIgnore ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.RetryCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowAbort => (usingExButtons && ButtonsEx == MessageBoxButtonEx.AbortRetryIgnore)
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowIgnore => (usingExButtons && ButtonsEx == MessageBoxButtonEx.AbortRetryIgnore)
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowIcon => (MessageIcon != null) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public ImageSource? MessageIcon {
|
||||
get => messageIcon;
|
||||
set {
|
||||
if (value != messageIcon)
|
||||
{
|
||||
messageIcon = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double ButtonWidth {
|
||||
get => buttonWidth;
|
||||
set {
|
||||
if (value != buttonWidth)
|
||||
{
|
||||
buttonWidth = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Expanded {
|
||||
get => expanded;
|
||||
set {
|
||||
if (value != expanded)
|
||||
{
|
||||
expanded = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultOK {
|
||||
get => isDefaultOK;
|
||||
set {
|
||||
if (value != isDefaultOK)
|
||||
{
|
||||
isDefaultOK = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultCancel {
|
||||
get => isDefaultCancel;
|
||||
set {
|
||||
if (value != isDefaultCancel)
|
||||
{
|
||||
isDefaultCancel = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultYes {
|
||||
get => isDefaultYes;
|
||||
set {
|
||||
if (value != isDefaultYes)
|
||||
{
|
||||
isDefaultYes = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultNo {
|
||||
get => isDefaultNo;
|
||||
set {
|
||||
if (value != isDefaultNo)
|
||||
{
|
||||
isDefaultNo = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultAbort {
|
||||
get => isDefaultAbort;
|
||||
set {
|
||||
if (value != isDefaultAbort)
|
||||
{
|
||||
isDefaultAbort = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultRetry {
|
||||
get => isDefaultRetry;
|
||||
set {
|
||||
if (value != isDefaultRetry)
|
||||
{ isDefaultRetry = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultIgnore {
|
||||
get => isDefaultIgnore;
|
||||
set {
|
||||
if (value != isDefaultIgnore)
|
||||
{
|
||||
isDefaultIgnore = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion properties
|
||||
|
||||
#region constructors
|
||||
|
||||
private MessageBoxEx()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
LargestButtonWidth();
|
||||
}
|
||||
|
||||
public MessageBoxEx(string msg, string title, MessageBoxButton buttons = MessageBoxButton.OK,
|
||||
MessageBoxImage image = MessageBoxImage.None)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
Init(msg, title, buttons, image);
|
||||
}
|
||||
|
||||
public MessageBoxEx(string msg, string title, MessageBoxButtonEx buttons = MessageBoxButtonEx.OK,
|
||||
MessageBoxImage image = MessageBoxImage.None)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
Init(msg, title, buttons, image);
|
||||
}
|
||||
|
||||
#endregion constructors
|
||||
|
||||
#region non-static methods
|
||||
|
||||
protected virtual void Init(string msg, string title, MessageBoxButton buttons, MessageBoxImage image)
|
||||
{
|
||||
InitTop(msg, title);
|
||||
usingExButtons = false;
|
||||
ButtonsEx = null;
|
||||
Buttons = buttons;
|
||||
SetButtonTemplates();
|
||||
InitBottom(image);
|
||||
FindDefaultButton(staticButtonDefault);
|
||||
}
|
||||
|
||||
protected virtual void Init(string msg, string title, MessageBoxButtonEx buttons, MessageBoxImage image)
|
||||
{
|
||||
InitTop(msg, title);
|
||||
usingExButtons = true;
|
||||
Buttons = null;
|
||||
ButtonsEx = buttons;
|
||||
SetButtonTemplates();
|
||||
InitBottom(image);
|
||||
FindDefaultButtonEx(staticButtonDefault);
|
||||
}
|
||||
|
||||
void InitTop(string msg, string title)
|
||||
{
|
||||
// determine whether or not to show the details pane and checkbox
|
||||
ShowDetailsBtn = (string.IsNullOrEmpty(DetailsText)) ? Visibility.Collapsed : Visibility.Visible;
|
||||
ShowCheckBox = (CheckBoxData == null) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
// Well, the binding for family/size don't appear to be working, so I have to set them
|
||||
// manually. Weird...
|
||||
FontFamily = MsgFontFamily;
|
||||
FontSize = MsgFontSize;
|
||||
LargestButtonWidth();
|
||||
|
||||
// configure the form based on specified criteria
|
||||
Message = msg;
|
||||
MessageTitle = (string.IsNullOrEmpty(title.Trim())) ? "Application Message" : title;
|
||||
|
||||
// url (if specified)
|
||||
if (Url != null)
|
||||
{
|
||||
tbUrl.Text = (string.IsNullOrEmpty(UrlDisplayName)) ? Url.ToString() : UrlDisplayName;
|
||||
tbUrl.ToolTip = new ToolTip() { Content = Url.ToString() };
|
||||
}
|
||||
}
|
||||
|
||||
private void InitBottom(MessageBoxImage image)
|
||||
{
|
||||
MessageBackground = (MessageBackground == null) ? new SolidColorBrush(Colors.White) : MessageBackground;
|
||||
MessageForeground = (MessageForeground == null) ? new SolidColorBrush(Colors.Black) : MessageForeground;
|
||||
ButtonBackground = (ButtonBackground == null) ? new SolidColorBrush(ColorFromString("#cdcdcd")) : ButtonBackground;
|
||||
|
||||
MessageIcon = null;
|
||||
|
||||
msgBoxImage = image;
|
||||
|
||||
if (DelegateObj != null)
|
||||
{
|
||||
Style style = (Style)(FindResource("ImageOpacityChanger"));
|
||||
|
||||
if (style != null)
|
||||
{
|
||||
imgMsgBoxIcon.Style = style;
|
||||
|
||||
if (!string.IsNullOrEmpty(DelegateToolTip))
|
||||
{
|
||||
System.Windows.Controls.ToolTip tooltip = new System.Windows.Controls.ToolTip() { Content = DelegateToolTip };
|
||||
// for some reason, Image elements can't do tooltips, so I assign the tootip
|
||||
// to the parent grid. This seems to work fine.
|
||||
imgGrid.ToolTip = tooltip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// multiple images have the same ordinal value, and are indicated in the comments below.
|
||||
// WTF Microsoft?
|
||||
switch ((int)image)
|
||||
{
|
||||
case 16: // MessageBoxImage.Error, MessageBoxImage.Stop, MessageBox.Image.Hand
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Error);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Hand.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
case 64: // MessageBoxImage.Information, MessageBoxImage.Asterisk
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Information);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Asterisk.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
case 32: // MessageBoxImage.Question
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Question);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Question.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
case 48: // MessageBoxImage.Warning, MessageBoxImage.Exclamation
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Warning);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Exclamation.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
MessageIcon = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public ImageSource GetIcon(Icon icon)
|
||||
{
|
||||
BitmapSource image = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
||||
return image;
|
||||
}
|
||||
|
||||
protected virtual void CenterInScreen()
|
||||
{
|
||||
double width = ActualWidth;
|
||||
double height = ActualHeight;
|
||||
Left = (SystemParameters.WorkArea.Width - width) / 2 + SystemParameters.WorkArea.Left;
|
||||
Top = (SystemParameters.WorkArea.Height - height) / 2 + SystemParameters.WorkArea.Top;
|
||||
}
|
||||
|
||||
protected void LargestButtonWidth()
|
||||
{
|
||||
Typeface typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
|
||||
|
||||
StackPanel panel = (StackPanel)stackButtons.Child;
|
||||
double width = 0;
|
||||
string largestName = string.Empty;
|
||||
foreach (System.Windows.Controls.Button button in panel.Children)
|
||||
{
|
||||
// Using the FormattedText object
|
||||
// will strip whitespace before measuring the text, so we convert spaces to double
|
||||
// hyphens to compensate (I like to pad button Content with a leading and trailing
|
||||
// space) so that the button is wide enough to present a more padded appearance.
|
||||
FormattedText formattedText = new FormattedText(
|
||||
(button.Name == "btnDetails") ? "--Details--" : ((string)(button.Content)).Replace(" ", "--"),
|
||||
CultureInfo.CurrentUICulture,
|
||||
System.Windows.FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
FontSize = FontSize,
|
||||
System.Windows.Media.Brushes.Black,
|
||||
VisualTreeHelper.GetDpi(this).PixelsPerDip);
|
||||
|
||||
if (width < formattedText.Width)
|
||||
largestName = button.Name;
|
||||
|
||||
width = Math.Max(width, formattedText.Width);
|
||||
}
|
||||
ButtonWidth = Math.Ceiling(width/*width + polyArrow.Width+polyArrow.Margin.Right+Margin.Left*/);
|
||||
}
|
||||
|
||||
void SetButtonTemplates()
|
||||
{
|
||||
// set the button template (if specified)
|
||||
if (!string.IsNullOrEmpty(ButtonTemplateName))
|
||||
{
|
||||
bool foundResource = true;
|
||||
|
||||
try
|
||||
{
|
||||
FindResource(ButtonTemplateName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
foundResource = false;
|
||||
}
|
||||
|
||||
if (foundResource)
|
||||
{
|
||||
btnOK.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnYes.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnNo.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnCancel.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnAbort.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnRetry.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnIgnore.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FindDefaultButtonEx(MessageBoxButtonDefault buttonDefault)
|
||||
{
|
||||
// determine default button
|
||||
IsDefaultOK = false;
|
||||
IsDefaultCancel = false;
|
||||
IsDefaultYes = false;
|
||||
IsDefaultNo = false;
|
||||
IsDefaultAbort = false;
|
||||
IsDefaultRetry = false;
|
||||
IsDefaultIgnore = false;
|
||||
|
||||
if (buttonDefault != MessageBoxButtonDefault.None)
|
||||
{
|
||||
switch (ButtonsEx)
|
||||
{
|
||||
case MessageBoxButtonEx.OK:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButtonEx.OKCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.OK:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNoCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
break;
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button3:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNo:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.RetryCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Retry:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultRetry = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultRetry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.AbortRetryIgnore:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Abort:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultAbort = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Retry:
|
||||
IsDefaultRetry = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button3:
|
||||
case MessageBoxButtonDefault.Ignore:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultIgnore = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultAbort = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FindDefaultButton(MessageBoxButtonDefault buttonDefault)
|
||||
{
|
||||
// determine default button
|
||||
IsDefaultOK = false;
|
||||
IsDefaultCancel = false;
|
||||
IsDefaultYes = false;
|
||||
IsDefaultNo = false;
|
||||
IsDefaultAbort = false;
|
||||
IsDefaultRetry = false;
|
||||
IsDefaultIgnore = false;
|
||||
|
||||
if (buttonDefault != MessageBoxButtonDefault.None)
|
||||
{
|
||||
switch (Buttons)
|
||||
{
|
||||
case MessageBoxButton.OK:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButton.OKCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.OK:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
break;
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button3:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButton.YesNo:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion non-static methods
|
||||
|
||||
#region event handlers
|
||||
|
||||
#region buttons
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the OK button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnOK_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.OK;
|
||||
this.MessageResultEx = MessageBoxResultEx.OK;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the Yes button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnYes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.Yes;
|
||||
this.MessageResultEx = MessageBoxResultEx.Yes;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the No button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnNo_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.No;
|
||||
this.MessageResultEx = MessageBoxResultEx.No;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
private void BtnAbort_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.None;
|
||||
this.MessageResultEx = MessageBoxResultEx.Abort;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
private void BtnRetry_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.None;
|
||||
this.MessageResultEx = MessageBoxResultEx.Retry;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
private void BtnIgnore_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.None;
|
||||
this.MessageResultEx = MessageBoxResultEx.Ignore;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the Cancel button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.Cancel;
|
||||
this.MessageResultEx = MessageBoxResultEx.Cancel;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
#endregion buttons
|
||||
|
||||
void NotifiableWindow_SizeChanged(object sender, SizeChangedEventArgs e) => CenterInScreen();
|
||||
|
||||
void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
imgMsgBoxIcon.ToolTip = (msgBoxImage == MessageBoxImage.Error) ? MsgBoxIconToolTip : null;
|
||||
}
|
||||
|
||||
void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
DetailsText = null;
|
||||
CheckBoxData = null;
|
||||
staticButtonDefault = MessageBoxButtonDefault.Forms;
|
||||
|
||||
if (MessageResult == MessageBoxResult.None)
|
||||
{
|
||||
if (usingExButtons)
|
||||
{
|
||||
switch (ButtonsEx)
|
||||
{
|
||||
case MessageBoxButtonEx.OK:
|
||||
MessageResultEx = MessageBoxResultEx.OK;
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNoCancel:
|
||||
case MessageBoxButtonEx.OKCancel:
|
||||
case MessageBoxButtonEx.RetryCancel:
|
||||
case MessageBoxButtonEx.AbortRetryIgnore:
|
||||
MessageResultEx = MessageBoxResultEx.Cancel;
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNo:
|
||||
MessageResultEx = MessageBoxResultEx.No;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (Buttons)
|
||||
{
|
||||
case MessageBoxButton.OK:
|
||||
MessageResult = MessageBoxResult.OK;
|
||||
break;
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
case MessageBoxButton.OKCancel:
|
||||
MessageResult = MessageBoxResult.Cancel;
|
||||
break;
|
||||
case MessageBoxButton.YesNo:
|
||||
MessageResult = MessageBoxResult.No;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImgMsgBoxIcon_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (DelegateObj != null && msgBoxImage == MessageBoxImage.Error && Buttons == MessageBoxButton.OK)
|
||||
{
|
||||
DelegateObj.PerformAction(Message);
|
||||
|
||||
if (ExitAfterErrorAction)
|
||||
{
|
||||
MessageResult = MessageBoxResult.None;
|
||||
DialogResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TbUrl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => ProcessHelp.ShellExecute(Url?.ToString());
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
|
||||
|
||||
private const uint MF_BYCOMMAND = 0x00000000;
|
||||
private const uint MF_GRAYED = 0x00000001;
|
||||
private const uint SC_CLOSE = 0xF060;
|
||||
|
||||
void Window_SourceInitialized(object sender, EventArgs e)
|
||||
{
|
||||
if (!enableCloseButton)
|
||||
{
|
||||
var hWnd = new WindowInteropHelper(this);
|
||||
var sysMenu = GetSystemMenu(hWnd.Handle, false);
|
||||
EnableMenuItem(sysMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion event handlers
|
||||
}
|
||||
56
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExCheckBoxData.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public class MsgBoxExCheckBoxData : INotifyPropertyChanged
|
||||
{
|
||||
private bool isModified = false;
|
||||
|
||||
public bool IsModified {
|
||||
get => isModified;
|
||||
set {
|
||||
if (value != isModified)
|
||||
{
|
||||
isModified = true;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (PropertyChanged != null)
|
||||
{
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
if (propertyName != "IsModified")
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
private string? checkBoxText;
|
||||
private bool checkBoxIsChecked;
|
||||
|
||||
public string? CheckBoxText {
|
||||
get => checkBoxText;
|
||||
set {
|
||||
if (value != checkBoxText)
|
||||
checkBoxText = value; NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckBoxIsChecked {
|
||||
get => checkBoxIsChecked;
|
||||
set {
|
||||
if (value != checkBoxIsChecked)
|
||||
{
|
||||
checkBoxIsChecked = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExDelegate.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
using System.Windows;
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public abstract class MsgBoxExDelegate
|
||||
{
|
||||
public string? Message { get; set; }
|
||||
public string? Details { get; set; }
|
||||
public DateTime MessageDate { get; set; }
|
||||
|
||||
public virtual MessageBoxResult PerformAction(string message, string? details = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
215
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExStatic.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
|
||||
// https://www.codeproject.com/Articles/5290638/Customizable-WPF-MessageBox
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Drawing.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public partial class MessageBoxEx : Window, INotifyPropertyChanged
|
||||
{
|
||||
#region fields
|
||||
|
||||
private static double screenWidth = SystemParameters.WorkArea.Width - 100;
|
||||
|
||||
private static bool enableCloseButton = true;
|
||||
private static bool isSilent = false;
|
||||
private static List<string> installedFonts = new List<string>();
|
||||
public static MessageBoxButtonDefault staticButtonDefault;
|
||||
|
||||
#endregion fields
|
||||
|
||||
#region properties
|
||||
|
||||
public static Color DefaultUrlForegroundColor => Colors.Blue;
|
||||
|
||||
private static string? MsgBoxIconToolTip { get; set; }
|
||||
|
||||
protected static MsgBoxExDelegate? DelegateObj { get; set; }
|
||||
protected static bool ExitAfterErrorAction { get; set; }
|
||||
|
||||
public static ContentControl? ParentWindow { get; set; }
|
||||
|
||||
public static string? ButtonTemplateName { get; set; }
|
||||
|
||||
public static Brush? MessageBackground { get; set; }
|
||||
|
||||
public static Brush? MessageForeground { get; set; }
|
||||
|
||||
public static Brush? ButtonBackground { get; set; }
|
||||
|
||||
public static double MaxFormWidth { get; set; } = screenWidth;
|
||||
|
||||
public static Visibility ShowDetailsBtn { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public static string? DetailsText { get; set; }
|
||||
|
||||
public static Visibility ShowCheckBox { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public static MsgBoxExCheckBoxData? CheckBoxData { get; set; } = null;
|
||||
|
||||
public static FontFamily MsgFontFamily { get; set; } = new FontFamily("Segoe UI");
|
||||
|
||||
public static double MsgFontSize { get; set; } = 12;
|
||||
|
||||
public static Uri? Url { get; set; } = null;
|
||||
|
||||
public static Visibility ShowUrl { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public static string? UrlDisplayName { get; set; } = null;
|
||||
|
||||
public static SolidColorBrush UrlForeground { get; set; } = new SolidColorBrush(DefaultUrlForegroundColor);
|
||||
|
||||
public static string? DelegateToolTip { get; set; }
|
||||
|
||||
#endregion properties
|
||||
|
||||
#region methods
|
||||
|
||||
public static void SetFont(string familyName) => MsgFontFamily = new FontFamily(familyName);
|
||||
|
||||
public static MessageBoxResult OpenMessageBox(
|
||||
string msg, string title, MessageBoxButton buttons, MessageBoxImage image)
|
||||
{
|
||||
MessageBoxEx window = new MessageBoxEx(msg, title, buttons, image);
|
||||
SetOwner(window);
|
||||
window.ShowDialog();
|
||||
return window.MessageResult;
|
||||
}
|
||||
|
||||
public static MessageBoxResultEx OpenMessageBox(
|
||||
string msg,
|
||||
string title,
|
||||
MessageBoxButtonEx buttons,
|
||||
MessageBoxImage image)
|
||||
{
|
||||
MessageBoxEx window = new MessageBoxEx(msg, title, buttons, image);
|
||||
SetOwner(window);
|
||||
window.ShowDialog();
|
||||
return window.MessageResultEx;
|
||||
}
|
||||
|
||||
public static void SetOwner(Window window)
|
||||
{
|
||||
IntPtr ownerHandle = GetOwnerHandle();
|
||||
|
||||
if (ownerHandle != IntPtr.Zero)
|
||||
new WindowInteropHelper(window).Owner = ownerHandle;
|
||||
}
|
||||
|
||||
public static IntPtr GetOwnerHandle()
|
||||
{
|
||||
IntPtr foregroundWindow = GetForegroundWindow();
|
||||
GetWindowThreadProcessId(foregroundWindow, out var procID);
|
||||
|
||||
using (var proc = Process.GetCurrentProcess())
|
||||
if (proc.Id == procID)
|
||||
return foregroundWindow;
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
public static Color ColorFromString(string colorString)
|
||||
{
|
||||
Color wpfColor = Colors.Black;
|
||||
|
||||
try {
|
||||
wpfColor = (Color)ColorConverter.ConvertFromString(colorString);
|
||||
} catch (Exception) { }
|
||||
|
||||
return wpfColor;
|
||||
}
|
||||
|
||||
public static void SetFont()
|
||||
{
|
||||
MsgFontFamily = Application.Current.MainWindow.FontFamily;
|
||||
MsgFontSize = Application.Current.MainWindow.FontSize;
|
||||
}
|
||||
|
||||
public static void SetFont(ContentControl parent)
|
||||
{
|
||||
MsgFontFamily = parent.FontFamily;
|
||||
MsgFontSize = parent.FontSize;
|
||||
}
|
||||
|
||||
public static void SetFont(string familyName, double size)
|
||||
{
|
||||
if (!IsFontFamilyValid(familyName))
|
||||
if (!string.IsNullOrEmpty(familyName))
|
||||
MsgFontFamily = new FontFamily(familyName);
|
||||
MsgFontSize = Math.Max(1.0, size);
|
||||
}
|
||||
|
||||
private static bool IsFontFamilyValid(string name)
|
||||
{
|
||||
if (installedFonts.Count == 0)
|
||||
using (InstalledFontCollection fontsCollection = new InstalledFontCollection())
|
||||
installedFonts = (from x in fontsCollection.Families select x.Name).ToList();
|
||||
return installedFonts.Contains(name);
|
||||
}
|
||||
|
||||
public static void SetButtonTemplateName(string name)
|
||||
{
|
||||
ButtonTemplateName = name;
|
||||
}
|
||||
|
||||
public static void SetMaxFormWidth(double value)
|
||||
{
|
||||
MaxFormWidth = Math.Max(value, 300);
|
||||
double minWidth = 300;
|
||||
MaxFormWidth = Math.Max(minWidth, Math.Min(value, screenWidth));
|
||||
}
|
||||
|
||||
public static void ResetToDefaults()
|
||||
{
|
||||
MsgFontSize = 12d;
|
||||
MsgFontFamily = new FontFamily("Segoe UI");
|
||||
DelegateObj = null;
|
||||
DetailsText = null;
|
||||
MessageForeground = null;
|
||||
MessageBackground = null;
|
||||
ButtonBackground = null;
|
||||
ParentWindow = null;
|
||||
isSilent = false;
|
||||
enableCloseButton = true;
|
||||
ButtonTemplateName = null;
|
||||
MsgBoxIconToolTip = null;
|
||||
ShowCheckBox = Visibility.Collapsed;
|
||||
CheckBoxData = null;
|
||||
ExitAfterErrorAction = false;
|
||||
MaxFormWidth = 800;
|
||||
Url = null;
|
||||
ShowUrl = Visibility.Collapsed;
|
||||
UrlDisplayName = null;
|
||||
UrlForeground = new SolidColorBrush(DefaultUrlForegroundColor);
|
||||
staticButtonDefault = MessageBoxButtonDefault.Forms;
|
||||
}
|
||||
|
||||
public static void EnableCloseButton(bool enable)
|
||||
{
|
||||
enableCloseButton = enable;
|
||||
}
|
||||
|
||||
public static void SetAsSilent(bool quiet)
|
||||
{
|
||||
isSilent = quiet;
|
||||
}
|
||||
|
||||
public static void SetDefaultButton(MessageBoxButtonDefault buttonDefault)
|
||||
{
|
||||
staticButtonDefault = buttonDefault;
|
||||
}
|
||||
|
||||
#endregion methods
|
||||
}
|
||||
24
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExtendedFunctionality.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public class MsgBoxExtendedFunctionality
|
||||
{
|
||||
public MessageBoxButtonDefault ButtonDefault { get; set; }
|
||||
public string? DetailsText { get; set; }
|
||||
public MsgBoxExCheckBoxData? CheckBoxData { get; set; }
|
||||
public MsgBoxExDelegate? MessageDelegate { get; set; }
|
||||
public bool ExitAfterAction { get; set; }
|
||||
public string DelegateToolTip { get; set; }
|
||||
|
||||
public MsgBoxUrl? URL { get; set; }
|
||||
|
||||
public MsgBoxExtendedFunctionality()
|
||||
{
|
||||
ButtonDefault = MessageBoxButtonDefault.Forms;
|
||||
DetailsText = null;
|
||||
CheckBoxData = null;
|
||||
MessageDelegate = null;
|
||||
URL = null;
|
||||
DelegateToolTip = "Click this icon for additional info/actions.";
|
||||
}
|
||||
}
|
||||
13
src/MpvNet.Windows/WPF/MsgBox/MsgBoxUrl.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public class MsgBoxUrl
|
||||
{
|
||||
public Uri? URL { get; set; }
|
||||
public string? DisplayName { get; set; }
|
||||
public Color Foreground { get; set; }
|
||||
|
||||
public MsgBoxUrl() => Foreground = MessageBoxEx.DefaultUrlForegroundColor;
|
||||
}
|
||||
15
src/MpvNet.Windows/WPF/MsgBox/MsgEnum.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public enum MessageBoxButtonEx { OK = 0, OKCancel, AbortRetryIgnore, YesNoCancel, YesNo, RetryCancel }
|
||||
|
||||
public enum MessageBoxResultEx { None = 0, OK, Cancel, Abort, Retry, Ignore, Yes, No }
|
||||
|
||||
public enum MessageBoxButtonDefault
|
||||
{
|
||||
OK, Cancel, Yes, No, Abort, Retry, Ignore, // specific button
|
||||
Button1, Button2, Button3, // button by ordinal left-to-right position
|
||||
MostPositive, LeastPositive, // button by positivity
|
||||
Forms, // button according to the Windows.Forms standard messagebox
|
||||
None // no default button
|
||||
}
|
||||
1034
src/MpvNet.Windows/WPF/Resources.xaml
Normal file
14
src/MpvNet.Windows/WPF/ViewModels/AboutViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace MpvNet.Windows.WPF.ViewModels;
|
||||
|
||||
public partial class AboutViewModel : ViewModelBase
|
||||
{
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
public string About { get; } = AppClass.About;
|
||||
|
||||
[RelayCommand]
|
||||
public void Close() => CloseAction!();
|
||||
}
|
||||
76
src/MpvNet.Windows/WPF/ViewModels/NodeViewModel.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF.ViewModels;
|
||||
|
||||
public class NodeViewModel : ObservableObject
|
||||
{
|
||||
readonly List<NodeViewModel> _children;
|
||||
readonly NodeViewModel? _parent;
|
||||
readonly TreeNode _node;
|
||||
|
||||
bool _isExpanded;
|
||||
bool _isSelected;
|
||||
|
||||
public NodeViewModel(TreeNode node) : this(node, null)
|
||||
{
|
||||
}
|
||||
|
||||
public NodeViewModel(TreeNode node, NodeViewModel? parent)
|
||||
{
|
||||
_node = node;
|
||||
_parent = parent;
|
||||
|
||||
_children = new List<NodeViewModel>(
|
||||
_node.Children.Select(i => new NodeViewModel(i, this)).ToList());
|
||||
}
|
||||
|
||||
public List<NodeViewModel> Children => _children;
|
||||
|
||||
public string Name => _node.Name;
|
||||
|
||||
public string Path {
|
||||
get {
|
||||
string path = Name;
|
||||
NodeViewModel? parent = Parent;
|
||||
|
||||
while (!string.IsNullOrEmpty(parent?.Name))
|
||||
{
|
||||
path = parent.Name + "/" + path;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public NodeViewModel? Parent => _parent;
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _isExpanded, value);
|
||||
|
||||
if (_isExpanded && _parent != null)
|
||||
_parent.IsExpanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public bool NameContains(string text)
|
||||
{
|
||||
if (text == "")
|
||||
return false;
|
||||
|
||||
return Name.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1;
|
||||
}
|
||||
}
|
||||
11
src/MpvNet.Windows/WPF/ViewModels/ViewModelBase.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF.ViewModels;
|
||||
|
||||
public class ViewModelBase : ObservableObject
|
||||
{
|
||||
public Theme Theme => Theme.Current!;
|
||||
}
|
||||
46
src/MpvNet.Windows/WPF/Views/AboutWindow.xaml
Normal file
@@ -0,0 +1,46 @@
|
||||
<Window
|
||||
x:Class="MpvNet.Windows.WPF.Views.AboutWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="About mpv.net"
|
||||
FontSize="16"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
>
|
||||
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="Esc" Command="{Binding CloseCommand}"/>
|
||||
</Window.InputBindings>
|
||||
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBox
|
||||
IsReadOnly="True"
|
||||
FontSize="30"
|
||||
TextAlignment="Center"
|
||||
Margin="10,10,10,0"
|
||||
BorderThickness="0"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
>mpv.net
|
||||
</TextBox>
|
||||
<TextBox
|
||||
IsReadOnly="True"
|
||||
Text="{Binding About, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
Margin="20,10,20,20"
|
||||
BorderThickness="0"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}">
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
16
src/MpvNet.Windows/WPF/Views/AboutWindow.xaml.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
using MpvNet.Windows.WPF.ViewModels;
|
||||
|
||||
namespace MpvNet.Windows.WPF.Views;
|
||||
|
||||
public partial class AboutWindow
|
||||
{
|
||||
|
||||
public AboutWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
var vm = new AboutViewModel();
|
||||
DataContext = vm;
|
||||
vm.CloseAction = Close;
|
||||
}
|
||||
}
|
||||
20
src/MpvNet.Windows/WPF/WpfApplication.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
using System.Windows;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public class WpfApplication
|
||||
{
|
||||
public static void Init()
|
||||
{
|
||||
new Application();
|
||||
|
||||
Application.Current!.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
||||
|
||||
Application.Current!.DispatcherUnhandledException += (sender, e) => Terminal.WriteError(e.Exception);
|
||||
|
||||
Application.Current?.Resources.MergedDictionaries.Add(
|
||||
Application.LoadComponent(new Uri("mpvnet;component/WPF/Resources.xaml",
|
||||
UriKind.Relative)) as ResourceDictionary);
|
||||
}
|
||||
}
|
||||
68
src/MpvNet.Windows/WinForms/MainForm.Designer.cs
generated
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
namespace MpvNet.Windows.WinForms;
|
||||
|
||||
partial class MainForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||
this.CursorTimer = new System.Windows.Forms.Timer(this.components);
|
||||
this.ProgressTimer = new System.Windows.Forms.Timer(this.components);
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// CursorTimer
|
||||
//
|
||||
this.CursorTimer.Enabled = true;
|
||||
this.CursorTimer.Interval = 1000;
|
||||
this.CursorTimer.Tick += new System.EventHandler(this.CursorTimer_Tick);
|
||||
//
|
||||
// ProgressTimer
|
||||
//
|
||||
this.ProgressTimer.Tick += new System.EventHandler(this.ProgressTimer_Tick);
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
this.AllowDrop = true;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(288F, 288F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
this.BackColor = System.Drawing.Color.Black;
|
||||
this.ClientSize = new System.Drawing.Size(857, 444);
|
||||
this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.Name = "MainForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Timer CursorTimer;
|
||||
private System.Windows.Forms.Timer ProgressTimer;
|
||||
}
|
||||
1433
src/MpvNet.Windows/WinForms/MainForm.cs
Normal file
4577
src/MpvNet.Windows/WinForms/MainForm.resx
Normal file
80
src/MpvNet.Windows/WinForms/SnapManager.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
using MpvNet.Windows.Native;
|
||||
|
||||
using static MpvNet.Windows.Native.WinApi;
|
||||
|
||||
namespace MpvNet.Windows.WinForms;
|
||||
|
||||
public class SnapManager
|
||||
{
|
||||
int DragOffsetX { get; set; }
|
||||
int DragOffsetY { get; set; }
|
||||
|
||||
IntPtr Handle;
|
||||
|
||||
[Flags]
|
||||
public enum SnapLocation
|
||||
{
|
||||
None = 0,
|
||||
Left = 1 << 0,
|
||||
Top = 1 << 1,
|
||||
Right = 1 << 2,
|
||||
Bottom = 1 << 3,
|
||||
All = Left | Top | Right | Bottom
|
||||
}
|
||||
|
||||
public int AnchorDistance { get; set; }
|
||||
|
||||
public int SnapDistance { get; set; }
|
||||
|
||||
bool InSnapRange(int a, int b) => Math.Abs(a - b) < SnapDistance;
|
||||
|
||||
void FindSnap(ref Rectangle effectiveBounds)
|
||||
{
|
||||
Screen currentScreen = Screen.FromPoint(effectiveBounds.Location);
|
||||
Rectangle workingArea = GetWorkingArea(Handle, currentScreen.WorkingArea);
|
||||
|
||||
if (InSnapRange(effectiveBounds.Left, workingArea.Left + AnchorDistance))
|
||||
effectiveBounds.X = workingArea.Left + AnchorDistance;
|
||||
else if (InSnapRange(effectiveBounds.Right, workingArea.Right - AnchorDistance))
|
||||
effectiveBounds.X = workingArea.Right - AnchorDistance - effectiveBounds.Width;
|
||||
if (InSnapRange(effectiveBounds.Top, workingArea.Top + AnchorDistance))
|
||||
effectiveBounds.Y = workingArea.Top + AnchorDistance;
|
||||
else if (InSnapRange(effectiveBounds.Bottom, workingArea.Bottom - AnchorDistance))
|
||||
effectiveBounds.Y = workingArea.Bottom - AnchorDistance - effectiveBounds.Height;
|
||||
}
|
||||
|
||||
public void OnMoving(ref Message m)
|
||||
{
|
||||
if (Handle == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
WinApi.Rect boundsLtrb = Marshal.PtrToStructure<WinApi.Rect>(m.LParam);
|
||||
Rectangle bounds = boundsLtrb.ToRectangle();
|
||||
// This is where the window _would_ be located if snapping
|
||||
// had not occurred. This prevents the cursor from sliding
|
||||
// off the title bar if the snap distance is too large.
|
||||
Rectangle effectiveBounds = new Rectangle(
|
||||
Cursor.Position.X - DragOffsetX,
|
||||
Cursor.Position.Y - DragOffsetY,
|
||||
bounds.Width,
|
||||
bounds.Height);
|
||||
FindSnap(ref effectiveBounds);
|
||||
WinApi.Rect newLtrb = WinApi.Rect.FromRectangle(effectiveBounds);
|
||||
Marshal.StructureToPtr(newLtrb, m.LParam, false);
|
||||
m.Result = new IntPtr(1);
|
||||
}
|
||||
|
||||
public void OnSizeAndEnterSizeMove(Form form)
|
||||
{
|
||||
Handle = form.Handle;
|
||||
SnapDistance = form.Font.Height;
|
||||
// Need to handle window size changed as well when
|
||||
// un-maximizing the form by dragging the title bar.
|
||||
DragOffsetX = Cursor.Position.X - form.Left;
|
||||
DragOffsetY = Cursor.Position.Y - form.Top;
|
||||
}
|
||||
}
|
||||
46
src/MpvNet.Windows/app.manifest
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly
|
||||
manifestVersion="1.0"
|
||||
xmlns="urn:schemas-microsoft-com:asm.v1"
|
||||
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
<application>
|
||||
<windowsSettings>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
BIN
src/MpvNet.Windows/mpv-icon.ico
Normal file
|
After Width: | Height: | Size: 264 KiB |
4
src/MpvNet.Windows/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.PowerShell.5.ReferenceAssemblies" version="1.1.0" targetFramework="net472" />
|
||||
</packages>
|
||||