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 15 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
Pull files from old branch
  • Loading branch information
Odotocodot committed Jan 18, 2025
commit 5f948c9eb22f174cc782ebab675f56428cec5cbd
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>
private 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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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.Windows.Controls;
using Microsoft.PowerToys.Settings.UI.Library;
using Wox.Plugin;

namespace Microsoft.PowerToys.Run.Plugin.OneNote.Components
{
public class OneNoteSettings : ISettingProvider
{
private bool coloredIcons;

internal bool ShowUnreadItems { get; private set; }

internal bool ShowEncryptedSections { get; private set; }

internal bool ShowRecycleBins { get; private set; }

// A timeout value is required as there currently no way to know if the Run window is visible.
internal double ComObjectTimeout { get; private set; }

internal bool ColoredIcons
{
get => coloredIcons;
private set
{
coloredIcons = value;
ColoredIconsSettingChanged?.Invoke();
}
}

internal event Action? ColoredIconsSettingChanged;

public IEnumerable<PluginAdditionalOption> AdditionalOptions => new List<PluginAdditionalOption>()
{
new PluginAdditionalOption()
{
Key = nameof(ShowUnreadItems),
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Checkbox,
DisplayLabel = string.Empty,
DisplayDescription = string.Empty,
Value = true,
},
new PluginAdditionalOption()
{
Key = nameof(ShowEncryptedSections),
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Checkbox,
DisplayLabel = string.Empty,
DisplayDescription = string.Empty,
Value = true,
},
new PluginAdditionalOption()
{
Key = nameof(ShowRecycleBins),
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Checkbox,
DisplayLabel = string.Empty,
DisplayDescription = string.Empty,
Value = true,
},
new PluginAdditionalOption()
{
Key = nameof(ComObjectTimeout),
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Numberbox,
DisplayLabel = "Test",
DisplayDescription = "Test",
NumberValue = 10000,
NumberBoxMin = 1000,
NumberBoxMax = 120000,
NumberBoxSmallChange = 1000,
NumberBoxLargeChange = 50000,
},
new PluginAdditionalOption()
{
Key = nameof(ColoredIcons),
PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Checkbox,
DisplayLabel = string.Empty,
DisplayDescription = string.Empty,
Value = true,
},
};

public Control CreateSettingPanel()
{
throw new NotImplementedException();
}

public void UpdateSettings(PowerLauncherPluginSettings? settings)
{
if (settings?.AdditionalOptions == null)
{
return;
}

ShowUnreadItems = GetBoolSettingOrDefault(settings, nameof(ShowUnreadItems));
ShowEncryptedSections = GetBoolSettingOrDefault(settings, nameof(ShowEncryptedSections));
ShowRecycleBins = GetBoolSettingOrDefault(settings, nameof(ShowRecycleBins));

var comObjectTimeout = settings.AdditionalOptions.FirstOrDefault(x => x.Key == nameof(ComObjectTimeout))?.NumberValue;
ComObjectTimeout = comObjectTimeout ?? AdditionalOptions.First(x => x.Key == nameof(ComObjectTimeout)).NumberValue;

ColoredIcons = GetBoolSettingOrDefault(settings, nameof(ColoredIcons));
}

private bool GetBoolSettingOrDefault(PowerLauncherPluginSettings settings, string settingName)
{
var value = settings.AdditionalOptions.FirstOrDefault(x => x.Key == settingName)?.Value;
return value ?? AdditionalOptions.First(x => x.Key == settingName).Value;
}
}
}
Loading
Oops, something went wrong.