Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PTRun][OneNote] Improve the OneNote plugin #36813

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions .github/actions/spell-check/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1057,9 +1057,13 @@ NULLCURSOR
nullonfailure
numberbox
nwc
Objbase
objidl
ocid
ocr
Ocrsettings
odbccp
Odotocodot
OEMCONVERT
officehubintl
OFN
Expand Down Expand Up @@ -1126,6 +1130,7 @@ PDEVMODE
pdisp
PDLL
pdo
pdpshare
pdto
pdtobj
pdw
Expand Down Expand Up @@ -1878,6 +1883,7 @@ XDocument
XElement
xfd
XFile
XIn
XIncrement
XNamespace
Xoshiro
Expand Down
11 changes: 5 additions & 6 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,16 @@
<PackageVersion Include="ControlzEx" Version="6.0.0" />
<PackageVersion Include="HelixToolkit" Version="2.24.0" />
<PackageVersion Include="HelixToolkit.Core.Wpf" Version="2.24.0" />
<PackageVersion Include="Humanizer" Version="2.14.1" />
<PackageVersion Include="hyjiacan.pinyin4net" Version="4.1.1" />
<PackageVersion Include="Interop.Microsoft.Office.Interop.OneNote" Version="1.1.0.2" />
<PackageVersion Include="LazyCache" Version="2.4.0" />
<PackageVersion Include="Mages" Version="3.0.0" />
<PackageVersion Include="Markdig.Signed" Version="0.34.0" />
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageVersion Include="MessagePack" Version="2.5.187" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.1" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.1" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.1" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.1" />
Expand All @@ -47,7 +46,7 @@
<!--
TODO: in Common.Dotnet.CsWinRT.props, on upgrade, verify RemoveCsWinRTPackageAnalyzer is no longer needed.
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
-->
-->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.1.5" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.6.241114003" />
Expand All @@ -60,9 +59,9 @@
<PackageVersion Include="NLog" Version="5.0.4" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.8" />
<PackageVersion Include="NLog.Schema" Version="5.2.8" />
<PackageVersion Include="Odotocodot.OneNote.Linq" Version="1.1.0" />
<PackageVersion Include="OpenAI" Version="2.0.0" />
<PackageVersion Include="ReverseMarkdown" Version="4.1.0" />
<PackageVersion Include="ScipBe.Common.Office.OneNote" Version="3.0.1" />
<PackageVersion Include="SharpCompress" Version="0.37.2" />
<PackageVersion Include="StreamJsonRpc" Version="2.19.27" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
Expand Down Expand Up @@ -98,4 +97,4 @@
<PackageVersion Include="Microsoft.VariantAssignment.Client" Version="2.4.17140001" />
<PackageVersion Include="Microsoft.VariantAssignment.Contract" Version="3.0.16990001" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Media.Imaging;
using ManagedCommon;
using Odotocodot.OneNote.Linq;
using Wox.Infrastructure.Image;
using Wox.Plugin;
using Wox.Plugin.Logger;

namespace Microsoft.PowerToys.Run.Plugin.OneNote.Components
{
public class IconProvider
{
private readonly PluginInitContext _context;
private readonly OneNoteSettings _settings;
private readonly string _imagesDirectory;
private readonly string _generatedImagesDirectory;
private readonly ConcurrentDictionary<string, BitmapSource> _imageCache = new();

private bool _deleteColoredIconsOnCleanup;

private string _powerToysTheme;
private string _pluginTheme;

internal string NewPage => $"Images/page_new.{_pluginTheme}.png";

internal string NewSection => $"Images/section_new.{_pluginTheme}.png";

internal string NewSectionGroup => $"Images/section_group_new.{_pluginTheme}.png";

internal string NewNotebook => $"Images/notebook_new.{_pluginTheme}.png";

internal string Page => $"Images/page.{_pluginTheme}.png";

internal string Recent => $"Images/page_recent.{_pluginTheme}.png";

internal string Sync => $"Images/sync.{_pluginTheme}.png";

internal string Search => $"Images/search.{_pluginTheme}.png";

internal string NotebookExplorer => $"Images/notebook_explorer.{_pluginTheme}.png";

internal string Warning => $"Images/warning.{_powerToysTheme}.png";

internal string QuickNote => NewPage;

internal IconProvider(PluginInitContext context, OneNoteSettings settings)
{
_settings = settings;
_context = context;
_settings.ColoredIconsSettingChanged += OnColoredIconsSettingChanged;
_context.API.ThemeChanged += OnThemeChanged;

_imagesDirectory = $"{_context.CurrentPluginMetadata.PluginDirectory}/Images/";
_generatedImagesDirectory = $"{_context.CurrentPluginMetadata.PluginDirectory}/Images/Generated/";

if (!Directory.Exists(_generatedImagesDirectory))
{
Directory.CreateDirectory(_generatedImagesDirectory);
}

foreach (var imagePath in Directory.EnumerateFiles(_generatedImagesDirectory))
{
_imageCache.TryAdd(Path.GetFileNameWithoutExtension(imagePath), Path2Bitmap(imagePath));
}

UpdatePowerToysTheme(_context.API.GetCurrentTheme());
}

private void OnColoredIconsSettingChanged()
{
_deleteColoredIconsOnCleanup = !_settings.ColoredIcons;
UpdatePluginTheme();
}

private void OnThemeChanged(Theme oldTheme, Theme newTheme) => UpdatePowerToysTheme(newTheme);

[MemberNotNull(nameof(_powerToysTheme))]
[MemberNotNull(nameof(_pluginTheme))]
private void UpdatePowerToysTheme(Theme theme)
{
_powerToysTheme = theme == Theme.Light || theme == Theme.HighContrastWhite ? "light" : "dark";
UpdatePluginTheme();
}

[MemberNotNull(nameof(_pluginTheme))]
private void UpdatePluginTheme() => _pluginTheme = _settings.ColoredIcons ? "color" : _powerToysTheme;

private BitmapSource GetColoredIcon(string itemType, Color itemColor)
{
return _imageCache.GetOrAdd($"{itemType}.{itemColor.ToArgb()}", key =>
{
var color = itemColor;
using var bitmap = new Bitmap($"{_imagesDirectory}{itemType}.dark.png");
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);

int bytesPerPixel = Image.GetPixelFormatSize(bitmap.PixelFormat) / 8;
byte[] pixels = new byte[bitmapData.Stride * bitmap.Height];
IntPtr pointer = bitmapData.Scan0;
Marshal.Copy(pointer, pixels, 0, pixels.Length);
int bytesWidth = bitmapData.Width * bytesPerPixel;

for (int j = 0; j < bitmapData.Height; j++)
{
int line = j * bitmapData.Stride;
for (int i = 0; i < bytesWidth; i += bytesPerPixel)
{
pixels[line + i] = color.B;
pixels[line + i + 1] = color.G;
pixels[line + i + 2] = color.R;
}
}

Marshal.Copy(pixels, 0, pointer, pixels.Length);
bitmap.UnlockBits(bitmapData);

var filePath = $"{_generatedImagesDirectory}{key}.png";
bitmap.Save(filePath, ImageFormat.Png);
return Path2Bitmap(filePath);
});
}

private BitmapSource Path2Bitmap(string path) => WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, Constant.ThumbnailSize, ThumbnailOptions.ThumbnailOnly);

internal System.Windows.Media.ImageSource GetIcon(IOneNoteItem item)
{
string key;
switch (item)
{
case OneNoteNotebook notebook:
if (!_settings.ColoredIcons || notebook.Color is null)
{
key = $"{nameof(notebook)}.{_powerToysTheme}";
break;
}
else
{
return GetColoredIcon(nameof(notebook), notebook.Color.Value);
}

case OneNoteSectionGroup sectionGroup:
key = $"{(sectionGroup.IsRecycleBin ? $"recycle_bin" : $"section_group")}.{_pluginTheme}";
break;

case OneNoteSection section:
if (!_settings.ColoredIcons || section.Color is null)
{
key = $"{nameof(section)}.{_powerToysTheme}";
break;
}
else
{
return GetColoredIcon(nameof(section), section.Color.Value);
}

case OneNotePage:
key = Path.GetFileNameWithoutExtension(Page);
break;

default:
throw new NotImplementedException();
}

return _imageCache.GetOrAdd(key, key => Path2Bitmap($"{_imagesDirectory}{key}.png"));
}

internal void Cleanup()
{
_imageCache.Clear();
if (_deleteColoredIconsOnCleanup)
{
foreach (var file in new DirectoryInfo(_generatedImagesDirectory).EnumerateFiles())
{
try
{
file.Delete();
}
catch (Exception ex) when (ex is DirectoryNotFoundException || ex is IOException)
{
Log.Error($"Failed to delete icon at \"{file}\"", GetType());
}
}
}

if (_settings != null)
{
_settings.ColoredIconsSettingChanged -= OnColoredIconsSettingChanged;
}

if (_context != null && _context.API != null)
{
_context.API.ThemeChanged -= OnThemeChanged;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Microsoft.PowerToys.Run.Plugin.OneNote.Components
{
public class Keywords
{
internal const string NotebookExplorerSeparator = @"\";

internal const string NotebookExplorer = $"nb:{NotebookExplorerSeparator}";

internal const string RecentPages = "rp:";

internal const string ScopedSearch = ">";

internal const string TitleSearch = "*";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Runtime.InteropServices;
using Odotocodot.OneNote.Linq;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;

namespace Microsoft.PowerToys.Run.Plugin.OneNote.Components
{
internal static class OneNoteItemExtensions
{
internal static bool OpenItemInOneNote(this IOneNoteItem item)
{
try
{
item.OpenInOneNote();
ShowOneNote();
return true;
}
catch (COMException)
{
// The page, section or even notebook may no longer exist, ignore and do nothing.
return false;
}
}

/// <summary>
/// Brings OneNote to the foreground and restores it if minimized.
/// </summary>
internal static void ShowOneNote()
{
using var process = Process.GetProcessesByName("onenote").FirstOrDefault();
if (process?.MainWindowHandle != null)
{
HWND handle = (HWND)process.MainWindowHandle;
if (PInvoke.IsIconic(handle))
{
PInvoke.ShowWindow(handle, SHOW_WINDOW_CMD.SW_RESTORE);
}

PInvoke.SetForegroundWindow(handle);
}
}
}
}
Loading
Loading