mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
whitespace forced changes (#6002)
This commit is contained in:
@@ -1,70 +1,70 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using FancyZonesEditor.Models;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace FancyZonesEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public Settings ZoneSettings { get; }
|
||||
|
||||
public App()
|
||||
{
|
||||
ZoneSettings = new Settings();
|
||||
}
|
||||
|
||||
private void OnStartup(object sender, StartupEventArgs e)
|
||||
{
|
||||
RunnerHelper.WaitForPowerToysRunner(Settings.PowerToysPID, () =>
|
||||
{
|
||||
Environment.Exit(0);
|
||||
});
|
||||
|
||||
LayoutModel foundModel = null;
|
||||
|
||||
foreach (LayoutModel model in ZoneSettings.DefaultModels)
|
||||
{
|
||||
if (model.Type == Settings.ActiveZoneSetLayoutType)
|
||||
{
|
||||
// found match
|
||||
foundModel = model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundModel == null)
|
||||
{
|
||||
foreach (LayoutModel model in Settings.CustomModels)
|
||||
{
|
||||
if ("{" + model.Guid.ToString().ToUpper() + "}" == Settings.ActiveZoneSetUUid.ToUpper())
|
||||
{
|
||||
// found match
|
||||
foundModel = model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundModel == null)
|
||||
{
|
||||
foundModel = ZoneSettings.DefaultModels[0];
|
||||
}
|
||||
|
||||
foundModel.IsSelected = true;
|
||||
|
||||
EditorOverlay overlay = new EditorOverlay();
|
||||
overlay.Show();
|
||||
overlay.DataContext = foundModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using FancyZonesEditor.Models;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace FancyZonesEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public Settings ZoneSettings { get; }
|
||||
|
||||
public App()
|
||||
{
|
||||
ZoneSettings = new Settings();
|
||||
}
|
||||
|
||||
private void OnStartup(object sender, StartupEventArgs e)
|
||||
{
|
||||
RunnerHelper.WaitForPowerToysRunner(Settings.PowerToysPID, () =>
|
||||
{
|
||||
Environment.Exit(0);
|
||||
});
|
||||
|
||||
LayoutModel foundModel = null;
|
||||
|
||||
foreach (LayoutModel model in ZoneSettings.DefaultModels)
|
||||
{
|
||||
if (model.Type == Settings.ActiveZoneSetLayoutType)
|
||||
{
|
||||
// found match
|
||||
foundModel = model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundModel == null)
|
||||
{
|
||||
foreach (LayoutModel model in Settings.CustomModels)
|
||||
{
|
||||
if ("{" + model.Guid.ToString().ToUpper() + "}" == Settings.ActiveZoneSetUUid.ToUpper())
|
||||
{
|
||||
// found match
|
||||
foundModel = model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundModel == null)
|
||||
{
|
||||
foundModel = ZoneSettings.DefaultModels[0];
|
||||
}
|
||||
|
||||
foundModel.IsSelected = true;
|
||||
|
||||
EditorOverlay overlay = new EditorOverlay();
|
||||
overlay.Show();
|
||||
overlay.DataContext = foundModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,135 +1,135 @@
|
||||
// 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;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using FancyZonesEditor.Models;
|
||||
|
||||
namespace FancyZonesEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for EditorOverlay.xaml
|
||||
/// </summary>
|
||||
public partial class EditorOverlay : Window
|
||||
{
|
||||
public static EditorOverlay Current { get; set; }
|
||||
|
||||
private readonly Settings _settings = ((App)Application.Current).ZoneSettings;
|
||||
private LayoutPreview _layoutPreview;
|
||||
|
||||
private UserControl _editor;
|
||||
|
||||
private static MainWindow _mainWindow = new MainWindow();
|
||||
|
||||
public Int32Rect[] GetZoneRects()
|
||||
{
|
||||
if (_editor != null)
|
||||
{
|
||||
if (_editor is GridEditor gridEditor)
|
||||
{
|
||||
return ZoneRectsFromPanel(gridEditor.PreviewPanel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// CanvasEditor
|
||||
return ZoneRectsFromPanel(((CanvasEditor)_editor).Preview);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// One of the predefined zones (neither grid or canvas editor used).
|
||||
return _layoutPreview.GetZoneRects();
|
||||
}
|
||||
}
|
||||
|
||||
private Int32Rect[] ZoneRectsFromPanel(Panel previewPanel)
|
||||
{
|
||||
// TODO: the ideal here is that the ArrangeRects logic is entirely inside the model, so we don't have to walk the UIElement children to get the rect info
|
||||
int count = previewPanel.Children.Count;
|
||||
Int32Rect[] zones = new Int32Rect[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
FrameworkElement child = (FrameworkElement)previewPanel.Children[i];
|
||||
Point topLeft = child.TransformToAncestor(previewPanel).Transform(default);
|
||||
|
||||
zones[i].X = (int)topLeft.X;
|
||||
zones[i].Y = (int)topLeft.Y;
|
||||
zones[i].Width = (int)child.ActualWidth;
|
||||
zones[i].Height = (int)child.ActualHeight;
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
public EditorOverlay()
|
||||
{
|
||||
InitializeComponent();
|
||||
Current = this;
|
||||
|
||||
Left = Settings.WorkArea.Left;
|
||||
Top = Settings.WorkArea.Top;
|
||||
Width = Settings.WorkArea.Width;
|
||||
Height = Settings.WorkArea.Height;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ShowLayoutPicker();
|
||||
}
|
||||
|
||||
public void ShowLayoutPicker()
|
||||
{
|
||||
_editor = null;
|
||||
_layoutPreview = new LayoutPreview
|
||||
{
|
||||
IsActualSize = true,
|
||||
Opacity = 0.5,
|
||||
};
|
||||
|
||||
Content = _layoutPreview;
|
||||
|
||||
_mainWindow.Owner = this;
|
||||
_mainWindow.ShowActivated = true;
|
||||
_mainWindow.Topmost = true;
|
||||
_mainWindow.Show();
|
||||
|
||||
// window is set to topmost to make sure it shows on top of PowerToys settings page
|
||||
// we can reset topmost flag now
|
||||
_mainWindow.Topmost = false;
|
||||
}
|
||||
|
||||
// These event handlers are used to track the current state of the Shift and Ctrl keys on the keyboard
|
||||
// They reflect that current state into properties on the Settings object, which the Zone view will listen to in editing mode
|
||||
protected override void OnPreviewKeyDown(KeyEventArgs e)
|
||||
{
|
||||
_settings.IsShiftKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift);
|
||||
_settings.IsCtrlKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Control);
|
||||
base.OnPreviewKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void OnPreviewKeyUp(KeyEventArgs e)
|
||||
{
|
||||
_settings.IsShiftKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift);
|
||||
_settings.IsCtrlKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Control);
|
||||
base.OnPreviewKeyUp(e);
|
||||
}
|
||||
|
||||
public void Edit()
|
||||
{
|
||||
_layoutPreview = null;
|
||||
if (DataContext is GridLayoutModel)
|
||||
{
|
||||
_editor = new GridEditor();
|
||||
}
|
||||
else if (DataContext is CanvasLayoutModel)
|
||||
{
|
||||
_editor = new CanvasEditor();
|
||||
}
|
||||
|
||||
Content = _editor;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using FancyZonesEditor.Models;
|
||||
|
||||
namespace FancyZonesEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for EditorOverlay.xaml
|
||||
/// </summary>
|
||||
public partial class EditorOverlay : Window
|
||||
{
|
||||
public static EditorOverlay Current { get; set; }
|
||||
|
||||
private readonly Settings _settings = ((App)Application.Current).ZoneSettings;
|
||||
private LayoutPreview _layoutPreview;
|
||||
|
||||
private UserControl _editor;
|
||||
|
||||
private static MainWindow _mainWindow = new MainWindow();
|
||||
|
||||
public Int32Rect[] GetZoneRects()
|
||||
{
|
||||
if (_editor != null)
|
||||
{
|
||||
if (_editor is GridEditor gridEditor)
|
||||
{
|
||||
return ZoneRectsFromPanel(gridEditor.PreviewPanel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// CanvasEditor
|
||||
return ZoneRectsFromPanel(((CanvasEditor)_editor).Preview);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// One of the predefined zones (neither grid or canvas editor used).
|
||||
return _layoutPreview.GetZoneRects();
|
||||
}
|
||||
}
|
||||
|
||||
private Int32Rect[] ZoneRectsFromPanel(Panel previewPanel)
|
||||
{
|
||||
// TODO: the ideal here is that the ArrangeRects logic is entirely inside the model, so we don't have to walk the UIElement children to get the rect info
|
||||
int count = previewPanel.Children.Count;
|
||||
Int32Rect[] zones = new Int32Rect[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
FrameworkElement child = (FrameworkElement)previewPanel.Children[i];
|
||||
Point topLeft = child.TransformToAncestor(previewPanel).Transform(default);
|
||||
|
||||
zones[i].X = (int)topLeft.X;
|
||||
zones[i].Y = (int)topLeft.Y;
|
||||
zones[i].Width = (int)child.ActualWidth;
|
||||
zones[i].Height = (int)child.ActualHeight;
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
public EditorOverlay()
|
||||
{
|
||||
InitializeComponent();
|
||||
Current = this;
|
||||
|
||||
Left = Settings.WorkArea.Left;
|
||||
Top = Settings.WorkArea.Top;
|
||||
Width = Settings.WorkArea.Width;
|
||||
Height = Settings.WorkArea.Height;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ShowLayoutPicker();
|
||||
}
|
||||
|
||||
public void ShowLayoutPicker()
|
||||
{
|
||||
_editor = null;
|
||||
_layoutPreview = new LayoutPreview
|
||||
{
|
||||
IsActualSize = true,
|
||||
Opacity = 0.5,
|
||||
};
|
||||
|
||||
Content = _layoutPreview;
|
||||
|
||||
_mainWindow.Owner = this;
|
||||
_mainWindow.ShowActivated = true;
|
||||
_mainWindow.Topmost = true;
|
||||
_mainWindow.Show();
|
||||
|
||||
// window is set to topmost to make sure it shows on top of PowerToys settings page
|
||||
// we can reset topmost flag now
|
||||
_mainWindow.Topmost = false;
|
||||
}
|
||||
|
||||
// These event handlers are used to track the current state of the Shift and Ctrl keys on the keyboard
|
||||
// They reflect that current state into properties on the Settings object, which the Zone view will listen to in editing mode
|
||||
protected override void OnPreviewKeyDown(KeyEventArgs e)
|
||||
{
|
||||
_settings.IsShiftKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift);
|
||||
_settings.IsCtrlKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Control);
|
||||
base.OnPreviewKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void OnPreviewKeyUp(KeyEventArgs e)
|
||||
{
|
||||
_settings.IsShiftKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift);
|
||||
_settings.IsCtrlKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Control);
|
||||
base.OnPreviewKeyUp(e);
|
||||
}
|
||||
|
||||
public void Edit()
|
||||
{
|
||||
_layoutPreview = null;
|
||||
if (DataContext is GridLayoutModel)
|
||||
{
|
||||
_editor = new GridEditor();
|
||||
}
|
||||
else if (DataContext is CanvasLayoutModel)
|
||||
{
|
||||
_editor = new CanvasEditor();
|
||||
}
|
||||
|
||||
Content = _editor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -854,41 +854,41 @@ namespace FancyZonesEditor
|
||||
CollapseIndices();
|
||||
FixAccuracyError(_rowInfo, _model.RowPercents);
|
||||
FixAccuracyError(_colInfo, _model.ColumnPercents);
|
||||
}
|
||||
|
||||
public void ReplaceIndicesToMaintainOrder(int zoneCount)
|
||||
{
|
||||
int[,] cells = _model.CellChildMap;
|
||||
}
|
||||
|
||||
public void ReplaceIndicesToMaintainOrder(int zoneCount)
|
||||
{
|
||||
int[,] cells = _model.CellChildMap;
|
||||
Dictionary<int, int> indexReplacement = new Dictionary<int, int>();
|
||||
List<int> zoneIndices = new List<int>(zoneCount);
|
||||
HashSet<int> zoneIndexSet = new HashSet<int>(zoneCount);
|
||||
|
||||
List<int> zoneIndices = new List<int>(zoneCount);
|
||||
HashSet<int> zoneIndexSet = new HashSet<int>(zoneCount);
|
||||
|
||||
for (int i = 0; i < zoneCount; i++)
|
||||
{
|
||||
zoneIndices.Add(i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (int row = 0; row < _model.Rows; row++)
|
||||
{
|
||||
for (int col = 0; col < _model.Columns; col++)
|
||||
{
|
||||
zoneIndexSet.Add(cells[row, col]);
|
||||
}
|
||||
}
|
||||
|
||||
int j = 0;
|
||||
foreach (int index in zoneIndexSet)
|
||||
{
|
||||
indexReplacement[index] = zoneIndices[j];
|
||||
j++;
|
||||
}
|
||||
|
||||
ReplaceIndicesToMaintainOrder(indexReplacement);
|
||||
}
|
||||
|
||||
int j = 0;
|
||||
foreach (int index in zoneIndexSet)
|
||||
{
|
||||
indexReplacement[index] = zoneIndices[j];
|
||||
j++;
|
||||
}
|
||||
|
||||
ReplaceIndicesToMaintainOrder(indexReplacement);
|
||||
}
|
||||
|
||||
private void ReplaceIndicesToMaintainOrder(Dictionary<int, int> indexReplacement)
|
||||
{
|
||||
int[,] cells = _model.CellChildMap;
|
||||
private void ReplaceIndicesToMaintainOrder(Dictionary<int, int> indexReplacement)
|
||||
{
|
||||
int[,] cells = _model.CellChildMap;
|
||||
|
||||
for (int row = 0; row < _model.Rows; row++)
|
||||
{
|
||||
@@ -896,7 +896,7 @@ namespace FancyZonesEditor
|
||||
{
|
||||
cells[row, col] = indexReplacement[cells[row, col]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CollapseIndices()
|
||||
|
||||
@@ -200,259 +200,259 @@ namespace FancyZonesEditor
|
||||
|
||||
if (isDeltaNegative)
|
||||
{
|
||||
DecreaseResizerValues(resizer, orientation);
|
||||
DecreaseResizerValues(resizer, orientation);
|
||||
}
|
||||
else
|
||||
{
|
||||
IncreaseResizerValues(resizer, orientation);
|
||||
}
|
||||
|
||||
// same orientation resizers update
|
||||
foreach (GridResizer r in _resizers)
|
||||
{
|
||||
if (r.Orientation == orientation)
|
||||
{
|
||||
if ((isHorizontal && r.StartRow == resizer.StartRow && r.StartCol != resizer.StartCol) ||
|
||||
(!isHorizontal && r.StartCol == resizer.StartCol && r.StartRow != resizer.StartRow))
|
||||
{
|
||||
if (isDeltaNegative)
|
||||
{
|
||||
IncreaseResizerValues(r, orientation);
|
||||
}
|
||||
else
|
||||
{
|
||||
DecreaseResizerValues(r, orientation);
|
||||
}
|
||||
|
||||
swappedResizers.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// different orientation resizers update
|
||||
foreach (GridResizer r in _resizers)
|
||||
{
|
||||
if (r.Orientation != resizer.Orientation)
|
||||
{
|
||||
if (isHorizontal)
|
||||
{
|
||||
// vertical resizers corresponding to dragged resizer
|
||||
if (r.StartCol >= resizer.StartCol && r.EndCol < resizer.EndCol)
|
||||
{
|
||||
if (r.StartRow == resizer.StartRow + 2 && isDeltaNegative)
|
||||
{
|
||||
r.StartRow--;
|
||||
}
|
||||
|
||||
if (r.EndRow == resizer.EndRow + 1 && isDeltaNegative)
|
||||
{
|
||||
r.EndRow--;
|
||||
}
|
||||
|
||||
if (r.StartRow == resizer.StartRow && !isDeltaNegative)
|
||||
{
|
||||
r.StartRow++;
|
||||
}
|
||||
|
||||
if (r.EndRow == resizer.EndRow - 1 && !isDeltaNegative)
|
||||
{
|
||||
r.EndRow++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// vertical resizers corresponding to swapped resizers
|
||||
foreach (GridResizer sr in swappedResizers)
|
||||
{
|
||||
if (r.StartCol >= sr.StartCol && r.EndCol <= sr.EndCol)
|
||||
{
|
||||
if (r.StartRow == resizer.StartRow + 1 && isDeltaNegative)
|
||||
{
|
||||
r.StartRow++;
|
||||
}
|
||||
|
||||
if (r.EndRow == resizer.EndRow && isDeltaNegative)
|
||||
{
|
||||
r.EndRow++;
|
||||
}
|
||||
|
||||
if (r.StartRow == resizer.StartRow + 1 && !isDeltaNegative)
|
||||
{
|
||||
r.StartRow--;
|
||||
}
|
||||
|
||||
if (r.EndRow == resizer.EndRow && !isDeltaNegative)
|
||||
{
|
||||
r.EndRow--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// horizontal resizers corresponding to dragged resizer
|
||||
if (r.StartRow >= resizer.StartRow && r.EndRow < resizer.EndRow)
|
||||
{
|
||||
if (r.StartCol == resizer.StartCol + 3 && isDeltaNegative)
|
||||
{
|
||||
r.StartCol--;
|
||||
}
|
||||
|
||||
if (r.EndCol == resizer.EndCol + 1 && isDeltaNegative)
|
||||
{
|
||||
r.EndCol--;
|
||||
}
|
||||
|
||||
if (r.StartCol == resizer.StartCol && !isDeltaNegative)
|
||||
{
|
||||
r.StartCol++;
|
||||
}
|
||||
|
||||
if (r.EndCol == resizer.EndCol - 1 && !isDeltaNegative)
|
||||
{
|
||||
r.EndCol++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// horizontal resizers corresponding to swapped resizers
|
||||
foreach (GridResizer sr in swappedResizers)
|
||||
{
|
||||
if (r.StartRow >= sr.StartRow && r.EndRow <= sr.EndRow)
|
||||
{
|
||||
if (r.StartCol == resizer.StartCol + 1 && isDeltaNegative)
|
||||
{
|
||||
r.StartCol++;
|
||||
}
|
||||
|
||||
if (r.EndCol == resizer.EndCol && isDeltaNegative)
|
||||
{
|
||||
r.EndCol++;
|
||||
}
|
||||
|
||||
if (r.StartCol == resizer.StartCol + 1 && !isDeltaNegative)
|
||||
{
|
||||
r.StartCol--;
|
||||
}
|
||||
|
||||
if (r.EndCol == resizer.EndCol && !isDeltaNegative)
|
||||
{
|
||||
r.EndCol--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IncreaseResizerValues(resizer, orientation);
|
||||
}
|
||||
|
||||
// same orientation resizers update
|
||||
foreach (GridResizer r in _resizers)
|
||||
{
|
||||
if (r.Orientation == orientation)
|
||||
{
|
||||
if ((isHorizontal && r.StartRow == resizer.StartRow && r.StartCol != resizer.StartCol) ||
|
||||
(!isHorizontal && r.StartCol == resizer.StartCol && r.StartRow != resizer.StartRow))
|
||||
{
|
||||
if (isDeltaNegative)
|
||||
{
|
||||
IncreaseResizerValues(r, orientation);
|
||||
}
|
||||
else
|
||||
{
|
||||
DecreaseResizerValues(r, orientation);
|
||||
}
|
||||
|
||||
swappedResizers.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// different orientation resizers update
|
||||
foreach (GridResizer r in _resizers)
|
||||
{
|
||||
if (r.Orientation != resizer.Orientation)
|
||||
{
|
||||
if (isHorizontal)
|
||||
{
|
||||
// vertical resizers corresponding to dragged resizer
|
||||
if (r.StartCol >= resizer.StartCol && r.EndCol < resizer.EndCol)
|
||||
{
|
||||
if (r.StartRow == resizer.StartRow + 2 && isDeltaNegative)
|
||||
{
|
||||
r.StartRow--;
|
||||
}
|
||||
|
||||
if (r.EndRow == resizer.EndRow + 1 && isDeltaNegative)
|
||||
{
|
||||
r.EndRow--;
|
||||
}
|
||||
|
||||
if (r.StartRow == resizer.StartRow && !isDeltaNegative)
|
||||
{
|
||||
r.StartRow++;
|
||||
}
|
||||
|
||||
if (r.EndRow == resizer.EndRow - 1 && !isDeltaNegative)
|
||||
{
|
||||
r.EndRow++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// vertical resizers corresponding to swapped resizers
|
||||
foreach (GridResizer sr in swappedResizers)
|
||||
{
|
||||
if (r.StartCol >= sr.StartCol && r.EndCol <= sr.EndCol)
|
||||
{
|
||||
if (r.StartRow == resizer.StartRow + 1 && isDeltaNegative)
|
||||
{
|
||||
r.StartRow++;
|
||||
}
|
||||
|
||||
if (r.EndRow == resizer.EndRow && isDeltaNegative)
|
||||
{
|
||||
r.EndRow++;
|
||||
}
|
||||
|
||||
if (r.StartRow == resizer.StartRow + 1 && !isDeltaNegative)
|
||||
{
|
||||
r.StartRow--;
|
||||
}
|
||||
|
||||
if (r.EndRow == resizer.EndRow && !isDeltaNegative)
|
||||
{
|
||||
r.EndRow--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// horizontal resizers corresponding to dragged resizer
|
||||
if (r.StartRow >= resizer.StartRow && r.EndRow < resizer.EndRow)
|
||||
{
|
||||
if (r.StartCol == resizer.StartCol + 3 && isDeltaNegative)
|
||||
{
|
||||
r.StartCol--;
|
||||
}
|
||||
|
||||
if (r.EndCol == resizer.EndCol + 1 && isDeltaNegative)
|
||||
{
|
||||
r.EndCol--;
|
||||
}
|
||||
|
||||
if (r.StartCol == resizer.StartCol && !isDeltaNegative)
|
||||
{
|
||||
r.StartCol++;
|
||||
}
|
||||
|
||||
if (r.EndCol == resizer.EndCol - 1 && !isDeltaNegative)
|
||||
{
|
||||
r.EndCol++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// horizontal resizers corresponding to swapped resizers
|
||||
foreach (GridResizer sr in swappedResizers)
|
||||
{
|
||||
if (r.StartRow >= sr.StartRow && r.EndRow <= sr.EndRow)
|
||||
{
|
||||
if (r.StartCol == resizer.StartCol + 1 && isDeltaNegative)
|
||||
{
|
||||
r.StartCol++;
|
||||
}
|
||||
|
||||
if (r.EndCol == resizer.EndCol && isDeltaNegative)
|
||||
{
|
||||
r.EndCol++;
|
||||
}
|
||||
|
||||
if (r.StartCol == resizer.StartCol + 1 && !isDeltaNegative)
|
||||
{
|
||||
r.StartCol--;
|
||||
}
|
||||
|
||||
if (r.EndCol == resizer.EndCol && !isDeltaNegative)
|
||||
{
|
||||
r.EndCol--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateAfterDetach(GridResizer resizer, double delta)
|
||||
{
|
||||
bool isDeltaNegative = delta < 0;
|
||||
Orientation orientation = resizer.Orientation;
|
||||
|
||||
foreach (GridResizer r in _resizers)
|
||||
{
|
||||
bool notEqual = r.StartRow != resizer.StartRow || r.EndRow != resizer.EndRow || r.StartCol != resizer.StartCol || r.EndCol != resizer.EndCol;
|
||||
if (r.Orientation == orientation && notEqual)
|
||||
{
|
||||
if (orientation == Orientation.Horizontal)
|
||||
{
|
||||
if (r.StartRow > resizer.StartRow || (r.StartRow == resizer.StartRow && isDeltaNegative))
|
||||
{
|
||||
r.StartRow++;
|
||||
}
|
||||
|
||||
if (r.EndRow > resizer.EndRow || (r.EndRow == resizer.EndRow && isDeltaNegative))
|
||||
{
|
||||
r.EndRow++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r.StartCol > resizer.StartCol || (r.StartCol == resizer.StartCol && isDeltaNegative))
|
||||
{
|
||||
r.StartCol++;
|
||||
}
|
||||
|
||||
if (r.EndCol > resizer.EndCol || (r.EndCol == resizer.EndCol && isDeltaNegative))
|
||||
{
|
||||
r.EndCol++;
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
bool isDeltaNegative = delta < 0;
|
||||
Orientation orientation = resizer.Orientation;
|
||||
|
||||
foreach (GridResizer r in _resizers)
|
||||
{
|
||||
bool notEqual = r.StartRow != resizer.StartRow || r.EndRow != resizer.EndRow || r.StartCol != resizer.StartCol || r.EndCol != resizer.EndCol;
|
||||
if (r.Orientation == orientation && notEqual)
|
||||
{
|
||||
if (orientation == Orientation.Horizontal)
|
||||
{
|
||||
if (r.StartRow > resizer.StartRow || (r.StartRow == resizer.StartRow && isDeltaNegative))
|
||||
{
|
||||
r.StartRow++;
|
||||
}
|
||||
|
||||
if (r.EndRow > resizer.EndRow || (r.EndRow == resizer.EndRow && isDeltaNegative))
|
||||
{
|
||||
r.EndRow++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r.StartCol > resizer.StartCol || (r.StartCol == resizer.StartCol && isDeltaNegative))
|
||||
{
|
||||
r.StartCol++;
|
||||
}
|
||||
|
||||
if (r.EndCol > resizer.EndCol || (r.EndCol == resizer.EndCol && isDeltaNegative))
|
||||
{
|
||||
r.EndCol++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDeltaNegative)
|
||||
{
|
||||
IncreaseResizerValues(resizer, orientation);
|
||||
}
|
||||
|
||||
foreach (GridResizer r in _resizers)
|
||||
{
|
||||
if (r.Orientation != orientation)
|
||||
{
|
||||
if (orientation == Orientation.Vertical)
|
||||
{
|
||||
if (isDeltaNegative)
|
||||
{
|
||||
bool isRowNonAdjacent = r.EndRow < resizer.StartRow || r.StartRow > resizer.EndRow;
|
||||
|
||||
if (r.StartCol > resizer.StartCol + 1 || (r.StartCol == resizer.StartCol + 1 && isRowNonAdjacent))
|
||||
{
|
||||
r.StartCol++;
|
||||
}
|
||||
|
||||
if (r.EndCol > resizer.EndCol || (r.EndCol == resizer.EndCol && isRowNonAdjacent))
|
||||
{
|
||||
r.EndCol++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r.StartCol > resizer.StartCol || (r.StartCol == resizer.StartCol && r.StartRow >= resizer.StartRow && r.EndRow <= resizer.EndRow))
|
||||
{
|
||||
r.StartCol++;
|
||||
}
|
||||
|
||||
if (r.EndCol > resizer.EndCol - 1 || (r.EndCol == resizer.EndCol - 1 && r.StartRow >= resizer.StartRow && r.EndRow <= resizer.EndRow))
|
||||
{
|
||||
r.EndCol++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isDeltaNegative)
|
||||
{
|
||||
bool isColNonAdjacent = r.EndCol < resizer.StartCol || r.StartCol > resizer.EndCol;
|
||||
|
||||
if (r.StartRow > resizer.StartRow + 1 || (r.StartRow == resizer.StartRow + 1 && isColNonAdjacent))
|
||||
{
|
||||
r.StartRow++;
|
||||
}
|
||||
|
||||
if (r.EndRow > resizer.EndRow || (r.EndRow == resizer.EndRow && isColNonAdjacent))
|
||||
{
|
||||
r.EndRow++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r.StartRow > resizer.StartRow || (r.StartRow == resizer.StartRow && r.StartCol >= resizer.StartCol && r.EndCol <= resizer.EndCol))
|
||||
{
|
||||
r.StartRow++;
|
||||
}
|
||||
|
||||
if (r.EndRow > resizer.EndRow - 1 || (r.EndRow == resizer.EndRow - 1 && r.StartCol >= resizer.StartCol && r.EndCol <= resizer.EndCol))
|
||||
{
|
||||
r.EndRow++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (GridResizer r in _resizers)
|
||||
{
|
||||
if (r.Orientation != orientation)
|
||||
{
|
||||
if (orientation == Orientation.Vertical)
|
||||
{
|
||||
if (isDeltaNegative)
|
||||
{
|
||||
bool isRowNonAdjacent = r.EndRow < resizer.StartRow || r.StartRow > resizer.EndRow;
|
||||
|
||||
if (r.StartCol > resizer.StartCol + 1 || (r.StartCol == resizer.StartCol + 1 && isRowNonAdjacent))
|
||||
{
|
||||
r.StartCol++;
|
||||
}
|
||||
|
||||
if (r.EndCol > resizer.EndCol || (r.EndCol == resizer.EndCol && isRowNonAdjacent))
|
||||
{
|
||||
r.EndCol++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r.StartCol > resizer.StartCol || (r.StartCol == resizer.StartCol && r.StartRow >= resizer.StartRow && r.EndRow <= resizer.EndRow))
|
||||
{
|
||||
r.StartCol++;
|
||||
}
|
||||
|
||||
if (r.EndCol > resizer.EndCol - 1 || (r.EndCol == resizer.EndCol - 1 && r.StartRow >= resizer.StartRow && r.EndRow <= resizer.EndRow))
|
||||
{
|
||||
r.EndCol++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isDeltaNegative)
|
||||
{
|
||||
bool isColNonAdjacent = r.EndCol < resizer.StartCol || r.StartCol > resizer.EndCol;
|
||||
|
||||
if (r.StartRow > resizer.StartRow + 1 || (r.StartRow == resizer.StartRow + 1 && isColNonAdjacent))
|
||||
{
|
||||
r.StartRow++;
|
||||
}
|
||||
|
||||
if (r.EndRow > resizer.EndRow || (r.EndRow == resizer.EndRow && isColNonAdjacent))
|
||||
{
|
||||
r.EndRow++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r.StartRow > resizer.StartRow || (r.StartRow == resizer.StartRow && r.StartCol >= resizer.StartCol && r.EndCol <= resizer.EndCol))
|
||||
{
|
||||
r.StartRow++;
|
||||
}
|
||||
|
||||
if (r.EndRow > resizer.EndRow - 1 || (r.EndRow == resizer.EndRow - 1 && r.StartCol >= resizer.StartCol && r.EndCol <= resizer.EndCol))
|
||||
{
|
||||
r.EndRow++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -110,10 +110,10 @@ namespace FancyZonesEditor
|
||||
{
|
||||
Settings settings = ((App)Application.Current).ZoneSettings;
|
||||
if (!settings.ShowSpacing)
|
||||
{
|
||||
return 1;
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
return Math.Max(settings.Spacing, 1);
|
||||
}
|
||||
}
|
||||
@@ -281,13 +281,13 @@ namespace FancyZonesEditor
|
||||
}
|
||||
|
||||
private void DoSplit(Orientation orientation, double offset)
|
||||
{
|
||||
int spacing = 0;
|
||||
Settings settings = ((App)Application.Current).ZoneSettings;
|
||||
if (settings.ShowSpacing)
|
||||
{
|
||||
spacing = settings.Spacing;
|
||||
}
|
||||
{
|
||||
int spacing = 0;
|
||||
Settings settings = ((App)Application.Current).ZoneSettings;
|
||||
if (settings.ShowSpacing)
|
||||
{
|
||||
spacing = settings.Spacing;
|
||||
}
|
||||
|
||||
Split?.Invoke(this, new SplitEventArgs(orientation, offset, spacing));
|
||||
}
|
||||
|
||||
@@ -1,202 +1,202 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using FancyZonesEditor.Models;
|
||||
using MahApps.Metro.Controls;
|
||||
|
||||
namespace FancyZonesEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : MetroWindow
|
||||
{
|
||||
// TODO: share the constants b/w C# Editor and FancyZoneLib
|
||||
public const int MaxZones = 40;
|
||||
private readonly Settings _settings = ((App)Application.Current).ZoneSettings;
|
||||
|
||||
// Localizable string
|
||||
private static readonly string _defaultNamePrefix = "Custom Layout ";
|
||||
|
||||
public int WrapPanelItemSize { get; set; } = 262;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = _settings;
|
||||
|
||||
KeyUp += MainWindow_KeyUp;
|
||||
|
||||
if (Settings.WorkArea.Height < 900)
|
||||
{
|
||||
SizeToContent = SizeToContent.WidthAndHeight;
|
||||
WrapPanelItemSize = 180;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape)
|
||||
{
|
||||
OnClosing(sender, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void DecrementZones_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_settings.ZoneCount > 1)
|
||||
{
|
||||
_settings.ZoneCount--;
|
||||
}
|
||||
}
|
||||
|
||||
private void IncrementZones_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_settings.ZoneCount < MaxZones)
|
||||
{
|
||||
_settings.ZoneCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private void NewCustomLayoutButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowLayout window = new WindowLayout();
|
||||
window.Show();
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void LayoutItem_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Select(((Border)sender).DataContext as LayoutModel);
|
||||
}
|
||||
|
||||
private void Select(LayoutModel newSelection)
|
||||
{
|
||||
if (EditorOverlay.Current.DataContext is LayoutModel currentSelection)
|
||||
{
|
||||
currentSelection.IsSelected = false;
|
||||
}
|
||||
|
||||
newSelection.IsSelected = true;
|
||||
EditorOverlay.Current.DataContext = newSelection;
|
||||
}
|
||||
|
||||
private void EditLayout_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EditorOverlay mainEditor = EditorOverlay.Current;
|
||||
if (!(mainEditor.DataContext is LayoutModel model))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
model.IsSelected = false;
|
||||
Hide();
|
||||
|
||||
bool isPredefinedLayout = Settings.IsPredefinedLayout(model);
|
||||
|
||||
if (!Settings.CustomModels.Contains(model) || isPredefinedLayout)
|
||||
{
|
||||
if (isPredefinedLayout)
|
||||
{
|
||||
// make a copy
|
||||
model = model.Clone();
|
||||
mainEditor.DataContext = model;
|
||||
}
|
||||
|
||||
int maxCustomIndex = 0;
|
||||
foreach (LayoutModel customModel in Settings.CustomModels)
|
||||
{
|
||||
string name = customModel.Name;
|
||||
if (name.StartsWith(_defaultNamePrefix))
|
||||
{
|
||||
if (int.TryParse(name.Substring(_defaultNamePrefix.Length), out int i))
|
||||
{
|
||||
if (maxCustomIndex < i)
|
||||
{
|
||||
maxCustomIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model.Name = _defaultNamePrefix + (++maxCustomIndex);
|
||||
}
|
||||
|
||||
mainEditor.Edit();
|
||||
|
||||
EditorWindow window;
|
||||
if (model is GridLayoutModel)
|
||||
{
|
||||
window = new GridEditorWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
window = new CanvasEditorWindow();
|
||||
}
|
||||
|
||||
window.Owner = EditorOverlay.Current;
|
||||
window.DataContext = model;
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void Apply_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EditorOverlay mainEditor = EditorOverlay.Current;
|
||||
|
||||
if (mainEditor.DataContext is LayoutModel model)
|
||||
{
|
||||
// If custom canvas layout has been scaled, persisting is needed
|
||||
if (model is CanvasLayoutModel && (model as CanvasLayoutModel).IsScaled)
|
||||
{
|
||||
model.Persist();
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Apply();
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, EventArgs e)
|
||||
{
|
||||
LayoutModel.SerializeDeletedCustomZoneSets();
|
||||
EditorOverlay.Current.Close();
|
||||
}
|
||||
|
||||
private void OnInitialized(object sender, EventArgs e)
|
||||
{
|
||||
SetSelectedItem();
|
||||
}
|
||||
|
||||
private void SetSelectedItem()
|
||||
{
|
||||
foreach (LayoutModel model in Settings.CustomModels)
|
||||
{
|
||||
if (model.IsSelected)
|
||||
{
|
||||
TemplateTab.SelectedItem = model;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDelete(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LayoutModel model = ((FrameworkElement)sender).DataContext as LayoutModel;
|
||||
if (model.IsSelected)
|
||||
{
|
||||
SetSelectedItem();
|
||||
}
|
||||
|
||||
model.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using FancyZonesEditor.Models;
|
||||
using MahApps.Metro.Controls;
|
||||
|
||||
namespace FancyZonesEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : MetroWindow
|
||||
{
|
||||
// TODO: share the constants b/w C# Editor and FancyZoneLib
|
||||
public const int MaxZones = 40;
|
||||
private readonly Settings _settings = ((App)Application.Current).ZoneSettings;
|
||||
|
||||
// Localizable string
|
||||
private static readonly string _defaultNamePrefix = "Custom Layout ";
|
||||
|
||||
public int WrapPanelItemSize { get; set; } = 262;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = _settings;
|
||||
|
||||
KeyUp += MainWindow_KeyUp;
|
||||
|
||||
if (Settings.WorkArea.Height < 900)
|
||||
{
|
||||
SizeToContent = SizeToContent.WidthAndHeight;
|
||||
WrapPanelItemSize = 180;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape)
|
||||
{
|
||||
OnClosing(sender, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void DecrementZones_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_settings.ZoneCount > 1)
|
||||
{
|
||||
_settings.ZoneCount--;
|
||||
}
|
||||
}
|
||||
|
||||
private void IncrementZones_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_settings.ZoneCount < MaxZones)
|
||||
{
|
||||
_settings.ZoneCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private void NewCustomLayoutButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowLayout window = new WindowLayout();
|
||||
window.Show();
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void LayoutItem_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Select(((Border)sender).DataContext as LayoutModel);
|
||||
}
|
||||
|
||||
private void Select(LayoutModel newSelection)
|
||||
{
|
||||
if (EditorOverlay.Current.DataContext is LayoutModel currentSelection)
|
||||
{
|
||||
currentSelection.IsSelected = false;
|
||||
}
|
||||
|
||||
newSelection.IsSelected = true;
|
||||
EditorOverlay.Current.DataContext = newSelection;
|
||||
}
|
||||
|
||||
private void EditLayout_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EditorOverlay mainEditor = EditorOverlay.Current;
|
||||
if (!(mainEditor.DataContext is LayoutModel model))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
model.IsSelected = false;
|
||||
Hide();
|
||||
|
||||
bool isPredefinedLayout = Settings.IsPredefinedLayout(model);
|
||||
|
||||
if (!Settings.CustomModels.Contains(model) || isPredefinedLayout)
|
||||
{
|
||||
if (isPredefinedLayout)
|
||||
{
|
||||
// make a copy
|
||||
model = model.Clone();
|
||||
mainEditor.DataContext = model;
|
||||
}
|
||||
|
||||
int maxCustomIndex = 0;
|
||||
foreach (LayoutModel customModel in Settings.CustomModels)
|
||||
{
|
||||
string name = customModel.Name;
|
||||
if (name.StartsWith(_defaultNamePrefix))
|
||||
{
|
||||
if (int.TryParse(name.Substring(_defaultNamePrefix.Length), out int i))
|
||||
{
|
||||
if (maxCustomIndex < i)
|
||||
{
|
||||
maxCustomIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model.Name = _defaultNamePrefix + (++maxCustomIndex);
|
||||
}
|
||||
|
||||
mainEditor.Edit();
|
||||
|
||||
EditorWindow window;
|
||||
if (model is GridLayoutModel)
|
||||
{
|
||||
window = new GridEditorWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
window = new CanvasEditorWindow();
|
||||
}
|
||||
|
||||
window.Owner = EditorOverlay.Current;
|
||||
window.DataContext = model;
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void Apply_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EditorOverlay mainEditor = EditorOverlay.Current;
|
||||
|
||||
if (mainEditor.DataContext is LayoutModel model)
|
||||
{
|
||||
// If custom canvas layout has been scaled, persisting is needed
|
||||
if (model is CanvasLayoutModel && (model as CanvasLayoutModel).IsScaled)
|
||||
{
|
||||
model.Persist();
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Apply();
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClosing(object sender, EventArgs e)
|
||||
{
|
||||
LayoutModel.SerializeDeletedCustomZoneSets();
|
||||
EditorOverlay.Current.Close();
|
||||
}
|
||||
|
||||
private void OnInitialized(object sender, EventArgs e)
|
||||
{
|
||||
SetSelectedItem();
|
||||
}
|
||||
|
||||
private void SetSelectedItem()
|
||||
{
|
||||
foreach (LayoutModel model in Settings.CustomModels)
|
||||
{
|
||||
if (model.IsSelected)
|
||||
{
|
||||
TemplateTab.SelectedItem = model;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDelete(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LayoutModel model = ((FrameworkElement)sender).DataContext as LayoutModel;
|
||||
if (model.IsSelected)
|
||||
{
|
||||
SetSelectedItem();
|
||||
}
|
||||
|
||||
model.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,212 +1,212 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
// CanvasLayoutModel
|
||||
// Free form Layout Model, which specifies independent zone rects
|
||||
public class CanvasLayoutModel : LayoutModel
|
||||
{
|
||||
// Localizable strings
|
||||
private const string ErrorPersistingCanvasLayout = "Error persisting canvas layout";
|
||||
|
||||
// Non-localizable strings
|
||||
private const string ModelTypeID = "canvas";
|
||||
|
||||
public CanvasLayoutModel(string uuid, string name, LayoutType type, IList<Int32Rect> zones, int workAreaWidth, int workAreaHeight)
|
||||
: base(uuid, name, type)
|
||||
{
|
||||
lastWorkAreaWidth = workAreaWidth;
|
||||
lastWorkAreaHeight = workAreaHeight;
|
||||
IsScaled = false;
|
||||
|
||||
if (ShouldScaleLayout())
|
||||
{
|
||||
ScaleLayout(zones);
|
||||
}
|
||||
else
|
||||
{
|
||||
Zones = zones;
|
||||
}
|
||||
}
|
||||
|
||||
public CanvasLayoutModel(string name, LayoutType type)
|
||||
: base(name, type)
|
||||
{
|
||||
IsScaled = false;
|
||||
}
|
||||
|
||||
public CanvasLayoutModel(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
// Zones - the list of all zones in this layout, described as independent rectangles
|
||||
public IList<Int32Rect> Zones { get; private set; } = new List<Int32Rect>();
|
||||
|
||||
private int lastWorkAreaWidth = (int)Settings.WorkArea.Width;
|
||||
|
||||
private int lastWorkAreaHeight = (int)Settings.WorkArea.Height;
|
||||
|
||||
public bool IsScaled { get; private set; }
|
||||
|
||||
// RemoveZoneAt
|
||||
// Removes the specified index from the Zones list, and fires a property changed notification for the Zones property
|
||||
public void RemoveZoneAt(int index)
|
||||
{
|
||||
Zones.RemoveAt(index);
|
||||
UpdateLayout();
|
||||
}
|
||||
|
||||
// AddZone
|
||||
// Adds the specified Zone to the end of the Zones list, and fires a property changed notification for the Zones property
|
||||
public void AddZone(Int32Rect zone)
|
||||
{
|
||||
Zones.Add(zone);
|
||||
UpdateLayout();
|
||||
}
|
||||
|
||||
private void UpdateLayout()
|
||||
{
|
||||
FirePropertyChanged();
|
||||
}
|
||||
|
||||
// Clone
|
||||
// Implements the LayoutModel.Clone abstract method
|
||||
// Clones the data from this CanvasLayoutModel to a new CanvasLayoutModel
|
||||
public override LayoutModel Clone()
|
||||
{
|
||||
CanvasLayoutModel layout = new CanvasLayoutModel(Name);
|
||||
|
||||
foreach (Int32Rect zone in Zones)
|
||||
{
|
||||
layout.Zones.Add(zone);
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
public void RestoreTo(CanvasLayoutModel other)
|
||||
{
|
||||
other.Zones.Clear();
|
||||
foreach (Int32Rect zone in Zones)
|
||||
{
|
||||
other.Zones.Add(zone);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldScaleLayout()
|
||||
{
|
||||
// Scale if:
|
||||
// - at least one dimension changed
|
||||
// - orientation remained the same
|
||||
return (lastWorkAreaHeight != Settings.WorkArea.Height || lastWorkAreaWidth != Settings.WorkArea.Width) &&
|
||||
((lastWorkAreaHeight > lastWorkAreaWidth && Settings.WorkArea.Height > Settings.WorkArea.Width) ||
|
||||
(lastWorkAreaWidth > lastWorkAreaHeight && Settings.WorkArea.Width > Settings.WorkArea.Height));
|
||||
}
|
||||
|
||||
private void ScaleLayout(IList<Int32Rect> zones)
|
||||
{
|
||||
foreach (Int32Rect zone in zones)
|
||||
{
|
||||
double widthFactor = (double)Settings.WorkArea.Width / lastWorkAreaWidth;
|
||||
double heightFactor = (double)Settings.WorkArea.Height / lastWorkAreaHeight;
|
||||
int scaledX = (int)(zone.X * widthFactor);
|
||||
int scaledY = (int)(zone.Y * heightFactor);
|
||||
int scaledWidth = (int)(zone.Width * widthFactor);
|
||||
int scaledHeight = (int)(zone.Height * heightFactor);
|
||||
Zones.Add(new Int32Rect(scaledX, scaledY, scaledWidth, scaledHeight));
|
||||
}
|
||||
|
||||
lastWorkAreaHeight = (int)Settings.WorkArea.Height;
|
||||
lastWorkAreaWidth = (int)Settings.WorkArea.Width;
|
||||
IsScaled = true;
|
||||
}
|
||||
|
||||
private struct Zone
|
||||
{
|
||||
public int X { get; set; }
|
||||
|
||||
public int Y { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
}
|
||||
|
||||
private struct CanvasLayoutInfo
|
||||
{
|
||||
public int RefWidth { get; set; }
|
||||
|
||||
public int RefHeight { get; set; }
|
||||
|
||||
public Zone[] Zones { get; set; }
|
||||
}
|
||||
|
||||
private struct CanvasLayoutJson
|
||||
{
|
||||
public string Uuid { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
|
||||
public CanvasLayoutInfo Info { get; set; }
|
||||
}
|
||||
|
||||
// PersistData
|
||||
// Implements the LayoutModel.PersistData abstract method
|
||||
protected override void PersistData()
|
||||
{
|
||||
CanvasLayoutInfo layoutInfo = new CanvasLayoutInfo
|
||||
{
|
||||
RefWidth = lastWorkAreaWidth,
|
||||
RefHeight = lastWorkAreaHeight,
|
||||
|
||||
Zones = new Zone[Zones.Count],
|
||||
};
|
||||
for (int i = 0; i < Zones.Count; ++i)
|
||||
{
|
||||
Zone zone = new Zone
|
||||
{
|
||||
X = Zones[i].X,
|
||||
Y = Zones[i].Y,
|
||||
Width = Zones[i].Width,
|
||||
Height = Zones[i].Height,
|
||||
};
|
||||
|
||||
layoutInfo.Zones[i] = zone;
|
||||
}
|
||||
|
||||
CanvasLayoutJson jsonObj = new CanvasLayoutJson
|
||||
{
|
||||
Uuid = "{" + Guid.ToString().ToUpper() + "}",
|
||||
Name = Name,
|
||||
Type = ModelTypeID,
|
||||
Info = layoutInfo,
|
||||
};
|
||||
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
string jsonString = JsonSerializer.Serialize(jsonObj, options);
|
||||
File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowExceptionMessageBox(ErrorPersistingCanvasLayout, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
// CanvasLayoutModel
|
||||
// Free form Layout Model, which specifies independent zone rects
|
||||
public class CanvasLayoutModel : LayoutModel
|
||||
{
|
||||
// Localizable strings
|
||||
private const string ErrorPersistingCanvasLayout = "Error persisting canvas layout";
|
||||
|
||||
// Non-localizable strings
|
||||
private const string ModelTypeID = "canvas";
|
||||
|
||||
public CanvasLayoutModel(string uuid, string name, LayoutType type, IList<Int32Rect> zones, int workAreaWidth, int workAreaHeight)
|
||||
: base(uuid, name, type)
|
||||
{
|
||||
lastWorkAreaWidth = workAreaWidth;
|
||||
lastWorkAreaHeight = workAreaHeight;
|
||||
IsScaled = false;
|
||||
|
||||
if (ShouldScaleLayout())
|
||||
{
|
||||
ScaleLayout(zones);
|
||||
}
|
||||
else
|
||||
{
|
||||
Zones = zones;
|
||||
}
|
||||
}
|
||||
|
||||
public CanvasLayoutModel(string name, LayoutType type)
|
||||
: base(name, type)
|
||||
{
|
||||
IsScaled = false;
|
||||
}
|
||||
|
||||
public CanvasLayoutModel(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
// Zones - the list of all zones in this layout, described as independent rectangles
|
||||
public IList<Int32Rect> Zones { get; private set; } = new List<Int32Rect>();
|
||||
|
||||
private int lastWorkAreaWidth = (int)Settings.WorkArea.Width;
|
||||
|
||||
private int lastWorkAreaHeight = (int)Settings.WorkArea.Height;
|
||||
|
||||
public bool IsScaled { get; private set; }
|
||||
|
||||
// RemoveZoneAt
|
||||
// Removes the specified index from the Zones list, and fires a property changed notification for the Zones property
|
||||
public void RemoveZoneAt(int index)
|
||||
{
|
||||
Zones.RemoveAt(index);
|
||||
UpdateLayout();
|
||||
}
|
||||
|
||||
// AddZone
|
||||
// Adds the specified Zone to the end of the Zones list, and fires a property changed notification for the Zones property
|
||||
public void AddZone(Int32Rect zone)
|
||||
{
|
||||
Zones.Add(zone);
|
||||
UpdateLayout();
|
||||
}
|
||||
|
||||
private void UpdateLayout()
|
||||
{
|
||||
FirePropertyChanged();
|
||||
}
|
||||
|
||||
// Clone
|
||||
// Implements the LayoutModel.Clone abstract method
|
||||
// Clones the data from this CanvasLayoutModel to a new CanvasLayoutModel
|
||||
public override LayoutModel Clone()
|
||||
{
|
||||
CanvasLayoutModel layout = new CanvasLayoutModel(Name);
|
||||
|
||||
foreach (Int32Rect zone in Zones)
|
||||
{
|
||||
layout.Zones.Add(zone);
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
public void RestoreTo(CanvasLayoutModel other)
|
||||
{
|
||||
other.Zones.Clear();
|
||||
foreach (Int32Rect zone in Zones)
|
||||
{
|
||||
other.Zones.Add(zone);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldScaleLayout()
|
||||
{
|
||||
// Scale if:
|
||||
// - at least one dimension changed
|
||||
// - orientation remained the same
|
||||
return (lastWorkAreaHeight != Settings.WorkArea.Height || lastWorkAreaWidth != Settings.WorkArea.Width) &&
|
||||
((lastWorkAreaHeight > lastWorkAreaWidth && Settings.WorkArea.Height > Settings.WorkArea.Width) ||
|
||||
(lastWorkAreaWidth > lastWorkAreaHeight && Settings.WorkArea.Width > Settings.WorkArea.Height));
|
||||
}
|
||||
|
||||
private void ScaleLayout(IList<Int32Rect> zones)
|
||||
{
|
||||
foreach (Int32Rect zone in zones)
|
||||
{
|
||||
double widthFactor = (double)Settings.WorkArea.Width / lastWorkAreaWidth;
|
||||
double heightFactor = (double)Settings.WorkArea.Height / lastWorkAreaHeight;
|
||||
int scaledX = (int)(zone.X * widthFactor);
|
||||
int scaledY = (int)(zone.Y * heightFactor);
|
||||
int scaledWidth = (int)(zone.Width * widthFactor);
|
||||
int scaledHeight = (int)(zone.Height * heightFactor);
|
||||
Zones.Add(new Int32Rect(scaledX, scaledY, scaledWidth, scaledHeight));
|
||||
}
|
||||
|
||||
lastWorkAreaHeight = (int)Settings.WorkArea.Height;
|
||||
lastWorkAreaWidth = (int)Settings.WorkArea.Width;
|
||||
IsScaled = true;
|
||||
}
|
||||
|
||||
private struct Zone
|
||||
{
|
||||
public int X { get; set; }
|
||||
|
||||
public int Y { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
}
|
||||
|
||||
private struct CanvasLayoutInfo
|
||||
{
|
||||
public int RefWidth { get; set; }
|
||||
|
||||
public int RefHeight { get; set; }
|
||||
|
||||
public Zone[] Zones { get; set; }
|
||||
}
|
||||
|
||||
private struct CanvasLayoutJson
|
||||
{
|
||||
public string Uuid { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
|
||||
public CanvasLayoutInfo Info { get; set; }
|
||||
}
|
||||
|
||||
// PersistData
|
||||
// Implements the LayoutModel.PersistData abstract method
|
||||
protected override void PersistData()
|
||||
{
|
||||
CanvasLayoutInfo layoutInfo = new CanvasLayoutInfo
|
||||
{
|
||||
RefWidth = lastWorkAreaWidth,
|
||||
RefHeight = lastWorkAreaHeight,
|
||||
|
||||
Zones = new Zone[Zones.Count],
|
||||
};
|
||||
for (int i = 0; i < Zones.Count; ++i)
|
||||
{
|
||||
Zone zone = new Zone
|
||||
{
|
||||
X = Zones[i].X,
|
||||
Y = Zones[i].Y,
|
||||
Width = Zones[i].Width,
|
||||
Height = Zones[i].Height,
|
||||
};
|
||||
|
||||
layoutInfo.Zones[i] = zone;
|
||||
}
|
||||
|
||||
CanvasLayoutJson jsonObj = new CanvasLayoutJson
|
||||
{
|
||||
Uuid = "{" + Guid.ToString().ToUpper() + "}",
|
||||
Name = Name,
|
||||
Type = ModelTypeID,
|
||||
Info = layoutInfo,
|
||||
};
|
||||
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
string jsonString = JsonSerializer.Serialize(jsonObj, options);
|
||||
File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowExceptionMessageBox(ErrorPersistingCanvasLayout, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,248 +1,248 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
// GridLayoutModel
|
||||
// Grid-styled Layout Model, which specifies rows, columns, percentage sizes, and row/column spans
|
||||
public class GridLayoutModel : LayoutModel
|
||||
{
|
||||
// Localizable strings
|
||||
private const string ErrorPersistingGridLayout = "Error persisting grid layout";
|
||||
|
||||
// Non-localizable strings
|
||||
private const string ModelTypeID = "grid";
|
||||
|
||||
// Rows - number of rows in the Grid
|
||||
public int Rows
|
||||
{
|
||||
get
|
||||
{
|
||||
return _rows;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_rows != value)
|
||||
{
|
||||
_rows = value;
|
||||
FirePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _rows = 1;
|
||||
|
||||
// Columns - number of columns in the Grid
|
||||
public int Columns
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cols;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_cols != value)
|
||||
{
|
||||
_cols = value;
|
||||
FirePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _cols = 1;
|
||||
|
||||
// CellChildMap - represents which "children" belong in which grid cells;
|
||||
// shows spanning children by the same index appearing in adjacent cells
|
||||
// TODO: ideally no setter here - this means moving logic like "split" over to model
|
||||
public int[,] CellChildMap { get; set; }
|
||||
|
||||
// RowPercents - represents the %age height of each row in the grid
|
||||
public List<int> RowPercents { get; set; }
|
||||
|
||||
// ColumnPercents - represents the %age width of each column in the grid
|
||||
public List<int> ColumnPercents { get; set; }
|
||||
|
||||
// FreeZones (not persisted) - used to keep track of child indices that are no longer in use in the CellChildMap,
|
||||
// making them candidates for re-use when it's needed to add another child
|
||||
// TODO: do I need FreeZones on the data model? - I think I do
|
||||
public IList<int> FreeZones { get; } = new List<int>();
|
||||
|
||||
public GridLayoutModel()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public GridLayoutModel(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
public GridLayoutModel(string name, LayoutType type)
|
||||
: base(name, type)
|
||||
{
|
||||
}
|
||||
|
||||
public GridLayoutModel(string uuid, string name, LayoutType type, int rows, int cols, List<int> rowPercents, List<int> colsPercents, int[,] cellChildMap)
|
||||
: base(uuid, name, type)
|
||||
{
|
||||
_rows = rows;
|
||||
_cols = cols;
|
||||
RowPercents = rowPercents;
|
||||
ColumnPercents = colsPercents;
|
||||
CellChildMap = cellChildMap;
|
||||
}
|
||||
|
||||
public void Reload(byte[] data)
|
||||
{
|
||||
// Skip version (2 bytes), id (2 bytes), and type (1 bytes)
|
||||
int i = 5;
|
||||
|
||||
Rows = data[i++];
|
||||
Columns = data[i++];
|
||||
|
||||
RowPercents = new List<int>(Rows);
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
RowPercents.Add((data[i++] * 256) + data[i++]);
|
||||
}
|
||||
|
||||
ColumnPercents = new List<int>(Columns);
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
ColumnPercents.Add((data[i++] * 256) + data[i++]);
|
||||
}
|
||||
|
||||
CellChildMap = new int[Rows, Columns];
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
CellChildMap[row, col] = data[i++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone
|
||||
// Implements the LayoutModel.Clone abstract method
|
||||
// Clones the data from this GridLayoutModel to a new GridLayoutModel
|
||||
public override LayoutModel Clone()
|
||||
{
|
||||
GridLayoutModel layout = new GridLayoutModel(Name);
|
||||
RestoreTo(layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
public void RestoreTo(GridLayoutModel layout)
|
||||
{
|
||||
int rows = Rows;
|
||||
int cols = Columns;
|
||||
|
||||
layout.Rows = rows;
|
||||
layout.Columns = cols;
|
||||
|
||||
int[,] cellChildMap = new int[rows, cols];
|
||||
for (int row = 0; row < rows; row++)
|
||||
{
|
||||
for (int col = 0; col < cols; col++)
|
||||
{
|
||||
cellChildMap[row, col] = CellChildMap[row, col];
|
||||
}
|
||||
}
|
||||
|
||||
layout.CellChildMap = cellChildMap;
|
||||
|
||||
List<int> rowPercents = new List<int>(rows);
|
||||
for (int row = 0; row < rows; row++)
|
||||
{
|
||||
rowPercents.Add(RowPercents[row]);
|
||||
}
|
||||
|
||||
layout.RowPercents = rowPercents;
|
||||
|
||||
List<int> colPercents = new List<int>(cols);
|
||||
for (int col = 0; col < cols; col++)
|
||||
{
|
||||
colPercents.Add(ColumnPercents[col]);
|
||||
}
|
||||
|
||||
layout.ColumnPercents = colPercents;
|
||||
}
|
||||
|
||||
private struct GridLayoutInfo
|
||||
{
|
||||
public int Rows { get; set; }
|
||||
|
||||
public int Columns { get; set; }
|
||||
|
||||
public List<int> RowsPercentage { get; set; }
|
||||
|
||||
public List<int> ColumnsPercentage { get; set; }
|
||||
|
||||
public int[][] CellChildMap { get; set; }
|
||||
}
|
||||
|
||||
private struct GridLayoutJson
|
||||
{
|
||||
public string Uuid { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
|
||||
public GridLayoutInfo Info { get; set; }
|
||||
}
|
||||
|
||||
// PersistData
|
||||
// Implements the LayoutModel.PersistData abstract method
|
||||
protected override void PersistData()
|
||||
{
|
||||
GridLayoutInfo layoutInfo = new GridLayoutInfo
|
||||
{
|
||||
Rows = Rows,
|
||||
Columns = Columns,
|
||||
RowsPercentage = RowPercents,
|
||||
ColumnsPercentage = ColumnPercents,
|
||||
CellChildMap = new int[Rows][],
|
||||
};
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
layoutInfo.CellChildMap[row] = new int[Columns];
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
layoutInfo.CellChildMap[row][col] = CellChildMap[row, col];
|
||||
}
|
||||
}
|
||||
|
||||
GridLayoutJson jsonObj = new GridLayoutJson
|
||||
{
|
||||
Uuid = "{" + Guid.ToString().ToUpper() + "}",
|
||||
Name = Name,
|
||||
Type = ModelTypeID,
|
||||
Info = layoutInfo,
|
||||
};
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
string jsonString = JsonSerializer.Serialize(jsonObj, options);
|
||||
File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowExceptionMessageBox(ErrorPersistingGridLayout, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
// GridLayoutModel
|
||||
// Grid-styled Layout Model, which specifies rows, columns, percentage sizes, and row/column spans
|
||||
public class GridLayoutModel : LayoutModel
|
||||
{
|
||||
// Localizable strings
|
||||
private const string ErrorPersistingGridLayout = "Error persisting grid layout";
|
||||
|
||||
// Non-localizable strings
|
||||
private const string ModelTypeID = "grid";
|
||||
|
||||
// Rows - number of rows in the Grid
|
||||
public int Rows
|
||||
{
|
||||
get
|
||||
{
|
||||
return _rows;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_rows != value)
|
||||
{
|
||||
_rows = value;
|
||||
FirePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _rows = 1;
|
||||
|
||||
// Columns - number of columns in the Grid
|
||||
public int Columns
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cols;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_cols != value)
|
||||
{
|
||||
_cols = value;
|
||||
FirePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _cols = 1;
|
||||
|
||||
// CellChildMap - represents which "children" belong in which grid cells;
|
||||
// shows spanning children by the same index appearing in adjacent cells
|
||||
// TODO: ideally no setter here - this means moving logic like "split" over to model
|
||||
public int[,] CellChildMap { get; set; }
|
||||
|
||||
// RowPercents - represents the %age height of each row in the grid
|
||||
public List<int> RowPercents { get; set; }
|
||||
|
||||
// ColumnPercents - represents the %age width of each column in the grid
|
||||
public List<int> ColumnPercents { get; set; }
|
||||
|
||||
// FreeZones (not persisted) - used to keep track of child indices that are no longer in use in the CellChildMap,
|
||||
// making them candidates for re-use when it's needed to add another child
|
||||
// TODO: do I need FreeZones on the data model? - I think I do
|
||||
public IList<int> FreeZones { get; } = new List<int>();
|
||||
|
||||
public GridLayoutModel()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public GridLayoutModel(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
public GridLayoutModel(string name, LayoutType type)
|
||||
: base(name, type)
|
||||
{
|
||||
}
|
||||
|
||||
public GridLayoutModel(string uuid, string name, LayoutType type, int rows, int cols, List<int> rowPercents, List<int> colsPercents, int[,] cellChildMap)
|
||||
: base(uuid, name, type)
|
||||
{
|
||||
_rows = rows;
|
||||
_cols = cols;
|
||||
RowPercents = rowPercents;
|
||||
ColumnPercents = colsPercents;
|
||||
CellChildMap = cellChildMap;
|
||||
}
|
||||
|
||||
public void Reload(byte[] data)
|
||||
{
|
||||
// Skip version (2 bytes), id (2 bytes), and type (1 bytes)
|
||||
int i = 5;
|
||||
|
||||
Rows = data[i++];
|
||||
Columns = data[i++];
|
||||
|
||||
RowPercents = new List<int>(Rows);
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
RowPercents.Add((data[i++] * 256) + data[i++]);
|
||||
}
|
||||
|
||||
ColumnPercents = new List<int>(Columns);
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
ColumnPercents.Add((data[i++] * 256) + data[i++]);
|
||||
}
|
||||
|
||||
CellChildMap = new int[Rows, Columns];
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
CellChildMap[row, col] = data[i++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone
|
||||
// Implements the LayoutModel.Clone abstract method
|
||||
// Clones the data from this GridLayoutModel to a new GridLayoutModel
|
||||
public override LayoutModel Clone()
|
||||
{
|
||||
GridLayoutModel layout = new GridLayoutModel(Name);
|
||||
RestoreTo(layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
public void RestoreTo(GridLayoutModel layout)
|
||||
{
|
||||
int rows = Rows;
|
||||
int cols = Columns;
|
||||
|
||||
layout.Rows = rows;
|
||||
layout.Columns = cols;
|
||||
|
||||
int[,] cellChildMap = new int[rows, cols];
|
||||
for (int row = 0; row < rows; row++)
|
||||
{
|
||||
for (int col = 0; col < cols; col++)
|
||||
{
|
||||
cellChildMap[row, col] = CellChildMap[row, col];
|
||||
}
|
||||
}
|
||||
|
||||
layout.CellChildMap = cellChildMap;
|
||||
|
||||
List<int> rowPercents = new List<int>(rows);
|
||||
for (int row = 0; row < rows; row++)
|
||||
{
|
||||
rowPercents.Add(RowPercents[row]);
|
||||
}
|
||||
|
||||
layout.RowPercents = rowPercents;
|
||||
|
||||
List<int> colPercents = new List<int>(cols);
|
||||
for (int col = 0; col < cols; col++)
|
||||
{
|
||||
colPercents.Add(ColumnPercents[col]);
|
||||
}
|
||||
|
||||
layout.ColumnPercents = colPercents;
|
||||
}
|
||||
|
||||
private struct GridLayoutInfo
|
||||
{
|
||||
public int Rows { get; set; }
|
||||
|
||||
public int Columns { get; set; }
|
||||
|
||||
public List<int> RowsPercentage { get; set; }
|
||||
|
||||
public List<int> ColumnsPercentage { get; set; }
|
||||
|
||||
public int[][] CellChildMap { get; set; }
|
||||
}
|
||||
|
||||
private struct GridLayoutJson
|
||||
{
|
||||
public string Uuid { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
|
||||
public GridLayoutInfo Info { get; set; }
|
||||
}
|
||||
|
||||
// PersistData
|
||||
// Implements the LayoutModel.PersistData abstract method
|
||||
protected override void PersistData()
|
||||
{
|
||||
GridLayoutInfo layoutInfo = new GridLayoutInfo
|
||||
{
|
||||
Rows = Rows,
|
||||
Columns = Columns,
|
||||
RowsPercentage = RowPercents,
|
||||
ColumnsPercentage = ColumnPercents,
|
||||
CellChildMap = new int[Rows][],
|
||||
};
|
||||
for (int row = 0; row < Rows; row++)
|
||||
{
|
||||
layoutInfo.CellChildMap[row] = new int[Columns];
|
||||
for (int col = 0; col < Columns; col++)
|
||||
{
|
||||
layoutInfo.CellChildMap[row][col] = CellChildMap[row, col];
|
||||
}
|
||||
}
|
||||
|
||||
GridLayoutJson jsonObj = new GridLayoutJson
|
||||
{
|
||||
Uuid = "{" + Guid.ToString().ToUpper() + "}",
|
||||
Name = Name,
|
||||
Type = ModelTypeID,
|
||||
Info = layoutInfo,
|
||||
};
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
string jsonString = JsonSerializer.Serialize(jsonObj, options);
|
||||
File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowExceptionMessageBox(ErrorPersistingGridLayout, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,443 +1,443 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
public enum LayoutType
|
||||
{
|
||||
Blank = -1,
|
||||
Focus,
|
||||
Columns,
|
||||
Rows,
|
||||
Grid,
|
||||
PriorityGrid,
|
||||
Custom,
|
||||
}
|
||||
|
||||
// Base LayoutModel
|
||||
// Manages common properties and base persistence
|
||||
public abstract class LayoutModel : INotifyPropertyChanged
|
||||
{
|
||||
// Localizable strings
|
||||
private const string ErrorMessageBoxTitle = "FancyZones Editor Exception Handler";
|
||||
private const string ErrorMessageBoxMessage = "Please report the bug to ";
|
||||
private const string ErrorLayoutMalformedData = "Layout '{0}' has malformed data";
|
||||
private const string ErrorSerializingDeletedLayouts = "Error serializing deleted layouts";
|
||||
private const string ErrorLoadingCustomLayouts = "Error loading custom layouts";
|
||||
private const string ErrorApplyingLayout = "Error applying layout";
|
||||
|
||||
// Non-localizable strings
|
||||
private const string NameStr = "name";
|
||||
private const string CustomZoneSetsJsonTag = "custom-zone-sets";
|
||||
private const string TypeJsonTag = "type";
|
||||
private const string UuidJsonTag = "uuid";
|
||||
private const string InfoJsonTag = "info";
|
||||
private const string GridJsonTag = "grid";
|
||||
private const string RowsJsonTag = "rows";
|
||||
private const string ColumnsJsonTag = "columns";
|
||||
private const string RowsPercentageJsonTag = "rows-percentage";
|
||||
private const string ColumnsPercentageJsonTag = "columns-percentage";
|
||||
private const string CellChildMapJsonTag = "cell-child-map";
|
||||
private const string ZonesJsonTag = "zones";
|
||||
private const string CanvasJsonTag = "canvas";
|
||||
private const string RefWidthJsonTag = "ref-width";
|
||||
private const string RefHeightJsonTag = "ref-height";
|
||||
private const string XJsonTag = "X";
|
||||
private const string YJsonTag = "Y";
|
||||
private const string WidthJsonTag = "width";
|
||||
private const string HeightJsonTag = "height";
|
||||
private const string FocusJsonTag = "focus";
|
||||
private const string PriorityGridJsonTag = "priority-grid";
|
||||
private const string CustomJsonTag = "custom";
|
||||
|
||||
private const string PowerToysIssuesLink = "https://aka.ms/powerToysReportBug";
|
||||
|
||||
public static void ShowExceptionMessageBox(string message, Exception exception = null)
|
||||
{
|
||||
string fullMessage = ErrorMessageBoxMessage + PowerToysIssuesLink + " \n" + message;
|
||||
if (exception != null)
|
||||
{
|
||||
fullMessage += ": " + exception.Message;
|
||||
}
|
||||
|
||||
MessageBox.Show(fullMessage, ErrorMessageBoxTitle);
|
||||
}
|
||||
|
||||
protected LayoutModel()
|
||||
{
|
||||
_guid = Guid.NewGuid();
|
||||
Type = LayoutType.Custom;
|
||||
}
|
||||
|
||||
protected LayoutModel(string name)
|
||||
: this()
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
protected LayoutModel(string uuid, string name, LayoutType type)
|
||||
: this()
|
||||
{
|
||||
_guid = Guid.Parse(uuid);
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
protected LayoutModel(string name, LayoutType type)
|
||||
: this(name)
|
||||
{
|
||||
_guid = Guid.NewGuid();
|
||||
Type = type;
|
||||
}
|
||||
|
||||
// Name - the display name for this layout model - is also used as the key in the registry
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_name != value)
|
||||
{
|
||||
_name = value;
|
||||
FirePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _name;
|
||||
|
||||
public LayoutType Type { get; set; }
|
||||
|
||||
public Guid Guid
|
||||
{
|
||||
get
|
||||
{
|
||||
return _guid;
|
||||
}
|
||||
}
|
||||
|
||||
private Guid _guid;
|
||||
|
||||
// IsSelected (not-persisted) - tracks whether or not this LayoutModel is selected in the picker
|
||||
// TODO: once we switch to a picker per monitor, we need to move this state to the view
|
||||
public bool IsSelected
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isSelected;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_isSelected != value)
|
||||
{
|
||||
_isSelected = value;
|
||||
FirePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isSelected;
|
||||
|
||||
// implementation of INotifyPropertyChanged
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
// FirePropertyChanged -- wrapper that calls INPC.PropertyChanged
|
||||
protected virtual void FirePropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
// Removes this Layout from the registry and the loaded CustomModels list
|
||||
public void Delete()
|
||||
{
|
||||
int i = _customModels.IndexOf(this);
|
||||
if (i != -1)
|
||||
{
|
||||
_customModels.RemoveAt(i);
|
||||
_deletedCustomModels.Add(Guid.ToString().ToUpper());
|
||||
}
|
||||
}
|
||||
|
||||
private struct DeletedCustomZoneSetsWrapper
|
||||
{
|
||||
public List<string> DeletedCustomZoneSets { get; set; }
|
||||
}
|
||||
|
||||
public static void SerializeDeletedCustomZoneSets()
|
||||
{
|
||||
DeletedCustomZoneSetsWrapper deletedLayouts = new DeletedCustomZoneSetsWrapper
|
||||
{
|
||||
DeletedCustomZoneSets = _deletedCustomModels,
|
||||
};
|
||||
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
string jsonString = JsonSerializer.Serialize(deletedLayouts, options);
|
||||
File.WriteAllText(Settings.DeletedCustomZoneSetsTmpFile, jsonString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowExceptionMessageBox(ErrorSerializingDeletedLayouts, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Loads all the custom Layouts from tmp file passed by FancyZonesLib
|
||||
public static ObservableCollection<LayoutModel> LoadCustomModels()
|
||||
{
|
||||
_customModels = new ObservableCollection<LayoutModel>();
|
||||
|
||||
try
|
||||
{
|
||||
FileStream inputStream = File.Open(Settings.FancyZonesSettingsFile, FileMode.Open);
|
||||
JsonDocument jsonObject = JsonDocument.Parse(inputStream, options: default);
|
||||
JsonElement.ArrayEnumerator customZoneSetsEnumerator = jsonObject.RootElement.GetProperty(CustomZoneSetsJsonTag).EnumerateArray();
|
||||
|
||||
while (customZoneSetsEnumerator.MoveNext())
|
||||
{
|
||||
var current = customZoneSetsEnumerator.Current;
|
||||
string name = current.GetProperty(NameStr).GetString();
|
||||
string type = current.GetProperty(TypeJsonTag).GetString();
|
||||
string uuid = current.GetProperty(UuidJsonTag).GetString();
|
||||
var info = current.GetProperty(InfoJsonTag);
|
||||
|
||||
if (type.Equals(GridJsonTag))
|
||||
{
|
||||
bool error = false;
|
||||
|
||||
int rows = info.GetProperty(RowsJsonTag).GetInt32();
|
||||
int columns = info.GetProperty(ColumnsJsonTag).GetInt32();
|
||||
|
||||
List<int> rowsPercentage = new List<int>(rows);
|
||||
JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty(RowsPercentageJsonTag).EnumerateArray();
|
||||
|
||||
List<int> columnsPercentage = new List<int>(columns);
|
||||
JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty(ColumnsPercentageJsonTag).EnumerateArray();
|
||||
|
||||
if (rows <= 0 || columns <= 0 || rowsPercentageEnumerator.Count() != rows || columnsPercentageEnumerator.Count() != columns)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
|
||||
while (!error && rowsPercentageEnumerator.MoveNext())
|
||||
{
|
||||
int percentage = rowsPercentageEnumerator.Current.GetInt32();
|
||||
if (percentage <= 0)
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
rowsPercentage.Add(percentage);
|
||||
}
|
||||
|
||||
while (!error && columnsPercentageEnumerator.MoveNext())
|
||||
{
|
||||
int percentage = columnsPercentageEnumerator.Current.GetInt32();
|
||||
if (percentage <= 0)
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
columnsPercentage.Add(percentage);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty(CellChildMapJsonTag).EnumerateArray();
|
||||
int[,] cellChildMap = new int[rows, columns];
|
||||
|
||||
if (cellChildMapRows.Count() != rows)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
|
||||
while (!error && cellChildMapRows.MoveNext())
|
||||
{
|
||||
int j = 0;
|
||||
JsonElement.ArrayEnumerator cellChildMapRowElems = cellChildMapRows.Current.EnumerateArray();
|
||||
if (cellChildMapRowElems.Count() != columns)
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
while (cellChildMapRowElems.MoveNext())
|
||||
{
|
||||
cellChildMap[i, j++] = cellChildMapRowElems.Current.GetInt32();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
ShowExceptionMessageBox(string.Format(ErrorLayoutMalformedData, name));
|
||||
continue;
|
||||
}
|
||||
|
||||
_customModels.Add(new GridLayoutModel(uuid, name, LayoutType.Custom, rows, columns, rowsPercentage, columnsPercentage, cellChildMap));
|
||||
}
|
||||
else if (type.Equals(CanvasJsonTag))
|
||||
{
|
||||
int lastWorkAreaWidth = info.GetProperty(RefWidthJsonTag).GetInt32();
|
||||
int lastWorkAreaHeight = info.GetProperty(RefHeightJsonTag).GetInt32();
|
||||
|
||||
JsonElement.ArrayEnumerator zonesEnumerator = info.GetProperty(ZonesJsonTag).EnumerateArray();
|
||||
IList<Int32Rect> zones = new List<Int32Rect>();
|
||||
|
||||
bool error = false;
|
||||
|
||||
if (lastWorkAreaWidth <= 0 || lastWorkAreaHeight <= 0)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
|
||||
while (!error && zonesEnumerator.MoveNext())
|
||||
{
|
||||
int x = zonesEnumerator.Current.GetProperty(XJsonTag).GetInt32();
|
||||
int y = zonesEnumerator.Current.GetProperty(YJsonTag).GetInt32();
|
||||
int width = zonesEnumerator.Current.GetProperty(WidthJsonTag).GetInt32();
|
||||
int height = zonesEnumerator.Current.GetProperty(HeightJsonTag).GetInt32();
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
zones.Add(new Int32Rect(x, y, width, height));
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
ShowExceptionMessageBox(string.Format(ErrorLayoutMalformedData, name));
|
||||
continue;
|
||||
}
|
||||
|
||||
_customModels.Add(new CanvasLayoutModel(uuid, name, LayoutType.Custom, zones, lastWorkAreaWidth, lastWorkAreaHeight));
|
||||
}
|
||||
}
|
||||
|
||||
inputStream.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowExceptionMessageBox(ErrorLoadingCustomLayouts, ex);
|
||||
return new ObservableCollection<LayoutModel>();
|
||||
}
|
||||
|
||||
return _customModels;
|
||||
}
|
||||
|
||||
private static ObservableCollection<LayoutModel> _customModels = null;
|
||||
private static List<string> _deletedCustomModels = new List<string>();
|
||||
|
||||
// Callbacks that the base LayoutModel makes to derived types
|
||||
protected abstract void PersistData();
|
||||
|
||||
public abstract LayoutModel Clone();
|
||||
|
||||
public void Persist()
|
||||
{
|
||||
PersistData();
|
||||
Apply();
|
||||
}
|
||||
|
||||
private struct ActiveZoneSetWrapper
|
||||
{
|
||||
public string Uuid { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
}
|
||||
|
||||
private struct AppliedZoneSet
|
||||
{
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
public ActiveZoneSetWrapper ActiveZoneset { get; set; }
|
||||
|
||||
public bool EditorShowSpacing { get; set; }
|
||||
|
||||
public int EditorSpacing { get; set; }
|
||||
|
||||
public int EditorZoneCount { get; set; }
|
||||
}
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
ActiveZoneSetWrapper activeZoneSet = new ActiveZoneSetWrapper
|
||||
{
|
||||
Uuid = "{" + Guid.ToString().ToUpper() + "}",
|
||||
};
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case LayoutType.Focus:
|
||||
activeZoneSet.Type = FocusJsonTag;
|
||||
break;
|
||||
case LayoutType.Rows:
|
||||
activeZoneSet.Type = RowsJsonTag;
|
||||
break;
|
||||
case LayoutType.Columns:
|
||||
activeZoneSet.Type = ColumnsJsonTag;
|
||||
break;
|
||||
case LayoutType.Grid:
|
||||
activeZoneSet.Type = GridJsonTag;
|
||||
break;
|
||||
case LayoutType.PriorityGrid:
|
||||
activeZoneSet.Type = PriorityGridJsonTag;
|
||||
break;
|
||||
case LayoutType.Custom:
|
||||
activeZoneSet.Type = CustomJsonTag;
|
||||
break;
|
||||
}
|
||||
|
||||
Settings settings = ((App)Application.Current).ZoneSettings;
|
||||
|
||||
AppliedZoneSet zoneSet = new AppliedZoneSet
|
||||
{
|
||||
DeviceId = Settings.UniqueKey,
|
||||
ActiveZoneset = activeZoneSet,
|
||||
EditorShowSpacing = settings.ShowSpacing,
|
||||
EditorSpacing = settings.Spacing,
|
||||
EditorZoneCount = settings.ZoneCount,
|
||||
};
|
||||
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
string jsonString = JsonSerializer.Serialize(zoneSet, options);
|
||||
File.WriteAllText(Settings.ActiveZoneSetTmpFile, jsonString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowExceptionMessageBox(ErrorApplyingLayout, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
|
||||
namespace FancyZonesEditor.Models
|
||||
{
|
||||
public enum LayoutType
|
||||
{
|
||||
Blank = -1,
|
||||
Focus,
|
||||
Columns,
|
||||
Rows,
|
||||
Grid,
|
||||
PriorityGrid,
|
||||
Custom,
|
||||
}
|
||||
|
||||
// Base LayoutModel
|
||||
// Manages common properties and base persistence
|
||||
public abstract class LayoutModel : INotifyPropertyChanged
|
||||
{
|
||||
// Localizable strings
|
||||
private const string ErrorMessageBoxTitle = "FancyZones Editor Exception Handler";
|
||||
private const string ErrorMessageBoxMessage = "Please report the bug to ";
|
||||
private const string ErrorLayoutMalformedData = "Layout '{0}' has malformed data";
|
||||
private const string ErrorSerializingDeletedLayouts = "Error serializing deleted layouts";
|
||||
private const string ErrorLoadingCustomLayouts = "Error loading custom layouts";
|
||||
private const string ErrorApplyingLayout = "Error applying layout";
|
||||
|
||||
// Non-localizable strings
|
||||
private const string NameStr = "name";
|
||||
private const string CustomZoneSetsJsonTag = "custom-zone-sets";
|
||||
private const string TypeJsonTag = "type";
|
||||
private const string UuidJsonTag = "uuid";
|
||||
private const string InfoJsonTag = "info";
|
||||
private const string GridJsonTag = "grid";
|
||||
private const string RowsJsonTag = "rows";
|
||||
private const string ColumnsJsonTag = "columns";
|
||||
private const string RowsPercentageJsonTag = "rows-percentage";
|
||||
private const string ColumnsPercentageJsonTag = "columns-percentage";
|
||||
private const string CellChildMapJsonTag = "cell-child-map";
|
||||
private const string ZonesJsonTag = "zones";
|
||||
private const string CanvasJsonTag = "canvas";
|
||||
private const string RefWidthJsonTag = "ref-width";
|
||||
private const string RefHeightJsonTag = "ref-height";
|
||||
private const string XJsonTag = "X";
|
||||
private const string YJsonTag = "Y";
|
||||
private const string WidthJsonTag = "width";
|
||||
private const string HeightJsonTag = "height";
|
||||
private const string FocusJsonTag = "focus";
|
||||
private const string PriorityGridJsonTag = "priority-grid";
|
||||
private const string CustomJsonTag = "custom";
|
||||
|
||||
private const string PowerToysIssuesLink = "https://aka.ms/powerToysReportBug";
|
||||
|
||||
public static void ShowExceptionMessageBox(string message, Exception exception = null)
|
||||
{
|
||||
string fullMessage = ErrorMessageBoxMessage + PowerToysIssuesLink + " \n" + message;
|
||||
if (exception != null)
|
||||
{
|
||||
fullMessage += ": " + exception.Message;
|
||||
}
|
||||
|
||||
MessageBox.Show(fullMessage, ErrorMessageBoxTitle);
|
||||
}
|
||||
|
||||
protected LayoutModel()
|
||||
{
|
||||
_guid = Guid.NewGuid();
|
||||
Type = LayoutType.Custom;
|
||||
}
|
||||
|
||||
protected LayoutModel(string name)
|
||||
: this()
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
protected LayoutModel(string uuid, string name, LayoutType type)
|
||||
: this()
|
||||
{
|
||||
_guid = Guid.Parse(uuid);
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
protected LayoutModel(string name, LayoutType type)
|
||||
: this(name)
|
||||
{
|
||||
_guid = Guid.NewGuid();
|
||||
Type = type;
|
||||
}
|
||||
|
||||
// Name - the display name for this layout model - is also used as the key in the registry
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_name != value)
|
||||
{
|
||||
_name = value;
|
||||
FirePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _name;
|
||||
|
||||
public LayoutType Type { get; set; }
|
||||
|
||||
public Guid Guid
|
||||
{
|
||||
get
|
||||
{
|
||||
return _guid;
|
||||
}
|
||||
}
|
||||
|
||||
private Guid _guid;
|
||||
|
||||
// IsSelected (not-persisted) - tracks whether or not this LayoutModel is selected in the picker
|
||||
// TODO: once we switch to a picker per monitor, we need to move this state to the view
|
||||
public bool IsSelected
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isSelected;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_isSelected != value)
|
||||
{
|
||||
_isSelected = value;
|
||||
FirePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isSelected;
|
||||
|
||||
// implementation of INotifyPropertyChanged
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
// FirePropertyChanged -- wrapper that calls INPC.PropertyChanged
|
||||
protected virtual void FirePropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
// Removes this Layout from the registry and the loaded CustomModels list
|
||||
public void Delete()
|
||||
{
|
||||
int i = _customModels.IndexOf(this);
|
||||
if (i != -1)
|
||||
{
|
||||
_customModels.RemoveAt(i);
|
||||
_deletedCustomModels.Add(Guid.ToString().ToUpper());
|
||||
}
|
||||
}
|
||||
|
||||
private struct DeletedCustomZoneSetsWrapper
|
||||
{
|
||||
public List<string> DeletedCustomZoneSets { get; set; }
|
||||
}
|
||||
|
||||
public static void SerializeDeletedCustomZoneSets()
|
||||
{
|
||||
DeletedCustomZoneSetsWrapper deletedLayouts = new DeletedCustomZoneSetsWrapper
|
||||
{
|
||||
DeletedCustomZoneSets = _deletedCustomModels,
|
||||
};
|
||||
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
string jsonString = JsonSerializer.Serialize(deletedLayouts, options);
|
||||
File.WriteAllText(Settings.DeletedCustomZoneSetsTmpFile, jsonString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowExceptionMessageBox(ErrorSerializingDeletedLayouts, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Loads all the custom Layouts from tmp file passed by FancyZonesLib
|
||||
public static ObservableCollection<LayoutModel> LoadCustomModels()
|
||||
{
|
||||
_customModels = new ObservableCollection<LayoutModel>();
|
||||
|
||||
try
|
||||
{
|
||||
FileStream inputStream = File.Open(Settings.FancyZonesSettingsFile, FileMode.Open);
|
||||
JsonDocument jsonObject = JsonDocument.Parse(inputStream, options: default);
|
||||
JsonElement.ArrayEnumerator customZoneSetsEnumerator = jsonObject.RootElement.GetProperty(CustomZoneSetsJsonTag).EnumerateArray();
|
||||
|
||||
while (customZoneSetsEnumerator.MoveNext())
|
||||
{
|
||||
var current = customZoneSetsEnumerator.Current;
|
||||
string name = current.GetProperty(NameStr).GetString();
|
||||
string type = current.GetProperty(TypeJsonTag).GetString();
|
||||
string uuid = current.GetProperty(UuidJsonTag).GetString();
|
||||
var info = current.GetProperty(InfoJsonTag);
|
||||
|
||||
if (type.Equals(GridJsonTag))
|
||||
{
|
||||
bool error = false;
|
||||
|
||||
int rows = info.GetProperty(RowsJsonTag).GetInt32();
|
||||
int columns = info.GetProperty(ColumnsJsonTag).GetInt32();
|
||||
|
||||
List<int> rowsPercentage = new List<int>(rows);
|
||||
JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty(RowsPercentageJsonTag).EnumerateArray();
|
||||
|
||||
List<int> columnsPercentage = new List<int>(columns);
|
||||
JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty(ColumnsPercentageJsonTag).EnumerateArray();
|
||||
|
||||
if (rows <= 0 || columns <= 0 || rowsPercentageEnumerator.Count() != rows || columnsPercentageEnumerator.Count() != columns)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
|
||||
while (!error && rowsPercentageEnumerator.MoveNext())
|
||||
{
|
||||
int percentage = rowsPercentageEnumerator.Current.GetInt32();
|
||||
if (percentage <= 0)
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
rowsPercentage.Add(percentage);
|
||||
}
|
||||
|
||||
while (!error && columnsPercentageEnumerator.MoveNext())
|
||||
{
|
||||
int percentage = columnsPercentageEnumerator.Current.GetInt32();
|
||||
if (percentage <= 0)
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
columnsPercentage.Add(percentage);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty(CellChildMapJsonTag).EnumerateArray();
|
||||
int[,] cellChildMap = new int[rows, columns];
|
||||
|
||||
if (cellChildMapRows.Count() != rows)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
|
||||
while (!error && cellChildMapRows.MoveNext())
|
||||
{
|
||||
int j = 0;
|
||||
JsonElement.ArrayEnumerator cellChildMapRowElems = cellChildMapRows.Current.EnumerateArray();
|
||||
if (cellChildMapRowElems.Count() != columns)
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
while (cellChildMapRowElems.MoveNext())
|
||||
{
|
||||
cellChildMap[i, j++] = cellChildMapRowElems.Current.GetInt32();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
ShowExceptionMessageBox(string.Format(ErrorLayoutMalformedData, name));
|
||||
continue;
|
||||
}
|
||||
|
||||
_customModels.Add(new GridLayoutModel(uuid, name, LayoutType.Custom, rows, columns, rowsPercentage, columnsPercentage, cellChildMap));
|
||||
}
|
||||
else if (type.Equals(CanvasJsonTag))
|
||||
{
|
||||
int lastWorkAreaWidth = info.GetProperty(RefWidthJsonTag).GetInt32();
|
||||
int lastWorkAreaHeight = info.GetProperty(RefHeightJsonTag).GetInt32();
|
||||
|
||||
JsonElement.ArrayEnumerator zonesEnumerator = info.GetProperty(ZonesJsonTag).EnumerateArray();
|
||||
IList<Int32Rect> zones = new List<Int32Rect>();
|
||||
|
||||
bool error = false;
|
||||
|
||||
if (lastWorkAreaWidth <= 0 || lastWorkAreaHeight <= 0)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
|
||||
while (!error && zonesEnumerator.MoveNext())
|
||||
{
|
||||
int x = zonesEnumerator.Current.GetProperty(XJsonTag).GetInt32();
|
||||
int y = zonesEnumerator.Current.GetProperty(YJsonTag).GetInt32();
|
||||
int width = zonesEnumerator.Current.GetProperty(WidthJsonTag).GetInt32();
|
||||
int height = zonesEnumerator.Current.GetProperty(HeightJsonTag).GetInt32();
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
zones.Add(new Int32Rect(x, y, width, height));
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
ShowExceptionMessageBox(string.Format(ErrorLayoutMalformedData, name));
|
||||
continue;
|
||||
}
|
||||
|
||||
_customModels.Add(new CanvasLayoutModel(uuid, name, LayoutType.Custom, zones, lastWorkAreaWidth, lastWorkAreaHeight));
|
||||
}
|
||||
}
|
||||
|
||||
inputStream.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowExceptionMessageBox(ErrorLoadingCustomLayouts, ex);
|
||||
return new ObservableCollection<LayoutModel>();
|
||||
}
|
||||
|
||||
return _customModels;
|
||||
}
|
||||
|
||||
private static ObservableCollection<LayoutModel> _customModels = null;
|
||||
private static List<string> _deletedCustomModels = new List<string>();
|
||||
|
||||
// Callbacks that the base LayoutModel makes to derived types
|
||||
protected abstract void PersistData();
|
||||
|
||||
public abstract LayoutModel Clone();
|
||||
|
||||
public void Persist()
|
||||
{
|
||||
PersistData();
|
||||
Apply();
|
||||
}
|
||||
|
||||
private struct ActiveZoneSetWrapper
|
||||
{
|
||||
public string Uuid { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
}
|
||||
|
||||
private struct AppliedZoneSet
|
||||
{
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
public ActiveZoneSetWrapper ActiveZoneset { get; set; }
|
||||
|
||||
public bool EditorShowSpacing { get; set; }
|
||||
|
||||
public int EditorSpacing { get; set; }
|
||||
|
||||
public int EditorZoneCount { get; set; }
|
||||
}
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
ActiveZoneSetWrapper activeZoneSet = new ActiveZoneSetWrapper
|
||||
{
|
||||
Uuid = "{" + Guid.ToString().ToUpper() + "}",
|
||||
};
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case LayoutType.Focus:
|
||||
activeZoneSet.Type = FocusJsonTag;
|
||||
break;
|
||||
case LayoutType.Rows:
|
||||
activeZoneSet.Type = RowsJsonTag;
|
||||
break;
|
||||
case LayoutType.Columns:
|
||||
activeZoneSet.Type = ColumnsJsonTag;
|
||||
break;
|
||||
case LayoutType.Grid:
|
||||
activeZoneSet.Type = GridJsonTag;
|
||||
break;
|
||||
case LayoutType.PriorityGrid:
|
||||
activeZoneSet.Type = PriorityGridJsonTag;
|
||||
break;
|
||||
case LayoutType.Custom:
|
||||
activeZoneSet.Type = CustomJsonTag;
|
||||
break;
|
||||
}
|
||||
|
||||
Settings settings = ((App)Application.Current).ZoneSettings;
|
||||
|
||||
AppliedZoneSet zoneSet = new AppliedZoneSet
|
||||
{
|
||||
DeviceId = Settings.UniqueKey,
|
||||
ActiveZoneset = activeZoneSet,
|
||||
EditorShowSpacing = settings.ShowSpacing,
|
||||
EditorSpacing = settings.Spacing,
|
||||
EditorZoneCount = settings.ZoneCount,
|
||||
};
|
||||
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
string jsonString = JsonSerializer.Serialize(zoneSet, options);
|
||||
File.WriteAllText(Settings.ActiveZoneSetTmpFile, jsonString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowExceptionMessageBox(ErrorApplyingLayout, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,189 +1,189 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <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 FancyZonesEditor.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", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public 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)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FancyZonesEditor.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)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add new zone.
|
||||
/// </summary>
|
||||
public static string Add_zone {
|
||||
get {
|
||||
return ResourceManager.GetString("Add_zone", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Apply.
|
||||
/// </summary>
|
||||
public static string Apply {
|
||||
get {
|
||||
return ResourceManager.GetString("Apply", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cancel.
|
||||
/// </summary>
|
||||
public static string Cancel {
|
||||
get {
|
||||
return ResourceManager.GetString("Cancel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Choose your layout for this desktop.
|
||||
/// </summary>
|
||||
public static string Choose_Layout {
|
||||
get {
|
||||
return ResourceManager.GetString("Choose_Layout", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Custom.
|
||||
/// </summary>
|
||||
public static string Custom {
|
||||
get {
|
||||
return ResourceManager.GetString("Custom", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Custom layout creator.
|
||||
/// </summary>
|
||||
public static string Custom_Layout_Creator {
|
||||
get {
|
||||
return ResourceManager.GetString("Custom_Layout_Creator", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Custom table layout creator.
|
||||
/// </summary>
|
||||
public static string Custom_Table_Layout {
|
||||
get {
|
||||
return ResourceManager.GetString("Custom_Table_Layout", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit selected layout.
|
||||
/// </summary>
|
||||
public static string Edit_Selected_Layout {
|
||||
get {
|
||||
return ResourceManager.GetString("Edit_Selected_Layout", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name.
|
||||
/// </summary>
|
||||
public static string Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Note: Hold down Shift Key to change orientation of splitter. To merge zones, select the zones and click "merge"..
|
||||
/// </summary>
|
||||
public static string Note_Custom_Table {
|
||||
get {
|
||||
return ResourceManager.GetString("Note_Custom_Table", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Save and apply.
|
||||
/// </summary>
|
||||
public static string Save_Apply {
|
||||
get {
|
||||
return ResourceManager.GetString("Save_Apply", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show space around zones.
|
||||
/// </summary>
|
||||
public static string Show_Space_Zones {
|
||||
get {
|
||||
return ResourceManager.GetString("Show_Space_Zones", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Space around zones.
|
||||
/// </summary>
|
||||
public static string Space_Around_Zones {
|
||||
get {
|
||||
return ResourceManager.GetString("Space_Around_Zones", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Templates.
|
||||
/// </summary>
|
||||
public static string Templates {
|
||||
get {
|
||||
return ResourceManager.GetString("Templates", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// <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 FancyZonesEditor.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", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public 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)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FancyZonesEditor.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)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add new zone.
|
||||
/// </summary>
|
||||
public static string Add_zone {
|
||||
get {
|
||||
return ResourceManager.GetString("Add_zone", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Apply.
|
||||
/// </summary>
|
||||
public static string Apply {
|
||||
get {
|
||||
return ResourceManager.GetString("Apply", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cancel.
|
||||
/// </summary>
|
||||
public static string Cancel {
|
||||
get {
|
||||
return ResourceManager.GetString("Cancel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Choose your layout for this desktop.
|
||||
/// </summary>
|
||||
public static string Choose_Layout {
|
||||
get {
|
||||
return ResourceManager.GetString("Choose_Layout", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Custom.
|
||||
/// </summary>
|
||||
public static string Custom {
|
||||
get {
|
||||
return ResourceManager.GetString("Custom", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Custom layout creator.
|
||||
/// </summary>
|
||||
public static string Custom_Layout_Creator {
|
||||
get {
|
||||
return ResourceManager.GetString("Custom_Layout_Creator", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Custom table layout creator.
|
||||
/// </summary>
|
||||
public static string Custom_Table_Layout {
|
||||
get {
|
||||
return ResourceManager.GetString("Custom_Table_Layout", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit selected layout.
|
||||
/// </summary>
|
||||
public static string Edit_Selected_Layout {
|
||||
get {
|
||||
return ResourceManager.GetString("Edit_Selected_Layout", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name.
|
||||
/// </summary>
|
||||
public static string Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Note: Hold down Shift Key to change orientation of splitter. To merge zones, select the zones and click "merge"..
|
||||
/// </summary>
|
||||
public static string Note_Custom_Table {
|
||||
get {
|
||||
return ResourceManager.GetString("Note_Custom_Table", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Save and apply.
|
||||
/// </summary>
|
||||
public static string Save_Apply {
|
||||
get {
|
||||
return ResourceManager.GetString("Save_Apply", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show space around zones.
|
||||
/// </summary>
|
||||
public static string Show_Space_Zones {
|
||||
get {
|
||||
return ResourceManager.GetString("Show_Space_Zones", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Space around zones.
|
||||
/// </summary>
|
||||
public static string Space_Around_Zones {
|
||||
get {
|
||||
return ResourceManager.GetString("Space_Around_Zones", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Templates.
|
||||
/// </summary>
|
||||
public static string Templates {
|
||||
get {
|
||||
return ResourceManager.GetString("Templates", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <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 FancyZonesEditor.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.1.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// <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 FancyZonesEditor.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.1.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ namespace FancyZonesEditor
|
||||
Percent = percent;
|
||||
}
|
||||
|
||||
public RowColInfo(RowColInfo other)
|
||||
{
|
||||
Percent = other.Percent;
|
||||
Extent = other.Extent;
|
||||
Start = other.Start;
|
||||
End = other.End;
|
||||
public RowColInfo(RowColInfo other)
|
||||
{
|
||||
Percent = other.Percent;
|
||||
Extent = other.Extent;
|
||||
Start = other.Start;
|
||||
End = other.End;
|
||||
}
|
||||
|
||||
public RowColInfo(int index, int count)
|
||||
@@ -42,9 +42,9 @@ namespace FancyZonesEditor
|
||||
return Extent;
|
||||
}
|
||||
|
||||
public void RecalculatePercent(double newTotalExtent)
|
||||
{
|
||||
Percent = (int)(Extent * _multiplier / newTotalExtent);
|
||||
public void RecalculatePercent(double newTotalExtent)
|
||||
{
|
||||
Percent = (int)(Extent * _multiplier / newTotalExtent);
|
||||
}
|
||||
|
||||
public RowColInfo[] Split(double offset, double space)
|
||||
@@ -54,9 +54,9 @@ namespace FancyZonesEditor
|
||||
double totalExtent = Extent * _multiplier / Percent;
|
||||
totalExtent -= space;
|
||||
|
||||
int percent0 = (int)(offset * _multiplier / totalExtent);
|
||||
int percent1 = (int)((Extent - space - offset) * _multiplier / totalExtent);
|
||||
|
||||
int percent0 = (int)(offset * _multiplier / totalExtent);
|
||||
int percent1 = (int)((Extent - space - offset) * _multiplier / totalExtent);
|
||||
|
||||
info[0] = new RowColInfo(percent0);
|
||||
info[1] = new RowColInfo(percent1);
|
||||
|
||||
|
||||
@@ -2,32 +2,32 @@
|
||||
// The Brice Lambson licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using ImageResizer.Models;
|
||||
using ImageResizer.Test;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Extensions;
|
||||
|
||||
namespace ImageResizer.Properties
|
||||
{
|
||||
public class SettingsTests : IClassFixture<AppFixture>, IDisposable
|
||||
{
|
||||
public SettingsTests()
|
||||
{
|
||||
// Change settings.json path to a temp file
|
||||
Settings.SettingsPath = ".\\test_settings.json";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
public SettingsTests()
|
||||
{
|
||||
// Change settings.json path to a temp file
|
||||
Settings.SettingsPath = ".\\test_settings.json";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (System.IO.File.Exists(Settings.SettingsPath))
|
||||
{
|
||||
System.IO.File.Delete(Settings.SettingsPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
|
||||
|
||||
using ImageResizer.Properties;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ImageResizer.Models
|
||||
{
|
||||
public class CustomSize : ResizeSize
|
||||
@@ -14,8 +14,8 @@ namespace ImageResizer.Models
|
||||
{
|
||||
get => Resources.Input_Custom;
|
||||
set { /* no-op */ }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public CustomSize(ResizeFit fit, double width, double height, ResizeUnit unit)
|
||||
{
|
||||
Fit = fit;
|
||||
|
||||
@@ -6,8 +6,8 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using GalaSoft.MvvmLight;
|
||||
using ImageResizer.Properties;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ImageResizer.Models
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
@@ -31,18 +31,18 @@ namespace ImageResizer.Models
|
||||
["$phone$"] = Resources.Phone,
|
||||
};
|
||||
|
||||
public ResizeSize(string name, ResizeFit fit, double width, double height, ResizeUnit unit)
|
||||
{
|
||||
Name = name;
|
||||
Fit = fit;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Unit = unit;
|
||||
public ResizeSize(string name, ResizeFit fit, double width, double height, ResizeUnit unit)
|
||||
{
|
||||
Name = name;
|
||||
Fit = fit;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Unit = unit;
|
||||
}
|
||||
|
||||
public ResizeSize()
|
||||
{
|
||||
}
|
||||
public ResizeSize()
|
||||
{
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public virtual string Name
|
||||
|
||||
@@ -1,196 +1,196 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using Mages.Core;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Calculator
|
||||
{
|
||||
public class Main : IPlugin, IPluginI18n, IDisposable
|
||||
{
|
||||
private static readonly Regex RegValidExpressChar = new Regex(
|
||||
@"^(" +
|
||||
@"ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|" +
|
||||
@"sin|cos|tan|arcsin|arccos|arctan|" +
|
||||
@"eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|" +
|
||||
@"bin2dec|hex2dec|oct2dec|" +
|
||||
@"==|~=|&&|\|\||" +
|
||||
@"[ei]|[0-9]|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
|
||||
@")+$", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex RegBrackets = new Regex(@"[\(\)\[\]]", RegexOptions.Compiled);
|
||||
private static readonly Engine MagesEngine = new Engine();
|
||||
|
||||
private PluginInitContext Context { get; set; }
|
||||
|
||||
private string IconPath { get; set; }
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(query));
|
||||
}
|
||||
|
||||
if (query.Search.Length <= 2 // don't affect when user only input "e" or "i" keyword
|
||||
|| !RegValidExpressChar.IsMatch(query.Search)
|
||||
|| !IsBracketComplete(query.Search))
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = MagesEngine.Interpret(query.Search);
|
||||
|
||||
// This could happen for some incorrect queries, like pi(2)
|
||||
if (result == null)
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
if (result.ToString() == "NaN")
|
||||
{
|
||||
result = Context.API.GetTranslation("wox_plugin_calculator_not_a_number");
|
||||
}
|
||||
|
||||
if (result is Function)
|
||||
{
|
||||
result = Context.API.GetTranslation("wox_plugin_calculator_expression_not_complete");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result?.ToString()))
|
||||
{
|
||||
var roundedResult = Math.Round(Convert.ToDecimal(result, CultureInfo.CurrentCulture), 10, MidpointRounding.AwayFromZero);
|
||||
|
||||
return new List<Result>
|
||||
{
|
||||
new Result
|
||||
{
|
||||
Title = roundedResult.ToString(CultureInfo.CurrentCulture),
|
||||
IcoPath = IconPath,
|
||||
Score = 300,
|
||||
SubTitle = Context.API.GetTranslation("wox_plugin_calculator_copy_number_to_clipboard"),
|
||||
Action = c =>
|
||||
{
|
||||
var ret = false;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(result.ToString());
|
||||
ret = true;
|
||||
}
|
||||
catch (ExternalException)
|
||||
{
|
||||
MessageBox.Show("Copy failed, please try later");
|
||||
}
|
||||
});
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
return ret;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
} // We want to keep the process alive if any the mages library throws any exceptions.
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception e)
|
||||
#pragma warning restore CA1031 // Do not catch general exception types
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Calculator.Main.Query|Exception when query for <{query}>", e);
|
||||
}
|
||||
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
private static bool IsBracketComplete(string query)
|
||||
{
|
||||
var matchs = RegBrackets.Matches(query);
|
||||
var leftBracketCount = 0;
|
||||
foreach (Match match in matchs)
|
||||
{
|
||||
if (match.Value == "(" || match.Value == "[")
|
||||
{
|
||||
leftBracketCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftBracketCount--;
|
||||
}
|
||||
}
|
||||
|
||||
return leftBracketCount == 0;
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(context));
|
||||
}
|
||||
|
||||
Context = context;
|
||||
Context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateIconPath(Context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
// Todo : Update with theme based IconPath
|
||||
private void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
IconPath = "Images/calculator.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
IconPath = "Images/calculator.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
{
|
||||
UpdateIconPath(newTheme);
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return Context.API.GetTranslation("wox_plugin_calculator_plugin_name");
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return Context.API.GetTranslation("wox_plugin_calculator_plugin_description");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Context.API.ThemeChanged -= OnThemeChanged;
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using Mages.Core;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Calculator
|
||||
{
|
||||
public class Main : IPlugin, IPluginI18n, IDisposable
|
||||
{
|
||||
private static readonly Regex RegValidExpressChar = new Regex(
|
||||
@"^(" +
|
||||
@"ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|" +
|
||||
@"sin|cos|tan|arcsin|arccos|arctan|" +
|
||||
@"eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|" +
|
||||
@"bin2dec|hex2dec|oct2dec|" +
|
||||
@"==|~=|&&|\|\||" +
|
||||
@"[ei]|[0-9]|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
|
||||
@")+$", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex RegBrackets = new Regex(@"[\(\)\[\]]", RegexOptions.Compiled);
|
||||
private static readonly Engine MagesEngine = new Engine();
|
||||
|
||||
private PluginInitContext Context { get; set; }
|
||||
|
||||
private string IconPath { get; set; }
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(query));
|
||||
}
|
||||
|
||||
if (query.Search.Length <= 2 // don't affect when user only input "e" or "i" keyword
|
||||
|| !RegValidExpressChar.IsMatch(query.Search)
|
||||
|| !IsBracketComplete(query.Search))
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = MagesEngine.Interpret(query.Search);
|
||||
|
||||
// This could happen for some incorrect queries, like pi(2)
|
||||
if (result == null)
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
if (result.ToString() == "NaN")
|
||||
{
|
||||
result = Context.API.GetTranslation("wox_plugin_calculator_not_a_number");
|
||||
}
|
||||
|
||||
if (result is Function)
|
||||
{
|
||||
result = Context.API.GetTranslation("wox_plugin_calculator_expression_not_complete");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result?.ToString()))
|
||||
{
|
||||
var roundedResult = Math.Round(Convert.ToDecimal(result, CultureInfo.CurrentCulture), 10, MidpointRounding.AwayFromZero);
|
||||
|
||||
return new List<Result>
|
||||
{
|
||||
new Result
|
||||
{
|
||||
Title = roundedResult.ToString(CultureInfo.CurrentCulture),
|
||||
IcoPath = IconPath,
|
||||
Score = 300,
|
||||
SubTitle = Context.API.GetTranslation("wox_plugin_calculator_copy_number_to_clipboard"),
|
||||
Action = c =>
|
||||
{
|
||||
var ret = false;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(result.ToString());
|
||||
ret = true;
|
||||
}
|
||||
catch (ExternalException)
|
||||
{
|
||||
MessageBox.Show("Copy failed, please try later");
|
||||
}
|
||||
});
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
return ret;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
} // We want to keep the process alive if any the mages library throws any exceptions.
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception e)
|
||||
#pragma warning restore CA1031 // Do not catch general exception types
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Calculator.Main.Query|Exception when query for <{query}>", e);
|
||||
}
|
||||
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
private static bool IsBracketComplete(string query)
|
||||
{
|
||||
var matchs = RegBrackets.Matches(query);
|
||||
var leftBracketCount = 0;
|
||||
foreach (Match match in matchs)
|
||||
{
|
||||
if (match.Value == "(" || match.Value == "[")
|
||||
{
|
||||
leftBracketCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftBracketCount--;
|
||||
}
|
||||
}
|
||||
|
||||
return leftBracketCount == 0;
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(context));
|
||||
}
|
||||
|
||||
Context = context;
|
||||
Context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateIconPath(Context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
// Todo : Update with theme based IconPath
|
||||
private void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
IconPath = "Images/calculator.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
IconPath = "Images/calculator.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
{
|
||||
UpdateIconPath(newTheme);
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return Context.API.GetTranslation("wox_plugin_calculator_plugin_name");
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return Context.API.GetTranslation("wox_plugin_calculator_plugin_description");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Context.API.ThemeChanged -= OnThemeChanged;
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,107 +1,107 @@
|
||||
// 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;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.Plugin.Calculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to convert all numbers in a text from one culture format to another.
|
||||
/// </summary>
|
||||
public class NumberTranslator
|
||||
{
|
||||
private readonly CultureInfo sourceCulture;
|
||||
private readonly CultureInfo targetCulture;
|
||||
private readonly Regex splitRegexForSource;
|
||||
private readonly Regex splitRegexForTarget;
|
||||
|
||||
private NumberTranslator(CultureInfo sourceCulture, CultureInfo targetCulture)
|
||||
{
|
||||
this.sourceCulture = sourceCulture;
|
||||
this.targetCulture = targetCulture;
|
||||
|
||||
splitRegexForSource = GetSplitRegex(this.sourceCulture);
|
||||
splitRegexForTarget = GetSplitRegex(this.targetCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="NumberTranslator"/> - returns null if no number conversion
|
||||
/// is required between the cultures.
|
||||
/// </summary>
|
||||
/// <param name="sourceCulture">source culture</param>
|
||||
/// <param name="targetCulture">target culture</param>
|
||||
/// <returns>Number translator for target culture</returns>
|
||||
public static NumberTranslator Create(CultureInfo sourceCulture, CultureInfo targetCulture)
|
||||
{
|
||||
if (sourceCulture == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(sourceCulture));
|
||||
}
|
||||
|
||||
if (targetCulture == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(sourceCulture));
|
||||
}
|
||||
|
||||
bool conversionRequired = sourceCulture.NumberFormat.NumberDecimalSeparator != targetCulture.NumberFormat.NumberDecimalSeparator
|
||||
|| sourceCulture.NumberFormat.PercentGroupSeparator != targetCulture.NumberFormat.PercentGroupSeparator
|
||||
|| sourceCulture.NumberFormat.NumberGroupSizes != targetCulture.NumberFormat.NumberGroupSizes;
|
||||
return conversionRequired
|
||||
? new NumberTranslator(sourceCulture, targetCulture)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translate from source to target culture.
|
||||
/// </summary>
|
||||
/// <param name="input">input string to translate</param>
|
||||
/// <returns>translated string</returns>
|
||||
public string Translate(string input)
|
||||
{
|
||||
return Translate(input, sourceCulture, targetCulture, splitRegexForSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translate from target to source culture.
|
||||
/// </summary>
|
||||
/// <param name="input">input string to translate back to source culture</param>
|
||||
/// <returns>source culture string</returns>
|
||||
public string TranslateBack(string input)
|
||||
{
|
||||
return Translate(input, targetCulture, sourceCulture, splitRegexForTarget);
|
||||
}
|
||||
|
||||
private static string Translate(string input, CultureInfo cultureFrom, CultureInfo cultureTo, Regex splitRegex)
|
||||
{
|
||||
var outputBuilder = new StringBuilder();
|
||||
|
||||
string[] tokens = splitRegex.Split(input);
|
||||
foreach (string token in tokens)
|
||||
{
|
||||
decimal number;
|
||||
outputBuilder.Append(
|
||||
decimal.TryParse(token, NumberStyles.Number, cultureFrom, out number)
|
||||
? number.ToString(cultureTo)
|
||||
: token);
|
||||
}
|
||||
|
||||
return outputBuilder.ToString();
|
||||
}
|
||||
|
||||
private static Regex GetSplitRegex(CultureInfo culture)
|
||||
{
|
||||
var splitPattern = $"((?:\\d|{Regex.Escape(culture.NumberFormat.NumberDecimalSeparator)}";
|
||||
if (!string.IsNullOrEmpty(culture.NumberFormat.NumberGroupSeparator))
|
||||
{
|
||||
splitPattern += $"|{Regex.Escape(culture.NumberFormat.NumberGroupSeparator)}";
|
||||
}
|
||||
|
||||
splitPattern += ")+)";
|
||||
return new Regex(splitPattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.Plugin.Calculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to convert all numbers in a text from one culture format to another.
|
||||
/// </summary>
|
||||
public class NumberTranslator
|
||||
{
|
||||
private readonly CultureInfo sourceCulture;
|
||||
private readonly CultureInfo targetCulture;
|
||||
private readonly Regex splitRegexForSource;
|
||||
private readonly Regex splitRegexForTarget;
|
||||
|
||||
private NumberTranslator(CultureInfo sourceCulture, CultureInfo targetCulture)
|
||||
{
|
||||
this.sourceCulture = sourceCulture;
|
||||
this.targetCulture = targetCulture;
|
||||
|
||||
splitRegexForSource = GetSplitRegex(this.sourceCulture);
|
||||
splitRegexForTarget = GetSplitRegex(this.targetCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="NumberTranslator"/> - returns null if no number conversion
|
||||
/// is required between the cultures.
|
||||
/// </summary>
|
||||
/// <param name="sourceCulture">source culture</param>
|
||||
/// <param name="targetCulture">target culture</param>
|
||||
/// <returns>Number translator for target culture</returns>
|
||||
public static NumberTranslator Create(CultureInfo sourceCulture, CultureInfo targetCulture)
|
||||
{
|
||||
if (sourceCulture == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(sourceCulture));
|
||||
}
|
||||
|
||||
if (targetCulture == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(sourceCulture));
|
||||
}
|
||||
|
||||
bool conversionRequired = sourceCulture.NumberFormat.NumberDecimalSeparator != targetCulture.NumberFormat.NumberDecimalSeparator
|
||||
|| sourceCulture.NumberFormat.PercentGroupSeparator != targetCulture.NumberFormat.PercentGroupSeparator
|
||||
|| sourceCulture.NumberFormat.NumberGroupSizes != targetCulture.NumberFormat.NumberGroupSizes;
|
||||
return conversionRequired
|
||||
? new NumberTranslator(sourceCulture, targetCulture)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translate from source to target culture.
|
||||
/// </summary>
|
||||
/// <param name="input">input string to translate</param>
|
||||
/// <returns>translated string</returns>
|
||||
public string Translate(string input)
|
||||
{
|
||||
return Translate(input, sourceCulture, targetCulture, splitRegexForSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translate from target to source culture.
|
||||
/// </summary>
|
||||
/// <param name="input">input string to translate back to source culture</param>
|
||||
/// <returns>source culture string</returns>
|
||||
public string TranslateBack(string input)
|
||||
{
|
||||
return Translate(input, targetCulture, sourceCulture, splitRegexForTarget);
|
||||
}
|
||||
|
||||
private static string Translate(string input, CultureInfo cultureFrom, CultureInfo cultureTo, Regex splitRegex)
|
||||
{
|
||||
var outputBuilder = new StringBuilder();
|
||||
|
||||
string[] tokens = splitRegex.Split(input);
|
||||
foreach (string token in tokens)
|
||||
{
|
||||
decimal number;
|
||||
outputBuilder.Append(
|
||||
decimal.TryParse(token, NumberStyles.Number, cultureFrom, out number)
|
||||
? number.ToString(cultureTo)
|
||||
: token);
|
||||
}
|
||||
|
||||
return outputBuilder.ToString();
|
||||
}
|
||||
|
||||
private static Regex GetSplitRegex(CultureInfo culture)
|
||||
{
|
||||
var splitPattern = $"((?:\\d|{Regex.Escape(culture.NumberFormat.NumberDecimalSeparator)}";
|
||||
if (!string.IsNullOrEmpty(culture.NumberFormat.NumberGroupSeparator))
|
||||
{
|
||||
splitPattern += $"|{Regex.Escape(culture.NumberFormat.NumberGroupSeparator)}";
|
||||
}
|
||||
|
||||
splitPattern += ")+)";
|
||||
return new Regex(splitPattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,142 +1,142 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Folder
|
||||
{
|
||||
internal class ContextMenuLoader : IContextMenu
|
||||
{
|
||||
private readonly PluginInitContext _context;
|
||||
|
||||
public ContextMenuLoader(PluginInitContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exception")]
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
var contextMenus = new List<ContextMenuResult>();
|
||||
if (selectedResult.ContextData is SearchResult record)
|
||||
{
|
||||
if (record.Type == ResultType.File)
|
||||
{
|
||||
contextMenus.Add(CreateOpenContainingFolderResult(record));
|
||||
}
|
||||
|
||||
var icoPath = (record.Type == ResultType.File) ? Main.FileImagePath : Main.FolderImagePath;
|
||||
contextMenus.Add(new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_folder_copy_path"),
|
||||
Glyph = "\xE8C8",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control,
|
||||
Action = (context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(record.FullPath);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = "Fail to set text in clipboard";
|
||||
LogException(message, e);
|
||||
_context.API.ShowMsg(message);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
contextMenus.Add(new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_folder_open_in_console"),
|
||||
Glyph = "\xE756",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
|
||||
Action = (context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (record.Type == ResultType.File)
|
||||
{
|
||||
Helper.OpenInConsole(Path.GetDirectoryName(record.FullPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
Helper.OpenInConsole(record.FullPath);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenuLoader.LoadContextMenus| Failed to open {record.FullPath} in console, {e.Message}", e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return contextMenus;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exception")]
|
||||
private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record)
|
||||
{
|
||||
return new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_folder_open_containing_folder"),
|
||||
Glyph = "\xE838",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.E,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start("explorer.exe", $" /select,\"{record.FullPath}\"");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"Fail to open file at {record.FullPath}";
|
||||
LogException(message, e);
|
||||
_context.API.ShowMsg(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static void LogException(string message, Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenu|{message}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ResultType
|
||||
{
|
||||
Volume,
|
||||
Folder,
|
||||
File,
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Folder
|
||||
{
|
||||
internal class ContextMenuLoader : IContextMenu
|
||||
{
|
||||
private readonly PluginInitContext _context;
|
||||
|
||||
public ContextMenuLoader(PluginInitContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exception")]
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
var contextMenus = new List<ContextMenuResult>();
|
||||
if (selectedResult.ContextData is SearchResult record)
|
||||
{
|
||||
if (record.Type == ResultType.File)
|
||||
{
|
||||
contextMenus.Add(CreateOpenContainingFolderResult(record));
|
||||
}
|
||||
|
||||
var icoPath = (record.Type == ResultType.File) ? Main.FileImagePath : Main.FolderImagePath;
|
||||
contextMenus.Add(new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_folder_copy_path"),
|
||||
Glyph = "\xE8C8",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control,
|
||||
Action = (context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(record.FullPath);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = "Fail to set text in clipboard";
|
||||
LogException(message, e);
|
||||
_context.API.ShowMsg(message);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
contextMenus.Add(new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_folder_open_in_console"),
|
||||
Glyph = "\xE756",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
|
||||
Action = (context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (record.Type == ResultType.File)
|
||||
{
|
||||
Helper.OpenInConsole(Path.GetDirectoryName(record.FullPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
Helper.OpenInConsole(record.FullPath);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenuLoader.LoadContextMenus| Failed to open {record.FullPath} in console, {e.Message}", e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return contextMenus;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exception")]
|
||||
private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record)
|
||||
{
|
||||
return new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_folder_open_containing_folder"),
|
||||
Glyph = "\xE838",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.E,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start("explorer.exe", $" /select,\"{record.FullPath}\"");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"Fail to open file at {record.FullPath}";
|
||||
LogException(message, e);
|
||||
_context.API.ShowMsg(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static void LogException(string message, Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenu|{message}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ResultType
|
||||
{
|
||||
Volume,
|
||||
Folder,
|
||||
File,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +1,127 @@
|
||||
// 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;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using Wox.Plugin;
|
||||
using DataFormats = System.Windows.DataFormats;
|
||||
using DragDropEffects = System.Windows.DragDropEffects;
|
||||
using DragEventArgs = System.Windows.DragEventArgs;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
|
||||
namespace Microsoft.Plugin.Folder
|
||||
{
|
||||
public partial class FileSystemSettings
|
||||
{
|
||||
private IPublicAPI _woxAPI;
|
||||
private FolderSettings _settings;
|
||||
|
||||
public FileSystemSettings(IPublicAPI woxAPI, FolderSettings settings)
|
||||
{
|
||||
_woxAPI = woxAPI;
|
||||
InitializeComponent();
|
||||
_settings = settings ?? throw new ArgumentNullException(paramName: nameof(settings));
|
||||
lbxFolders.ItemsSource = _settings.FolderLinks;
|
||||
}
|
||||
|
||||
private void BtnDelete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (lbxFolders.SelectedItem is FolderLink selectedFolder)
|
||||
{
|
||||
string msg = string.Format(CultureInfo.InvariantCulture, _woxAPI.GetTranslation("wox_plugin_folder_delete_folder_link"), selectedFolder.Path);
|
||||
|
||||
if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
_settings.FolderLinks.Remove(selectedFolder);
|
||||
lbxFolders.Items.Refresh();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string warning = _woxAPI.GetTranslation("wox_plugin_folder_select_folder_link_warning");
|
||||
MessageBox.Show(warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (lbxFolders.SelectedItem is FolderLink selectedFolder)
|
||||
{
|
||||
using (var folderBrowserDialog = new FolderBrowserDialog())
|
||||
{
|
||||
folderBrowserDialog.SelectedPath = selectedFolder.Path;
|
||||
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
var link = _settings.FolderLinks.First(x => x.Path == selectedFolder.Path);
|
||||
link.Path = folderBrowserDialog.SelectedPath;
|
||||
}
|
||||
|
||||
lbxFolders.Items.Refresh();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string warning = _woxAPI.GetTranslation("wox_plugin_folder_select_folder_link_warning");
|
||||
MessageBox.Show(warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
using (var folderBrowserDialog = new FolderBrowserDialog())
|
||||
{
|
||||
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
var newFolder = new FolderLink
|
||||
{
|
||||
Path = folderBrowserDialog.SelectedPath,
|
||||
};
|
||||
|
||||
_settings.FolderLinks.Add(newFolder);
|
||||
}
|
||||
|
||||
lbxFolders.Items.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void LbxFolders_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||||
|
||||
if (files != null && files.Any())
|
||||
{
|
||||
foreach (string s in files)
|
||||
{
|
||||
if (Directory.Exists(s))
|
||||
{
|
||||
var newFolder = new FolderLink
|
||||
{
|
||||
Path = s,
|
||||
};
|
||||
|
||||
_settings.FolderLinks.Add(newFolder);
|
||||
}
|
||||
|
||||
lbxFolders.Items.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LbxFolders_DragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
{
|
||||
e.Effects = DragDropEffects.Link;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Effects = DragDropEffects.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using Wox.Plugin;
|
||||
using DataFormats = System.Windows.DataFormats;
|
||||
using DragDropEffects = System.Windows.DragDropEffects;
|
||||
using DragEventArgs = System.Windows.DragEventArgs;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
|
||||
namespace Microsoft.Plugin.Folder
|
||||
{
|
||||
public partial class FileSystemSettings
|
||||
{
|
||||
private IPublicAPI _woxAPI;
|
||||
private FolderSettings _settings;
|
||||
|
||||
public FileSystemSettings(IPublicAPI woxAPI, FolderSettings settings)
|
||||
{
|
||||
_woxAPI = woxAPI;
|
||||
InitializeComponent();
|
||||
_settings = settings ?? throw new ArgumentNullException(paramName: nameof(settings));
|
||||
lbxFolders.ItemsSource = _settings.FolderLinks;
|
||||
}
|
||||
|
||||
private void BtnDelete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (lbxFolders.SelectedItem is FolderLink selectedFolder)
|
||||
{
|
||||
string msg = string.Format(CultureInfo.InvariantCulture, _woxAPI.GetTranslation("wox_plugin_folder_delete_folder_link"), selectedFolder.Path);
|
||||
|
||||
if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
_settings.FolderLinks.Remove(selectedFolder);
|
||||
lbxFolders.Items.Refresh();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string warning = _woxAPI.GetTranslation("wox_plugin_folder_select_folder_link_warning");
|
||||
MessageBox.Show(warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (lbxFolders.SelectedItem is FolderLink selectedFolder)
|
||||
{
|
||||
using (var folderBrowserDialog = new FolderBrowserDialog())
|
||||
{
|
||||
folderBrowserDialog.SelectedPath = selectedFolder.Path;
|
||||
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
var link = _settings.FolderLinks.First(x => x.Path == selectedFolder.Path);
|
||||
link.Path = folderBrowserDialog.SelectedPath;
|
||||
}
|
||||
|
||||
lbxFolders.Items.Refresh();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string warning = _woxAPI.GetTranslation("wox_plugin_folder_select_folder_link_warning");
|
||||
MessageBox.Show(warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
using (var folderBrowserDialog = new FolderBrowserDialog())
|
||||
{
|
||||
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
var newFolder = new FolderLink
|
||||
{
|
||||
Path = folderBrowserDialog.SelectedPath,
|
||||
};
|
||||
|
||||
_settings.FolderLinks.Add(newFolder);
|
||||
}
|
||||
|
||||
lbxFolders.Items.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void LbxFolders_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||||
|
||||
if (files != null && files.Any())
|
||||
{
|
||||
foreach (string s in files)
|
||||
{
|
||||
if (Directory.Exists(s))
|
||||
{
|
||||
var newFolder = new FolderLink
|
||||
{
|
||||
Path = s,
|
||||
};
|
||||
|
||||
_settings.FolderLinks.Add(newFolder);
|
||||
}
|
||||
|
||||
lbxFolders.Items.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LbxFolders_DragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
{
|
||||
e.Effects = DragDropEffects.Link;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Effects = DragDropEffects.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,392 +1,392 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Folder
|
||||
{
|
||||
public class Main : IPlugin, ISettingProvider, IPluginI18n, ISavable, IContextMenu, IDisposable
|
||||
{
|
||||
public const string FolderImagePath = "Images\\folder.dark.png";
|
||||
public const string FileImagePath = "Images\\file.dark.png";
|
||||
public const string DeleteFileFolderImagePath = "Images\\delete.dark.png";
|
||||
public const string CopyImagePath = "Images\\copy.dark.png";
|
||||
|
||||
private const string _fileExplorerProgramName = "explorer";
|
||||
private static readonly PluginJsonStorage<FolderSettings> _storage = new PluginJsonStorage<FolderSettings>();
|
||||
private static readonly FolderSettings _settings = _storage.Load();
|
||||
private static List<string> _driverNames;
|
||||
private static PluginInitContext _context;
|
||||
private IContextMenu _contextMenuLoader;
|
||||
private static string warningIconPath;
|
||||
private bool _disposed = false;
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_storage.Save();
|
||||
}
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
return new FileSystemSettings(_context.API, _settings);
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
_contextMenuLoader = new ContextMenuLoader(context);
|
||||
InitialDriverList();
|
||||
|
||||
_context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateIconPath(_context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
private static void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
warningIconPath = "Images/Warning.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
warningIconPath = "Images/Warning.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "The parameter is unused")]
|
||||
private void OnThemeChanged(Theme _, Theme newTheme)
|
||||
{
|
||||
UpdateIconPath(newTheme);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(query));
|
||||
}
|
||||
|
||||
var results = GetFolderPluginResults(query);
|
||||
|
||||
// todo why was this hack here?
|
||||
foreach (var result in results)
|
||||
{
|
||||
result.Score += 10;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
|
||||
public static List<Result> GetFolderPluginResults(Query query)
|
||||
{
|
||||
var results = GetUserFolderResults(query);
|
||||
string search = query.Search.ToLower(CultureInfo.InvariantCulture);
|
||||
|
||||
if (!IsDriveOrSharedFolder(search))
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
results.AddRange(QueryInternalDirectoryExists(query));
|
||||
return results;
|
||||
}
|
||||
|
||||
private static bool IsDriveOrSharedFolder(string search)
|
||||
{
|
||||
if (search == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(search));
|
||||
}
|
||||
|
||||
if (search.StartsWith(@"\\", StringComparison.InvariantCulture))
|
||||
{ // share folder
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_driverNames != null && _driverNames.Any(search.StartsWith))
|
||||
{ // normal drive letter
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_driverNames == null && search.Length > 2 && char.IsLetter(search[0]) && search[1] == ':')
|
||||
{ // when we don't have the drive letters we can try...
|
||||
return true; // we don't know so let's give it the possibility
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Result CreateFolderResult(string title, string subtitle, string path, Query query)
|
||||
{
|
||||
return new Result
|
||||
{
|
||||
Title = title,
|
||||
IcoPath = path,
|
||||
SubTitle = "Folder: " + subtitle,
|
||||
QueryTextDisplay = path,
|
||||
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
|
||||
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path },
|
||||
Action = c =>
|
||||
{
|
||||
Process.Start(_fileExplorerProgramName, path);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
|
||||
private static List<Result> GetUserFolderResults(Query query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(query));
|
||||
}
|
||||
|
||||
string search = query.Search.ToLower(CultureInfo.InvariantCulture);
|
||||
var userFolderLinks = _settings.FolderLinks.Where(
|
||||
x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
|
||||
var results = userFolderLinks.Select(item =>
|
||||
CreateFolderResult(item.Nickname, item.Path, item.Path, query)).ToList();
|
||||
return results;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
|
||||
private static void InitialDriverList()
|
||||
{
|
||||
if (_driverNames == null)
|
||||
{
|
||||
_driverNames = new List<string>();
|
||||
var allDrives = DriveInfo.GetDrives();
|
||||
foreach (DriveInfo driver in allDrives)
|
||||
{
|
||||
_driverNames.Add(driver.Name.ToLower(CultureInfo.InvariantCulture).TrimEnd('\\'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly char[] _specialSearchChars = new char[]
|
||||
{
|
||||
'?', '*', '>',
|
||||
};
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
|
||||
private static List<Result> QueryInternalDirectoryExists(Query query)
|
||||
{
|
||||
var search = query.Search;
|
||||
var results = new List<Result>();
|
||||
var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0;
|
||||
string incompleteName = string.Empty;
|
||||
if (hasSpecial || !Directory.Exists(search + "\\"))
|
||||
{
|
||||
// if folder doesn't exist, we want to take the last part and use it afterwards to help the user
|
||||
// find the right folder.
|
||||
int index = search.LastIndexOf('\\');
|
||||
if (index > 0 && index < (search.Length - 1))
|
||||
{
|
||||
incompleteName = search.Substring(index + 1).ToLower(CultureInfo.InvariantCulture);
|
||||
search = search.Substring(0, index + 1);
|
||||
if (!Directory.Exists(search))
|
||||
{
|
||||
return results;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return results;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// folder exist, add \ at the end of doesn't exist
|
||||
if (!search.EndsWith("\\", StringComparison.InvariantCulture))
|
||||
{
|
||||
search += "\\";
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(CreateOpenCurrentFolderResult(search));
|
||||
|
||||
var searchOption = SearchOption.TopDirectoryOnly;
|
||||
incompleteName += "*";
|
||||
|
||||
// give the ability to search all folder when starting with >
|
||||
if (incompleteName.StartsWith(">", StringComparison.InvariantCulture))
|
||||
{
|
||||
searchOption = SearchOption.AllDirectories;
|
||||
|
||||
// match everything before and after search term using supported wildcard '*', ie. *searchterm*
|
||||
incompleteName = "*" + incompleteName.Substring(1);
|
||||
}
|
||||
|
||||
var folderList = new List<Result>();
|
||||
var fileList = new List<Result>();
|
||||
|
||||
try
|
||||
{
|
||||
// search folder and add results
|
||||
var directoryInfo = new DirectoryInfo(search);
|
||||
var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption);
|
||||
|
||||
foreach (var fileSystemInfo in fileSystemInfos)
|
||||
{
|
||||
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fileSystemInfo is DirectoryInfo)
|
||||
{
|
||||
var folderSubtitleString = fileSystemInfo.FullName;
|
||||
|
||||
folderList.Add(CreateFolderResult(fileSystemInfo.Name, folderSubtitleString, fileSystemInfo.FullName, query));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileList.Add(CreateFileResult(fileSystemInfo.FullName, query));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is UnauthorizedAccessException || e is ArgumentException)
|
||||
{
|
||||
results.Add(new Result { Title = e.Message, Score = 501 });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
results = results.Concat(folderList.OrderBy(x => x.Title).Take(_settings.MaxFolderResults)).Concat(fileList.OrderBy(x => x.Title).Take(_settings.MaxFileResults)).ToList();
|
||||
|
||||
// Show warning message if result has been truncated
|
||||
if (folderList.Count > _settings.MaxFolderResults || fileList.Count > _settings.MaxFileResults)
|
||||
{
|
||||
var preTruncationCount = folderList.Count + fileList.Count;
|
||||
var postTruncationCount = Math.Min(folderList.Count, _settings.MaxFolderResults) + Math.Min(fileList.Count, _settings.MaxFileResults);
|
||||
results.Add(CreateTruncatedItemsResult(search, preTruncationCount, postTruncationCount));
|
||||
}
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
private static Result CreateTruncatedItemsResult(string search, int preTruncationCount, int postTruncationCount)
|
||||
{
|
||||
return new Result
|
||||
{
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_folder_truncation_warning_title"),
|
||||
QueryTextDisplay = search,
|
||||
SubTitle = string.Format(CultureInfo.InvariantCulture, _context.API.GetTranslation("Microsoft_plugin_folder_truncation_warning_subtitle"), postTruncationCount, preTruncationCount),
|
||||
IcoPath = warningIconPath,
|
||||
};
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alve and instead inform the user of the error")]
|
||||
private static Result CreateFileResult(string filePath, Query query)
|
||||
{
|
||||
var result = new Result
|
||||
{
|
||||
Title = Path.GetFileName(filePath),
|
||||
SubTitle = "Folder: " + filePath,
|
||||
IcoPath = filePath,
|
||||
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData,
|
||||
Action = c =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(_fileExplorerProgramName, filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, "Could not start " + filePath);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath },
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Result CreateOpenCurrentFolderResult(string search)
|
||||
{
|
||||
var firstResult = "Open " + search;
|
||||
|
||||
var folderName = search.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last();
|
||||
var sanitizedPath = Regex.Replace(search, @"[\/\\]+", "\\");
|
||||
|
||||
// A network path must start with \\
|
||||
if (sanitizedPath.StartsWith("\\", StringComparison.InvariantCulture))
|
||||
{
|
||||
sanitizedPath = sanitizedPath.Insert(0, "\\");
|
||||
}
|
||||
|
||||
return new Result
|
||||
{
|
||||
Title = firstResult,
|
||||
QueryTextDisplay = search,
|
||||
SubTitle = $"Folder: Use > to search within the directory. Use * to search for file extensions. Or use both >*.",
|
||||
IcoPath = search,
|
||||
Score = 500,
|
||||
Action = c =>
|
||||
{
|
||||
Process.Start(_fileExplorerProgramName, sanitizedPath);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_folder_plugin_name");
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_folder_plugin_description");
|
||||
}
|
||||
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
return _contextMenuLoader.LoadContextMenus(selectedResult);
|
||||
}
|
||||
|
||||
public void UpdateSettings(PowerLauncherSettings settings)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_context.API.ThemeChanged -= OnThemeChanged;
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Folder
|
||||
{
|
||||
public class Main : IPlugin, ISettingProvider, IPluginI18n, ISavable, IContextMenu, IDisposable
|
||||
{
|
||||
public const string FolderImagePath = "Images\\folder.dark.png";
|
||||
public const string FileImagePath = "Images\\file.dark.png";
|
||||
public const string DeleteFileFolderImagePath = "Images\\delete.dark.png";
|
||||
public const string CopyImagePath = "Images\\copy.dark.png";
|
||||
|
||||
private const string _fileExplorerProgramName = "explorer";
|
||||
private static readonly PluginJsonStorage<FolderSettings> _storage = new PluginJsonStorage<FolderSettings>();
|
||||
private static readonly FolderSettings _settings = _storage.Load();
|
||||
private static List<string> _driverNames;
|
||||
private static PluginInitContext _context;
|
||||
private IContextMenu _contextMenuLoader;
|
||||
private static string warningIconPath;
|
||||
private bool _disposed = false;
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_storage.Save();
|
||||
}
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
return new FileSystemSettings(_context.API, _settings);
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
_contextMenuLoader = new ContextMenuLoader(context);
|
||||
InitialDriverList();
|
||||
|
||||
_context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateIconPath(_context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
private static void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
warningIconPath = "Images/Warning.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
warningIconPath = "Images/Warning.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "The parameter is unused")]
|
||||
private void OnThemeChanged(Theme _, Theme newTheme)
|
||||
{
|
||||
UpdateIconPath(newTheme);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(query));
|
||||
}
|
||||
|
||||
var results = GetFolderPluginResults(query);
|
||||
|
||||
// todo why was this hack here?
|
||||
foreach (var result in results)
|
||||
{
|
||||
result.Score += 10;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
|
||||
public static List<Result> GetFolderPluginResults(Query query)
|
||||
{
|
||||
var results = GetUserFolderResults(query);
|
||||
string search = query.Search.ToLower(CultureInfo.InvariantCulture);
|
||||
|
||||
if (!IsDriveOrSharedFolder(search))
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
results.AddRange(QueryInternalDirectoryExists(query));
|
||||
return results;
|
||||
}
|
||||
|
||||
private static bool IsDriveOrSharedFolder(string search)
|
||||
{
|
||||
if (search == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(search));
|
||||
}
|
||||
|
||||
if (search.StartsWith(@"\\", StringComparison.InvariantCulture))
|
||||
{ // share folder
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_driverNames != null && _driverNames.Any(search.StartsWith))
|
||||
{ // normal drive letter
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_driverNames == null && search.Length > 2 && char.IsLetter(search[0]) && search[1] == ':')
|
||||
{ // when we don't have the drive letters we can try...
|
||||
return true; // we don't know so let's give it the possibility
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Result CreateFolderResult(string title, string subtitle, string path, Query query)
|
||||
{
|
||||
return new Result
|
||||
{
|
||||
Title = title,
|
||||
IcoPath = path,
|
||||
SubTitle = "Folder: " + subtitle,
|
||||
QueryTextDisplay = path,
|
||||
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
|
||||
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path },
|
||||
Action = c =>
|
||||
{
|
||||
Process.Start(_fileExplorerProgramName, path);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
|
||||
private static List<Result> GetUserFolderResults(Query query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(query));
|
||||
}
|
||||
|
||||
string search = query.Search.ToLower(CultureInfo.InvariantCulture);
|
||||
var userFolderLinks = _settings.FolderLinks.Where(
|
||||
x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
|
||||
var results = userFolderLinks.Select(item =>
|
||||
CreateFolderResult(item.Nickname, item.Path, item.Path, query)).ToList();
|
||||
return results;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
|
||||
private static void InitialDriverList()
|
||||
{
|
||||
if (_driverNames == null)
|
||||
{
|
||||
_driverNames = new List<string>();
|
||||
var allDrives = DriveInfo.GetDrives();
|
||||
foreach (DriveInfo driver in allDrives)
|
||||
{
|
||||
_driverNames.Add(driver.Name.ToLower(CultureInfo.InvariantCulture).TrimEnd('\\'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly char[] _specialSearchChars = new char[]
|
||||
{
|
||||
'?', '*', '>',
|
||||
};
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
|
||||
private static List<Result> QueryInternalDirectoryExists(Query query)
|
||||
{
|
||||
var search = query.Search;
|
||||
var results = new List<Result>();
|
||||
var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0;
|
||||
string incompleteName = string.Empty;
|
||||
if (hasSpecial || !Directory.Exists(search + "\\"))
|
||||
{
|
||||
// if folder doesn't exist, we want to take the last part and use it afterwards to help the user
|
||||
// find the right folder.
|
||||
int index = search.LastIndexOf('\\');
|
||||
if (index > 0 && index < (search.Length - 1))
|
||||
{
|
||||
incompleteName = search.Substring(index + 1).ToLower(CultureInfo.InvariantCulture);
|
||||
search = search.Substring(0, index + 1);
|
||||
if (!Directory.Exists(search))
|
||||
{
|
||||
return results;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return results;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// folder exist, add \ at the end of doesn't exist
|
||||
if (!search.EndsWith("\\", StringComparison.InvariantCulture))
|
||||
{
|
||||
search += "\\";
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(CreateOpenCurrentFolderResult(search));
|
||||
|
||||
var searchOption = SearchOption.TopDirectoryOnly;
|
||||
incompleteName += "*";
|
||||
|
||||
// give the ability to search all folder when starting with >
|
||||
if (incompleteName.StartsWith(">", StringComparison.InvariantCulture))
|
||||
{
|
||||
searchOption = SearchOption.AllDirectories;
|
||||
|
||||
// match everything before and after search term using supported wildcard '*', ie. *searchterm*
|
||||
incompleteName = "*" + incompleteName.Substring(1);
|
||||
}
|
||||
|
||||
var folderList = new List<Result>();
|
||||
var fileList = new List<Result>();
|
||||
|
||||
try
|
||||
{
|
||||
// search folder and add results
|
||||
var directoryInfo = new DirectoryInfo(search);
|
||||
var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption);
|
||||
|
||||
foreach (var fileSystemInfo in fileSystemInfos)
|
||||
{
|
||||
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fileSystemInfo is DirectoryInfo)
|
||||
{
|
||||
var folderSubtitleString = fileSystemInfo.FullName;
|
||||
|
||||
folderList.Add(CreateFolderResult(fileSystemInfo.Name, folderSubtitleString, fileSystemInfo.FullName, query));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileList.Add(CreateFileResult(fileSystemInfo.FullName, query));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is UnauthorizedAccessException || e is ArgumentException)
|
||||
{
|
||||
results.Add(new Result { Title = e.Message, Score = 501 });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
results = results.Concat(folderList.OrderBy(x => x.Title).Take(_settings.MaxFolderResults)).Concat(fileList.OrderBy(x => x.Title).Take(_settings.MaxFileResults)).ToList();
|
||||
|
||||
// Show warning message if result has been truncated
|
||||
if (folderList.Count > _settings.MaxFolderResults || fileList.Count > _settings.MaxFileResults)
|
||||
{
|
||||
var preTruncationCount = folderList.Count + fileList.Count;
|
||||
var postTruncationCount = Math.Min(folderList.Count, _settings.MaxFolderResults) + Math.Min(fileList.Count, _settings.MaxFileResults);
|
||||
results.Add(CreateTruncatedItemsResult(search, preTruncationCount, postTruncationCount));
|
||||
}
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
private static Result CreateTruncatedItemsResult(string search, int preTruncationCount, int postTruncationCount)
|
||||
{
|
||||
return new Result
|
||||
{
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_folder_truncation_warning_title"),
|
||||
QueryTextDisplay = search,
|
||||
SubTitle = string.Format(CultureInfo.InvariantCulture, _context.API.GetTranslation("Microsoft_plugin_folder_truncation_warning_subtitle"), postTruncationCount, preTruncationCount),
|
||||
IcoPath = warningIconPath,
|
||||
};
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alve and instead inform the user of the error")]
|
||||
private static Result CreateFileResult(string filePath, Query query)
|
||||
{
|
||||
var result = new Result
|
||||
{
|
||||
Title = Path.GetFileName(filePath),
|
||||
SubTitle = "Folder: " + filePath,
|
||||
IcoPath = filePath,
|
||||
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData,
|
||||
Action = c =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(_fileExplorerProgramName, filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, "Could not start " + filePath);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath },
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Result CreateOpenCurrentFolderResult(string search)
|
||||
{
|
||||
var firstResult = "Open " + search;
|
||||
|
||||
var folderName = search.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last();
|
||||
var sanitizedPath = Regex.Replace(search, @"[\/\\]+", "\\");
|
||||
|
||||
// A network path must start with \\
|
||||
if (sanitizedPath.StartsWith("\\", StringComparison.InvariantCulture))
|
||||
{
|
||||
sanitizedPath = sanitizedPath.Insert(0, "\\");
|
||||
}
|
||||
|
||||
return new Result
|
||||
{
|
||||
Title = firstResult,
|
||||
QueryTextDisplay = search,
|
||||
SubTitle = $"Folder: Use > to search within the directory. Use * to search for file extensions. Or use both >*.",
|
||||
IcoPath = search,
|
||||
Score = 500,
|
||||
Action = c =>
|
||||
{
|
||||
Process.Start(_fileExplorerProgramName, sanitizedPath);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_folder_plugin_name");
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_folder_plugin_description");
|
||||
}
|
||||
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
return _contextMenuLoader.LoadContextMenus(selectedResult);
|
||||
}
|
||||
|
||||
public void UpdateSettings(PowerLauncherSettings settings)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_context.API.ThemeChanged -= OnThemeChanged;
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,196 +1,196 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Plugin.Indexer.SearchHelper;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Indexer
|
||||
{
|
||||
internal class ContextMenuLoader : IContextMenu
|
||||
{
|
||||
private readonly PluginInitContext _context;
|
||||
|
||||
public enum ResultType
|
||||
{
|
||||
Folder,
|
||||
File,
|
||||
}
|
||||
|
||||
// Extensions for adding run as admin context menu item for applications
|
||||
private readonly string[] appExtensions = { ".exe", ".bat", ".appref-ms", ".lnk" };
|
||||
|
||||
public ContextMenuLoader(PluginInitContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log and show an error message")]
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
var contextMenus = new List<ContextMenuResult>();
|
||||
if (selectedResult.ContextData is SearchResult record)
|
||||
{
|
||||
ResultType type = Path.HasExtension(record.Path) ? ResultType.File : ResultType.Folder;
|
||||
|
||||
if (type == ResultType.File)
|
||||
{
|
||||
contextMenus.Add(CreateOpenContainingFolderResult(record));
|
||||
}
|
||||
|
||||
// Test to check if File can be Run as admin, if yes, we add a 'run as admin' context menu item
|
||||
if (CanFileBeRunAsAdmin(record.Path))
|
||||
{
|
||||
contextMenus.Add(CreateRunAsAdminContextMenu(record));
|
||||
}
|
||||
|
||||
contextMenus.Add(new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_copy_path"),
|
||||
Glyph = "\xE8C8",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control,
|
||||
|
||||
Action = (context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(record.Path);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = "Fail to set text in clipboard";
|
||||
LogException(message, e);
|
||||
_context.API.ShowMsg(message);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
contextMenus.Add(new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_open_in_console"),
|
||||
Glyph = "\xE756",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
|
||||
Action = (context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (type == ResultType.File)
|
||||
{
|
||||
Helper.OpenInConsole(Path.GetDirectoryName(record.Path));
|
||||
}
|
||||
else
|
||||
{
|
||||
Helper.OpenInConsole(record.Path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Indexer.ContextMenuLoader.LoadContextMenus| Failed to open {record.Path} in console, {e.Message}", e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return contextMenus;
|
||||
}
|
||||
|
||||
// Function to add the context menu item to run as admin
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exeption message")]
|
||||
private ContextMenuResult CreateRunAsAdminContextMenu(SearchResult record)
|
||||
{
|
||||
return new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_run_as_administrator"),
|
||||
Glyph = "\xE7EF",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.Enter,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Task.Run(() => Helper.RunAsAdmin(record.Path));
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Indexer.ContextMenu| Failed to run {record.Path} as admin, {e.Message}", e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Function to test if the file can be run as admin
|
||||
private bool CanFileBeRunAsAdmin(string path)
|
||||
{
|
||||
string fileExtension = Path.GetExtension(path);
|
||||
foreach (string extension in appExtensions)
|
||||
{
|
||||
if (extension.Equals(fileExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log and show an error message")]
|
||||
private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record)
|
||||
{
|
||||
return new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_open_containing_folder"),
|
||||
Glyph = "\xE838",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.E,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start("explorer.exe", $" /select,\"{record.Path}\"");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"Fail to open file at {record.Path}";
|
||||
LogException(message, e);
|
||||
_context.API.ShowMsg(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static void LogException(string message, Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenu|{message}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Plugin.Indexer.SearchHelper;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Indexer
|
||||
{
|
||||
internal class ContextMenuLoader : IContextMenu
|
||||
{
|
||||
private readonly PluginInitContext _context;
|
||||
|
||||
public enum ResultType
|
||||
{
|
||||
Folder,
|
||||
File,
|
||||
}
|
||||
|
||||
// Extensions for adding run as admin context menu item for applications
|
||||
private readonly string[] appExtensions = { ".exe", ".bat", ".appref-ms", ".lnk" };
|
||||
|
||||
public ContextMenuLoader(PluginInitContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log and show an error message")]
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
var contextMenus = new List<ContextMenuResult>();
|
||||
if (selectedResult.ContextData is SearchResult record)
|
||||
{
|
||||
ResultType type = Path.HasExtension(record.Path) ? ResultType.File : ResultType.Folder;
|
||||
|
||||
if (type == ResultType.File)
|
||||
{
|
||||
contextMenus.Add(CreateOpenContainingFolderResult(record));
|
||||
}
|
||||
|
||||
// Test to check if File can be Run as admin, if yes, we add a 'run as admin' context menu item
|
||||
if (CanFileBeRunAsAdmin(record.Path))
|
||||
{
|
||||
contextMenus.Add(CreateRunAsAdminContextMenu(record));
|
||||
}
|
||||
|
||||
contextMenus.Add(new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_copy_path"),
|
||||
Glyph = "\xE8C8",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control,
|
||||
|
||||
Action = (context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(record.Path);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = "Fail to set text in clipboard";
|
||||
LogException(message, e);
|
||||
_context.API.ShowMsg(message);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
contextMenus.Add(new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_open_in_console"),
|
||||
Glyph = "\xE756",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
|
||||
Action = (context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (type == ResultType.File)
|
||||
{
|
||||
Helper.OpenInConsole(Path.GetDirectoryName(record.Path));
|
||||
}
|
||||
else
|
||||
{
|
||||
Helper.OpenInConsole(record.Path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Indexer.ContextMenuLoader.LoadContextMenus| Failed to open {record.Path} in console, {e.Message}", e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return contextMenus;
|
||||
}
|
||||
|
||||
// Function to add the context menu item to run as admin
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exeption message")]
|
||||
private ContextMenuResult CreateRunAsAdminContextMenu(SearchResult record)
|
||||
{
|
||||
return new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_run_as_administrator"),
|
||||
Glyph = "\xE7EF",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.Enter,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Task.Run(() => Helper.RunAsAdmin(record.Path));
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Indexer.ContextMenu| Failed to run {record.Path} as admin, {e.Message}", e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Function to test if the file can be run as admin
|
||||
private bool CanFileBeRunAsAdmin(string path)
|
||||
{
|
||||
string fileExtension = Path.GetExtension(path);
|
||||
foreach (string extension in appExtensions)
|
||||
{
|
||||
if (extension.Equals(fileExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log and show an error message")]
|
||||
private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record)
|
||||
{
|
||||
return new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_open_containing_folder"),
|
||||
Glyph = "\xE838",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.E,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start("explorer.exe", $" /select,\"{record.Path}\"");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"Fail to open file at {record.Path}";
|
||||
LogException(message, e);
|
||||
_context.API.ShowMsg(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static void LogException(string message, Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenu|{message}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,267 +1,267 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Plugin.Indexer.DriveDetection;
|
||||
using Microsoft.Plugin.Indexer.SearchHelper;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Indexer
|
||||
{
|
||||
internal class Main : ISettingProvider, IPlugin, ISavable, IPluginI18n, IContextMenu, IDisposable, IDelayedExecutionPlugin
|
||||
{
|
||||
// This variable contains metadata about the Plugin
|
||||
private PluginInitContext _context;
|
||||
|
||||
// This variable contains information about the context menus
|
||||
private IndexerSettings _settings;
|
||||
|
||||
// Contains information about the plugin stored in json format
|
||||
private PluginJsonStorage<IndexerSettings> _storage;
|
||||
|
||||
// To access Windows Search functionalities
|
||||
private static readonly OleDBSearch _search = new OleDBSearch();
|
||||
private readonly WindowsSearchAPI _api = new WindowsSearchAPI(_search);
|
||||
|
||||
// To obtain information regarding the drives that are indexed
|
||||
private readonly IndexerDriveDetection _driveDetection = new IndexerDriveDetection(new RegistryWrapper());
|
||||
|
||||
// Reserved keywords in oleDB
|
||||
private readonly string reservedStringPattern = @"^[\/\\\$\%]+$";
|
||||
|
||||
private string WarningIconPath { get; set; }
|
||||
|
||||
private IContextMenu _contextMenuLoader;
|
||||
private bool disposedValue;
|
||||
|
||||
// To save the configurations of plugins
|
||||
public void Save()
|
||||
{
|
||||
_storage.Save();
|
||||
}
|
||||
|
||||
// This function uses the Windows indexer and returns the list of results obtained
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive but will log the exception")]
|
||||
public List<Result> Query(Query query, bool isFullQuery)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
|
||||
if (!string.IsNullOrEmpty(query.Search))
|
||||
{
|
||||
var searchQuery = query.Search;
|
||||
if (_settings.MaxSearchCount <= 0)
|
||||
{
|
||||
_settings.MaxSearchCount = 30;
|
||||
}
|
||||
|
||||
var regexMatch = Regex.Match(searchQuery, reservedStringPattern);
|
||||
|
||||
if (!regexMatch.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_driveDetection.DisplayWarning())
|
||||
{
|
||||
results.Add(new Result
|
||||
{
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_drivedetectionwarning"),
|
||||
SubTitle = _context.API.GetTranslation("Microsoft_plugin_indexer_disable_warning_in_settings"),
|
||||
IcoPath = WarningIconPath,
|
||||
Action = e =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(GetWindowsSearchSettingsProcessInfo());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Exception("Microsoft.Plugin.Indexer", $"Unable to launch Windows Search Settings: {ex.Message}", ex, "Query");
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
var searchResultsList = _api.Search(searchQuery, isFullQuery, maxCount: _settings.MaxSearchCount).ToList();
|
||||
|
||||
// If the delayed execution query is not required (since the SQL query is fast) return empty results
|
||||
if (searchResultsList.Count == 0 && isFullQuery)
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
foreach (var searchResult in searchResultsList)
|
||||
{
|
||||
var path = searchResult.Path;
|
||||
var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0} : {1}", _context.API.GetTranslation("Microsoft_plugin_indexer_name"), searchResult.Title);
|
||||
var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0} : {1}", _context.API.GetTranslation("Microsoft_plugin_indexer_path"), path);
|
||||
string workingDir = null;
|
||||
if (_settings.UseLocationAsWorkingDir)
|
||||
{
|
||||
workingDir = Path.GetDirectoryName(path);
|
||||
}
|
||||
|
||||
Result r = new Result();
|
||||
r.Title = searchResult.Title;
|
||||
r.SubTitle = "Search: " + path;
|
||||
r.IcoPath = path;
|
||||
r.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
|
||||
r.Action = c =>
|
||||
{
|
||||
bool hide;
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = true,
|
||||
WorkingDirectory = workingDir,
|
||||
});
|
||||
hide = true;
|
||||
}
|
||||
catch (Win32Exception)
|
||||
{
|
||||
var name = $"Plugin: {_context.CurrentPluginMetadata.Name}";
|
||||
var msg = "Can't Open this file";
|
||||
_context.API.ShowMsg(name, msg, string.Empty);
|
||||
hide = false;
|
||||
}
|
||||
|
||||
return hide;
|
||||
};
|
||||
r.ContextData = searchResult;
|
||||
|
||||
// If the result is a directory, then it's display should show a directory.
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
r.QueryTextDisplay = path;
|
||||
}
|
||||
|
||||
results.Add(r);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// The connection has closed, internal error of ExecuteReader()
|
||||
// Not showing this exception to the users
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Info(ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// This function uses the Windows indexer and returns the list of results obtained. This version is required to implement the interface
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
return Query(query, false);
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
// initialize the context of the plugin
|
||||
_context = context;
|
||||
_contextMenuLoader = new ContextMenuLoader(context);
|
||||
_storage = new PluginJsonStorage<IndexerSettings>();
|
||||
_settings = _storage.Load();
|
||||
_context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateIconPath(_context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
// Todo : Update with theme based IconPath
|
||||
private void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
WarningIconPath = "Images/Warning.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
WarningIconPath = "Images/Warning.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
{
|
||||
UpdateIconPath(newTheme);
|
||||
}
|
||||
|
||||
// TODO: Localize the strings
|
||||
// Set the Plugin Title
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return "Windows Indexer Plugin";
|
||||
}
|
||||
|
||||
// TODO: Localize the string
|
||||
// Set the plugin Description
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return "Returns files and folders";
|
||||
}
|
||||
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
return _contextMenuLoader.LoadContextMenus(selectedResult);
|
||||
}
|
||||
|
||||
public void UpdateSettings(PowerLauncherSettings settings)
|
||||
{
|
||||
_driveDetection.IsDriveDetectionWarningCheckBoxSelected = settings.Properties.DisableDriveDetectionWarning;
|
||||
}
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Returns the Process Start Information for the new Windows Search Settings
|
||||
public static ProcessStartInfo GetWindowsSearchSettingsProcessInfo()
|
||||
{
|
||||
var ps = new ProcessStartInfo("ms-settings:cortana-windowssearch")
|
||||
{
|
||||
UseShellExecute = true,
|
||||
Verb = "open",
|
||||
};
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_search.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Plugin.Indexer.DriveDetection;
|
||||
using Microsoft.Plugin.Indexer.SearchHelper;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Indexer
|
||||
{
|
||||
internal class Main : ISettingProvider, IPlugin, ISavable, IPluginI18n, IContextMenu, IDisposable, IDelayedExecutionPlugin
|
||||
{
|
||||
// This variable contains metadata about the Plugin
|
||||
private PluginInitContext _context;
|
||||
|
||||
// This variable contains information about the context menus
|
||||
private IndexerSettings _settings;
|
||||
|
||||
// Contains information about the plugin stored in json format
|
||||
private PluginJsonStorage<IndexerSettings> _storage;
|
||||
|
||||
// To access Windows Search functionalities
|
||||
private static readonly OleDBSearch _search = new OleDBSearch();
|
||||
private readonly WindowsSearchAPI _api = new WindowsSearchAPI(_search);
|
||||
|
||||
// To obtain information regarding the drives that are indexed
|
||||
private readonly IndexerDriveDetection _driveDetection = new IndexerDriveDetection(new RegistryWrapper());
|
||||
|
||||
// Reserved keywords in oleDB
|
||||
private readonly string reservedStringPattern = @"^[\/\\\$\%]+$";
|
||||
|
||||
private string WarningIconPath { get; set; }
|
||||
|
||||
private IContextMenu _contextMenuLoader;
|
||||
private bool disposedValue;
|
||||
|
||||
// To save the configurations of plugins
|
||||
public void Save()
|
||||
{
|
||||
_storage.Save();
|
||||
}
|
||||
|
||||
// This function uses the Windows indexer and returns the list of results obtained
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive but will log the exception")]
|
||||
public List<Result> Query(Query query, bool isFullQuery)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
|
||||
if (!string.IsNullOrEmpty(query.Search))
|
||||
{
|
||||
var searchQuery = query.Search;
|
||||
if (_settings.MaxSearchCount <= 0)
|
||||
{
|
||||
_settings.MaxSearchCount = 30;
|
||||
}
|
||||
|
||||
var regexMatch = Regex.Match(searchQuery, reservedStringPattern);
|
||||
|
||||
if (!regexMatch.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_driveDetection.DisplayWarning())
|
||||
{
|
||||
results.Add(new Result
|
||||
{
|
||||
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_drivedetectionwarning"),
|
||||
SubTitle = _context.API.GetTranslation("Microsoft_plugin_indexer_disable_warning_in_settings"),
|
||||
IcoPath = WarningIconPath,
|
||||
Action = e =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(GetWindowsSearchSettingsProcessInfo());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Exception("Microsoft.Plugin.Indexer", $"Unable to launch Windows Search Settings: {ex.Message}", ex, "Query");
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
var searchResultsList = _api.Search(searchQuery, isFullQuery, maxCount: _settings.MaxSearchCount).ToList();
|
||||
|
||||
// If the delayed execution query is not required (since the SQL query is fast) return empty results
|
||||
if (searchResultsList.Count == 0 && isFullQuery)
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
|
||||
foreach (var searchResult in searchResultsList)
|
||||
{
|
||||
var path = searchResult.Path;
|
||||
var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0} : {1}", _context.API.GetTranslation("Microsoft_plugin_indexer_name"), searchResult.Title);
|
||||
var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0} : {1}", _context.API.GetTranslation("Microsoft_plugin_indexer_path"), path);
|
||||
string workingDir = null;
|
||||
if (_settings.UseLocationAsWorkingDir)
|
||||
{
|
||||
workingDir = Path.GetDirectoryName(path);
|
||||
}
|
||||
|
||||
Result r = new Result();
|
||||
r.Title = searchResult.Title;
|
||||
r.SubTitle = "Search: " + path;
|
||||
r.IcoPath = path;
|
||||
r.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
|
||||
r.Action = c =>
|
||||
{
|
||||
bool hide;
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = true,
|
||||
WorkingDirectory = workingDir,
|
||||
});
|
||||
hide = true;
|
||||
}
|
||||
catch (Win32Exception)
|
||||
{
|
||||
var name = $"Plugin: {_context.CurrentPluginMetadata.Name}";
|
||||
var msg = "Can't Open this file";
|
||||
_context.API.ShowMsg(name, msg, string.Empty);
|
||||
hide = false;
|
||||
}
|
||||
|
||||
return hide;
|
||||
};
|
||||
r.ContextData = searchResult;
|
||||
|
||||
// If the result is a directory, then it's display should show a directory.
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
r.QueryTextDisplay = path;
|
||||
}
|
||||
|
||||
results.Add(r);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// The connection has closed, internal error of ExecuteReader()
|
||||
// Not showing this exception to the users
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Info(ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// This function uses the Windows indexer and returns the list of results obtained. This version is required to implement the interface
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
return Query(query, false);
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
// initialize the context of the plugin
|
||||
_context = context;
|
||||
_contextMenuLoader = new ContextMenuLoader(context);
|
||||
_storage = new PluginJsonStorage<IndexerSettings>();
|
||||
_settings = _storage.Load();
|
||||
_context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateIconPath(_context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
// Todo : Update with theme based IconPath
|
||||
private void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
WarningIconPath = "Images/Warning.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
WarningIconPath = "Images/Warning.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
{
|
||||
UpdateIconPath(newTheme);
|
||||
}
|
||||
|
||||
// TODO: Localize the strings
|
||||
// Set the Plugin Title
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return "Windows Indexer Plugin";
|
||||
}
|
||||
|
||||
// TODO: Localize the string
|
||||
// Set the plugin Description
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return "Returns files and folders";
|
||||
}
|
||||
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
return _contextMenuLoader.LoadContextMenus(selectedResult);
|
||||
}
|
||||
|
||||
public void UpdateSettings(PowerLauncherSettings settings)
|
||||
{
|
||||
_driveDetection.IsDriveDetectionWarningCheckBoxSelected = settings.Properties.DisableDriveDetectionWarning;
|
||||
}
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Returns the Process Start Information for the new Windows Search Settings
|
||||
public static ProcessStartInfo GetWindowsSearchSettingsProcessInfo()
|
||||
{
|
||||
var ps = new ProcessStartInfo("ms-settings:cortana-windowssearch")
|
||||
{
|
||||
UseShellExecute = true,
|
||||
Verb = "open",
|
||||
};
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_search.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,122 +1,122 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.OleDb;
|
||||
|
||||
namespace Microsoft.Plugin.Indexer.SearchHelper
|
||||
{
|
||||
public class OleDBSearch : ISearch, IDisposable
|
||||
{
|
||||
private OleDbCommand command;
|
||||
private OleDbConnection conn;
|
||||
private OleDbDataReader wDSResults;
|
||||
private bool disposedValue;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Security",
|
||||
"CA2100:Review SQL queries for security vulnerabilities",
|
||||
Justification = "sqlQuery does not come from user input but is generated via the ISearchQueryHelper::GenerateSqlFromUserQuery see: https://docs.microsoft.com/en-us/windows/win32/search/-search-3x-wds-qryidx-searchqueryhelper#using-the-generatesqlfromuserquery-method")]
|
||||
public List<OleDBResult> Query(string connectionString, string sqlQuery)
|
||||
{
|
||||
List<OleDBResult> result = new List<OleDBResult>();
|
||||
|
||||
using (conn = new OleDbConnection(connectionString))
|
||||
{
|
||||
// open the connection
|
||||
conn.Open();
|
||||
|
||||
try
|
||||
{
|
||||
// now create an OleDB command object with the query we built above and the connection we just opened.
|
||||
using (command = new OleDbCommand(sqlQuery, conn))
|
||||
{
|
||||
using (wDSResults = command.ExecuteReader())
|
||||
{
|
||||
if (!wDSResults.IsClosed && wDSResults.HasRows)
|
||||
{
|
||||
while (!wDSResults.IsClosed && wDSResults.Read())
|
||||
{
|
||||
List<object> fieldData = new List<object>(wDSResults.FieldCount);
|
||||
for (int i = 0; i < wDSResults.FieldCount; i++)
|
||||
{
|
||||
fieldData.Add(wDSResults.GetValue(i));
|
||||
}
|
||||
|
||||
result.Add(new OleDBResult(fieldData));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AccessViolationException can occur if another query is made before the current query completes. Since the old query would be cancelled we can ignore the exception
|
||||
catch (System.AccessViolationException)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Checks if all the variables related to database connection have been properly disposed
|
||||
public bool HaveAllDisposableItemsBeenDisposed()
|
||||
{
|
||||
bool commandDisposed = false;
|
||||
bool connDisposed = false;
|
||||
bool resultDisposed = false;
|
||||
|
||||
try
|
||||
{
|
||||
command.ExecuteReader();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
commandDisposed = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
wDSResults.Read();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
resultDisposed = true;
|
||||
}
|
||||
|
||||
if (conn.State == System.Data.ConnectionState.Closed)
|
||||
{
|
||||
connDisposed = true;
|
||||
}
|
||||
|
||||
return commandDisposed && resultDisposed && connDisposed;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
command?.Dispose();
|
||||
conn?.Dispose();
|
||||
wDSResults?.Dispose();
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.OleDb;
|
||||
|
||||
namespace Microsoft.Plugin.Indexer.SearchHelper
|
||||
{
|
||||
public class OleDBSearch : ISearch, IDisposable
|
||||
{
|
||||
private OleDbCommand command;
|
||||
private OleDbConnection conn;
|
||||
private OleDbDataReader wDSResults;
|
||||
private bool disposedValue;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Security",
|
||||
"CA2100:Review SQL queries for security vulnerabilities",
|
||||
Justification = "sqlQuery does not come from user input but is generated via the ISearchQueryHelper::GenerateSqlFromUserQuery see: https://docs.microsoft.com/en-us/windows/win32/search/-search-3x-wds-qryidx-searchqueryhelper#using-the-generatesqlfromuserquery-method")]
|
||||
public List<OleDBResult> Query(string connectionString, string sqlQuery)
|
||||
{
|
||||
List<OleDBResult> result = new List<OleDBResult>();
|
||||
|
||||
using (conn = new OleDbConnection(connectionString))
|
||||
{
|
||||
// open the connection
|
||||
conn.Open();
|
||||
|
||||
try
|
||||
{
|
||||
// now create an OleDB command object with the query we built above and the connection we just opened.
|
||||
using (command = new OleDbCommand(sqlQuery, conn))
|
||||
{
|
||||
using (wDSResults = command.ExecuteReader())
|
||||
{
|
||||
if (!wDSResults.IsClosed && wDSResults.HasRows)
|
||||
{
|
||||
while (!wDSResults.IsClosed && wDSResults.Read())
|
||||
{
|
||||
List<object> fieldData = new List<object>(wDSResults.FieldCount);
|
||||
for (int i = 0; i < wDSResults.FieldCount; i++)
|
||||
{
|
||||
fieldData.Add(wDSResults.GetValue(i));
|
||||
}
|
||||
|
||||
result.Add(new OleDBResult(fieldData));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AccessViolationException can occur if another query is made before the current query completes. Since the old query would be cancelled we can ignore the exception
|
||||
catch (System.AccessViolationException)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Checks if all the variables related to database connection have been properly disposed
|
||||
public bool HaveAllDisposableItemsBeenDisposed()
|
||||
{
|
||||
bool commandDisposed = false;
|
||||
bool connDisposed = false;
|
||||
bool resultDisposed = false;
|
||||
|
||||
try
|
||||
{
|
||||
command.ExecuteReader();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
commandDisposed = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
wDSResults.Read();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
resultDisposed = true;
|
||||
}
|
||||
|
||||
if (conn.State == System.Data.ConnectionState.Closed)
|
||||
{
|
||||
connDisposed = true;
|
||||
}
|
||||
|
||||
return commandDisposed && resultDisposed && connDisposed;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
command?.Dispose();
|
||||
conn?.Dispose();
|
||||
wDSResults?.Dispose();
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,150 +1,150 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Search.Interop;
|
||||
|
||||
namespace Microsoft.Plugin.Indexer.SearchHelper
|
||||
{
|
||||
public class WindowsSearchAPI
|
||||
{
|
||||
public bool DisplayHiddenFiles { get; set; }
|
||||
|
||||
private readonly ISearch windowsIndexerSearch;
|
||||
|
||||
private const uint _fileAttributeHidden = 0x2;
|
||||
private static readonly Regex _likeRegex = new Regex(@"[^\s(]+\s+LIKE\s+'([^']|'')*'\s+OR\s+", RegexOptions.Compiled);
|
||||
|
||||
public WindowsSearchAPI(ISearch windowsIndexerSearch, bool displayHiddenFiles = false)
|
||||
{
|
||||
this.windowsIndexerSearch = windowsIndexerSearch;
|
||||
DisplayHiddenFiles = displayHiddenFiles;
|
||||
}
|
||||
|
||||
public List<SearchResult> ExecuteQuery(ISearchQueryHelper queryHelper, string keyword, bool isFullQuery = false)
|
||||
{
|
||||
if (queryHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(queryHelper));
|
||||
}
|
||||
|
||||
List<SearchResult> results = new List<SearchResult>();
|
||||
|
||||
// Generate SQL from our parameters, converting the userQuery from AQS->WHERE clause
|
||||
string sqlQuery = queryHelper.GenerateSQLFromUserQuery(keyword);
|
||||
var simplifiedQuery = SimplifyQuery(sqlQuery);
|
||||
|
||||
if (!isFullQuery)
|
||||
{
|
||||
sqlQuery = simplifiedQuery;
|
||||
}
|
||||
else if (simplifiedQuery.Equals(sqlQuery, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
// if a full query is requested but there is no difference between the queries, return empty results
|
||||
return results;
|
||||
}
|
||||
|
||||
// execute the command, which returns the results as an OleDBResults.
|
||||
List<OleDBResult> oleDBResults = windowsIndexerSearch.Query(queryHelper.ConnectionString, sqlQuery);
|
||||
|
||||
// Loop over all records from the database
|
||||
foreach (OleDBResult oleDBResult in oleDBResults)
|
||||
{
|
||||
if (oleDBResult.FieldData[0] == DBNull.Value || oleDBResult.FieldData[1] == DBNull.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var uri_path = new Uri((string)oleDBResult.FieldData[0]);
|
||||
var result = new SearchResult
|
||||
{
|
||||
Path = uri_path.LocalPath,
|
||||
Title = (string)oleDBResult.FieldData[1],
|
||||
};
|
||||
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static void ModifyQueryHelper(ref ISearchQueryHelper queryHelper, string pattern)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(pattern));
|
||||
}
|
||||
|
||||
if (queryHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(queryHelper));
|
||||
}
|
||||
|
||||
// convert file pattern if it is not '*'. Don't create restriction for '*' as it includes all files.
|
||||
if (pattern != "*")
|
||||
{
|
||||
pattern = pattern.Replace("*", "%", StringComparison.InvariantCulture);
|
||||
pattern = pattern.Replace("?", "_", StringComparison.InvariantCulture);
|
||||
|
||||
if (pattern.Contains("%", StringComparison.InvariantCulture) || pattern.Contains("_", StringComparison.InvariantCulture))
|
||||
{
|
||||
queryHelper.QueryWhereRestrictions += " AND System.FileName LIKE '" + pattern + "' ";
|
||||
}
|
||||
else
|
||||
{
|
||||
// if there are no wildcards we can use a contains which is much faster as it uses the index
|
||||
queryHelper.QueryWhereRestrictions += " AND Contains(System.FileName, '" + pattern + "') ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void InitQueryHelper(out ISearchQueryHelper queryHelper, int maxCount, bool displayHiddenFiles)
|
||||
{
|
||||
// This uses the Microsoft.Search.Interop assembly
|
||||
CSearchManager manager = new CSearchManager();
|
||||
|
||||
// SystemIndex catalog is the default catalog in Windows
|
||||
ISearchCatalogManager catalogManager = manager.GetCatalog("SystemIndex");
|
||||
|
||||
// Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer
|
||||
queryHelper = catalogManager.GetQueryHelper();
|
||||
|
||||
// Set the number of results we want. Don't set this property if all results are needed.
|
||||
queryHelper.QueryMaxResults = maxCount;
|
||||
|
||||
// Set list of columns we want to display, getting the path presently
|
||||
queryHelper.QuerySelectColumns = "System.ItemUrl, System.FileName, System.FileAttributes";
|
||||
|
||||
// Set additional query restriction
|
||||
queryHelper.QueryWhereRestrictions = "AND scope='file:'";
|
||||
|
||||
if (!displayHiddenFiles)
|
||||
{
|
||||
// https://docs.microsoft.com/en-us/windows/win32/search/all-bitwise
|
||||
queryHelper.QueryWhereRestrictions += " AND System.FileAttributes <> SOME BITWISE " + _fileAttributeHidden;
|
||||
}
|
||||
|
||||
// To filter based on title for now
|
||||
queryHelper.QueryContentProperties = "System.FileName";
|
||||
|
||||
// Set sorting order
|
||||
queryHelper.QuerySorting = "System.DateModified DESC";
|
||||
}
|
||||
|
||||
public IEnumerable<SearchResult> Search(string keyword, bool isFullQuery = false, string pattern = "*", int maxCount = 30)
|
||||
{
|
||||
ISearchQueryHelper queryHelper;
|
||||
InitQueryHelper(out queryHelper, maxCount, DisplayHiddenFiles);
|
||||
ModifyQueryHelper(ref queryHelper, pattern);
|
||||
return ExecuteQuery(queryHelper, keyword, isFullQuery);
|
||||
}
|
||||
|
||||
public static string SimplifyQuery(string sqlQuery)
|
||||
{
|
||||
return _likeRegex.Replace(sqlQuery, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Search.Interop;
|
||||
|
||||
namespace Microsoft.Plugin.Indexer.SearchHelper
|
||||
{
|
||||
public class WindowsSearchAPI
|
||||
{
|
||||
public bool DisplayHiddenFiles { get; set; }
|
||||
|
||||
private readonly ISearch windowsIndexerSearch;
|
||||
|
||||
private const uint _fileAttributeHidden = 0x2;
|
||||
private static readonly Regex _likeRegex = new Regex(@"[^\s(]+\s+LIKE\s+'([^']|'')*'\s+OR\s+", RegexOptions.Compiled);
|
||||
|
||||
public WindowsSearchAPI(ISearch windowsIndexerSearch, bool displayHiddenFiles = false)
|
||||
{
|
||||
this.windowsIndexerSearch = windowsIndexerSearch;
|
||||
DisplayHiddenFiles = displayHiddenFiles;
|
||||
}
|
||||
|
||||
public List<SearchResult> ExecuteQuery(ISearchQueryHelper queryHelper, string keyword, bool isFullQuery = false)
|
||||
{
|
||||
if (queryHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(queryHelper));
|
||||
}
|
||||
|
||||
List<SearchResult> results = new List<SearchResult>();
|
||||
|
||||
// Generate SQL from our parameters, converting the userQuery from AQS->WHERE clause
|
||||
string sqlQuery = queryHelper.GenerateSQLFromUserQuery(keyword);
|
||||
var simplifiedQuery = SimplifyQuery(sqlQuery);
|
||||
|
||||
if (!isFullQuery)
|
||||
{
|
||||
sqlQuery = simplifiedQuery;
|
||||
}
|
||||
else if (simplifiedQuery.Equals(sqlQuery, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
// if a full query is requested but there is no difference between the queries, return empty results
|
||||
return results;
|
||||
}
|
||||
|
||||
// execute the command, which returns the results as an OleDBResults.
|
||||
List<OleDBResult> oleDBResults = windowsIndexerSearch.Query(queryHelper.ConnectionString, sqlQuery);
|
||||
|
||||
// Loop over all records from the database
|
||||
foreach (OleDBResult oleDBResult in oleDBResults)
|
||||
{
|
||||
if (oleDBResult.FieldData[0] == DBNull.Value || oleDBResult.FieldData[1] == DBNull.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var uri_path = new Uri((string)oleDBResult.FieldData[0]);
|
||||
var result = new SearchResult
|
||||
{
|
||||
Path = uri_path.LocalPath,
|
||||
Title = (string)oleDBResult.FieldData[1],
|
||||
};
|
||||
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static void ModifyQueryHelper(ref ISearchQueryHelper queryHelper, string pattern)
|
||||
{
|
||||
if (pattern == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(pattern));
|
||||
}
|
||||
|
||||
if (queryHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(queryHelper));
|
||||
}
|
||||
|
||||
// convert file pattern if it is not '*'. Don't create restriction for '*' as it includes all files.
|
||||
if (pattern != "*")
|
||||
{
|
||||
pattern = pattern.Replace("*", "%", StringComparison.InvariantCulture);
|
||||
pattern = pattern.Replace("?", "_", StringComparison.InvariantCulture);
|
||||
|
||||
if (pattern.Contains("%", StringComparison.InvariantCulture) || pattern.Contains("_", StringComparison.InvariantCulture))
|
||||
{
|
||||
queryHelper.QueryWhereRestrictions += " AND System.FileName LIKE '" + pattern + "' ";
|
||||
}
|
||||
else
|
||||
{
|
||||
// if there are no wildcards we can use a contains which is much faster as it uses the index
|
||||
queryHelper.QueryWhereRestrictions += " AND Contains(System.FileName, '" + pattern + "') ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void InitQueryHelper(out ISearchQueryHelper queryHelper, int maxCount, bool displayHiddenFiles)
|
||||
{
|
||||
// This uses the Microsoft.Search.Interop assembly
|
||||
CSearchManager manager = new CSearchManager();
|
||||
|
||||
// SystemIndex catalog is the default catalog in Windows
|
||||
ISearchCatalogManager catalogManager = manager.GetCatalog("SystemIndex");
|
||||
|
||||
// Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer
|
||||
queryHelper = catalogManager.GetQueryHelper();
|
||||
|
||||
// Set the number of results we want. Don't set this property if all results are needed.
|
||||
queryHelper.QueryMaxResults = maxCount;
|
||||
|
||||
// Set list of columns we want to display, getting the path presently
|
||||
queryHelper.QuerySelectColumns = "System.ItemUrl, System.FileName, System.FileAttributes";
|
||||
|
||||
// Set additional query restriction
|
||||
queryHelper.QueryWhereRestrictions = "AND scope='file:'";
|
||||
|
||||
if (!displayHiddenFiles)
|
||||
{
|
||||
// https://docs.microsoft.com/en-us/windows/win32/search/all-bitwise
|
||||
queryHelper.QueryWhereRestrictions += " AND System.FileAttributes <> SOME BITWISE " + _fileAttributeHidden;
|
||||
}
|
||||
|
||||
// To filter based on title for now
|
||||
queryHelper.QueryContentProperties = "System.FileName";
|
||||
|
||||
// Set sorting order
|
||||
queryHelper.QuerySorting = "System.DateModified DESC";
|
||||
}
|
||||
|
||||
public IEnumerable<SearchResult> Search(string keyword, bool isFullQuery = false, string pattern = "*", int maxCount = 30)
|
||||
{
|
||||
ISearchQueryHelper queryHelper;
|
||||
InitQueryHelper(out queryHelper, maxCount, DisplayHiddenFiles);
|
||||
ModifyQueryHelper(ref queryHelper, pattern);
|
||||
return ExecuteQuery(queryHelper, keyword, isFullQuery);
|
||||
}
|
||||
|
||||
public static string SimplifyQuery(string sqlQuery)
|
||||
{
|
||||
return _likeRegex.Replace(sqlQuery, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,449 +1,449 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
{
|
||||
using Win32Program = Microsoft.Plugin.Program.Programs.Win32Program;
|
||||
|
||||
[TestFixture]
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
{
|
||||
using Win32Program = Microsoft.Plugin.Program.Programs.Win32Program;
|
||||
|
||||
[TestFixture]
|
||||
public class Win32Tests
|
||||
{
|
||||
private static readonly Win32Program _notepadAppdata = new Win32Program
|
||||
{
|
||||
Name = "Notepad",
|
||||
ExecutableName = "notepad.exe",
|
||||
FullPath = "c:\\windows\\system32\\notepad.exe",
|
||||
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _notepadUsers = new Win32Program
|
||||
{
|
||||
Name = "Notepad",
|
||||
ExecutableName = "notepad.exe",
|
||||
FullPath = "c:\\windows\\system32\\notepad.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _azureCommandPrompt = new Win32Program
|
||||
{
|
||||
Name = "Microsoft Azure Command Prompt - v2.9",
|
||||
ExecutableName = "cmd.exe",
|
||||
FullPath = "c:\\windows\\system32\\cmd.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft azure\\microsoft azure sdk for .net\\v2.9\\microsoft azure command prompt - v2.9.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _visualStudioCommandPrompt = new Win32Program
|
||||
{
|
||||
Name = "x64 Native Tools Command Prompt for VS 2019",
|
||||
ExecutableName = "cmd.exe",
|
||||
FullPath = "c:\\windows\\system32\\cmd.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\visual studio 2019\\visual studio tools\\vc\\x64 native tools command prompt for vs 2019.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _commandPrompt = new Win32Program
|
||||
{
|
||||
Name = "Command Prompt",
|
||||
ExecutableName = "cmd.exe",
|
||||
FullPath = "c:\\windows\\system32\\cmd.exe",
|
||||
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\command prompt.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _fileExplorer = new Win32Program
|
||||
{
|
||||
Name = "File Explorer",
|
||||
ExecutableName = "File Explorer.lnk",
|
||||
FullPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\file explorer.lnk",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _wordpad = new Win32Program
|
||||
{
|
||||
Name = "Wordpad",
|
||||
ExecutableName = "wordpad.exe",
|
||||
FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\wordpad.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _wordpadDuplicate = new Win32Program
|
||||
{
|
||||
Name = "WORDPAD",
|
||||
ExecutableName = "WORDPAD.EXE",
|
||||
FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _twitterChromePwa = new Win32Program
|
||||
{
|
||||
Name = "Twitter",
|
||||
FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome_proxy.exe",
|
||||
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\chrome apps\\twitter.lnk",
|
||||
Arguments = " --profile-directory=Default --app-id=jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi",
|
||||
AppType = 0,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _pinnedWebpage = new Win32Program
|
||||
{
|
||||
Name = "Web page",
|
||||
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe",
|
||||
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\web page.lnk",
|
||||
Arguments = "--profile-directory=Default --app-id=homljgmgpmcbpjbnjpfijnhipfkiclkd",
|
||||
AppType = 0,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _edgeNamedPinnedWebpage = new Win32Program
|
||||
{
|
||||
Name = "edge - Bing",
|
||||
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe",
|
||||
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\edge - bing.lnk",
|
||||
Arguments = " --profile-directory=Default --app-id=aocfnapldcnfbofgmbbllojgocaelgdd",
|
||||
AppType = 0,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _msedge = new Win32Program
|
||||
{
|
||||
Name = "Microsoft Edge",
|
||||
ExecutableName = "msedge.exe",
|
||||
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft edge.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _chrome = new Win32Program
|
||||
{
|
||||
Name = "Google Chrome",
|
||||
ExecutableName = "chrome.exe",
|
||||
FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\google chrome.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _dummyProxyApp = new Win32Program
|
||||
{
|
||||
Name = "Proxy App",
|
||||
ExecutableName = "test_proxy.exe",
|
||||
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\test_proxy.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\test proxy.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _cmdRunCommand = new Win32Program
|
||||
{
|
||||
Name = "cmd",
|
||||
ExecutableName = "cmd.exe",
|
||||
FullPath = "c:\\windows\\system32\\cmd.exe",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 3, // Run command
|
||||
};
|
||||
|
||||
private static readonly Win32Program _cmderRunCommand = new Win32Program
|
||||
{
|
||||
Name = "Cmder",
|
||||
Description = "Cmder: Lovely Console Emulator",
|
||||
ExecutableName = "Cmder.exe",
|
||||
FullPath = "c:\\tools\\cmder\\cmder.exe",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 3, // Run command
|
||||
};
|
||||
|
||||
private static readonly Win32Program _dummyInternetShortcutApp = new Win32Program
|
||||
{
|
||||
Name = "Shop Titans",
|
||||
ExecutableName = "Shop Titans.url",
|
||||
FullPath = "steam://rungameid/1258080",
|
||||
ParentDirectory = "C:\\Users\\temp\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Steam",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 1,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _dummyInternetShortcutAppDuplicate = new Win32Program
|
||||
{
|
||||
Name = "Shop Titans",
|
||||
ExecutableName = "Shop Titans.url",
|
||||
FullPath = "steam://rungameid/1258080",
|
||||
ParentDirectory = "C:\\Users\\temp\\Desktop",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 1,
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void DedupFunctionWhenCalledMustRemoveDuplicateNotepads()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32Program> prgms = new List<Win32Program>
|
||||
{
|
||||
_notepadAppdata,
|
||||
_notepadUsers,
|
||||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DedupFunctionWhenCalledMustRemoveInternetShortcuts()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32Program> prgms = new List<Win32Program>
|
||||
{
|
||||
_dummyInternetShortcutApp,
|
||||
_dummyInternetShortcutAppDuplicate,
|
||||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DedupFunctionWhenCalledMustNotRemovelnkWhichdoesNotHaveExe()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32Program> prgms = new List<Win32Program>
|
||||
{
|
||||
_fileExplorer,
|
||||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DedupFunctionMustRemoveDuplicatesForExeExtensionsWithoutLnkResolvedPath()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32Program> prgms = new List<Win32Program>
|
||||
{
|
||||
_wordpad,
|
||||
_wordpadDuplicate,
|
||||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
Assert.IsTrue(!string.IsNullOrEmpty(apps[0].LnkResolvedPath));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DedupFunctionMustNotRemoveProgramsWithSameExeNameAndFullPath()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32Program> prgms = new List<Win32Program>
|
||||
{
|
||||
_azureCommandPrompt,
|
||||
_visualStudioCommandPrompt,
|
||||
_commandPrompt,
|
||||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FunctionIsWebApplicationShouldReturnTrueForWebApplications()
|
||||
{
|
||||
// The IsWebApplication(() function must return true for all PWAs and pinned web pages
|
||||
Assert.IsTrue(_twitterChromePwa.IsWebApplication());
|
||||
Assert.IsTrue(_pinnedWebpage.IsWebApplication());
|
||||
Assert.IsTrue(_edgeNamedPinnedWebpage.IsWebApplication());
|
||||
|
||||
// Should not filter apps whose executable name ends with proxy.exe
|
||||
Assert.IsFalse(_dummyProxyApp.IsWebApplication());
|
||||
}
|
||||
|
||||
[TestCase("ignore")]
|
||||
public void FunctionFilterWebApplicationShouldReturnFalseWhenSearchingForTheMainApp(string query)
|
||||
{
|
||||
// Irrespective of the query, the FilterWebApplication() Function must not filter main apps such as edge and chrome
|
||||
Assert.IsFalse(_msedge.FilterWebApplication(query));
|
||||
Assert.IsFalse(_chrome.FilterWebApplication(query));
|
||||
}
|
||||
|
||||
[TestCase("edge", ExpectedResult = true)]
|
||||
[TestCase("EDGE", ExpectedResult = true)]
|
||||
[TestCase("msedge", ExpectedResult = true)]
|
||||
[TestCase("Microsoft", ExpectedResult = true)]
|
||||
[TestCase("edg", ExpectedResult = true)]
|
||||
[TestCase("Edge page", ExpectedResult = false)]
|
||||
[TestCase("Edge Web page", ExpectedResult = false)]
|
||||
public bool EdgeWebSitesShouldBeFilteredWhenSearchingForEdge(string query)
|
||||
{
|
||||
return _pinnedWebpage.FilterWebApplication(query);
|
||||
}
|
||||
|
||||
[TestCase("chrome", ExpectedResult = true)]
|
||||
[TestCase("CHROME", ExpectedResult = true)]
|
||||
[TestCase("Google", ExpectedResult = true)]
|
||||
[TestCase("Google Chrome", ExpectedResult = true)]
|
||||
[TestCase("Google Chrome twitter", ExpectedResult = false)]
|
||||
public bool ChromeWebSitesShouldBeFilteredWhenSearchingForChrome(string query)
|
||||
{
|
||||
return _twitterChromePwa.FilterWebApplication(query);
|
||||
}
|
||||
|
||||
[TestCase("twitter", 0, ExpectedResult = false)]
|
||||
[TestCase("Twit", 0, ExpectedResult = false)]
|
||||
[TestCase("TWITTER", 0, ExpectedResult = false)]
|
||||
[TestCase("web", 1, ExpectedResult = false)]
|
||||
[TestCase("Page", 1, ExpectedResult = false)]
|
||||
[TestCase("WEB PAGE", 1, ExpectedResult = false)]
|
||||
[TestCase("edge", 2, ExpectedResult = false)]
|
||||
[TestCase("EDGE", 2, ExpectedResult = false)]
|
||||
public bool PinnedWebPagesShouldNotBeFilteredWhenSearchingForThem(string query, int scenario)
|
||||
{
|
||||
const int CASE_TWITTER = 0;
|
||||
const int CASE_WEB_PAGE = 1;
|
||||
const int CASE_EDGE_NAMED_WEBPAGE = 2;
|
||||
|
||||
// If the query is a part of the name of the web application, it should not be filtered,
|
||||
// even if the name is the same as that of the main application, eg: case 2 - edge
|
||||
switch (scenario)
|
||||
{
|
||||
case CASE_TWITTER:
|
||||
return _twitterChromePwa.FilterWebApplication(query);
|
||||
case CASE_WEB_PAGE:
|
||||
return _pinnedWebpage.FilterWebApplication(query);
|
||||
case CASE_EDGE_NAMED_WEBPAGE:
|
||||
return _edgeNamedPinnedWebpage.FilterWebApplication(query);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// unreachable code
|
||||
return true;
|
||||
}
|
||||
|
||||
[TestCase("Command Prompt")]
|
||||
[TestCase("cmd")]
|
||||
[TestCase("cmd.exe")]
|
||||
[TestCase("ignoreQueryText")]
|
||||
public void Win32ApplicationsShouldNotBeFilteredWhenFilteringRunCommands(string query)
|
||||
{
|
||||
// Even if there is an exact match in the name or exe name, win32 applications should never be filtered
|
||||
Assert.IsTrue(_commandPrompt.QueryEqualsNameForRunCommands(query));
|
||||
}
|
||||
|
||||
[TestCase("cmd")]
|
||||
[TestCase("Cmd")]
|
||||
[TestCase("CMD")]
|
||||
public void RunCommandsShouldNotBeFilteredOnExactMatch(string query)
|
||||
{
|
||||
// Partial matches should be filtered as cmd is not equal to cmder
|
||||
Assert.IsFalse(_cmderRunCommand.QueryEqualsNameForRunCommands(query));
|
||||
|
||||
// the query matches the name (cmd) and is therefore not filtered (case-insensitive)
|
||||
Assert.IsTrue(_cmdRunCommand.QueryEqualsNameForRunCommands(query));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WebApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mock = new Mock<IPublicAPI>();
|
||||
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
|
||||
|
||||
// Act
|
||||
List<ContextMenuResult> contextMenuResults = _pinnedWebpage.ContextMenus(mock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(contextMenuResults.Count, 3);
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InternetShortcutApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mock = new Mock<IPublicAPI>();
|
||||
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
|
||||
|
||||
// Act
|
||||
List<ContextMenuResult> contextMenuResults = _dummyInternetShortcutApp.ContextMenus(mock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(contextMenuResults.Count, 2);
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Win32ApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mock = new Mock<IPublicAPI>();
|
||||
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
|
||||
|
||||
// Act
|
||||
List<ContextMenuResult> contextMenuResults = _chrome.ContextMenus(mock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(contextMenuResults.Count, 3);
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RunCommandShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mock = new Mock<IPublicAPI>();
|
||||
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
|
||||
|
||||
// Act
|
||||
List<ContextMenuResult> contextMenuResults = _cmdRunCommand.ContextMenus(mock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(contextMenuResults.Count, 3);
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Win32AppsShouldSetNameAsTitleWhileCreatingResult()
|
||||
{
|
||||
var mock = new Mock<IPublicAPI>();
|
||||
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
|
||||
StringMatcher.Instance = new StringMatcher();
|
||||
|
||||
// Act
|
||||
var result = _cmderRunCommand.Result("cmder", mock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Title.Equals(_cmderRunCommand.Name, StringComparison.Ordinal));
|
||||
Assert.IsFalse(result.Title.Equals(_cmderRunCommand.Description, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
private static readonly Win32Program _notepadAppdata = new Win32Program
|
||||
{
|
||||
Name = "Notepad",
|
||||
ExecutableName = "notepad.exe",
|
||||
FullPath = "c:\\windows\\system32\\notepad.exe",
|
||||
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _notepadUsers = new Win32Program
|
||||
{
|
||||
Name = "Notepad",
|
||||
ExecutableName = "notepad.exe",
|
||||
FullPath = "c:\\windows\\system32\\notepad.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _azureCommandPrompt = new Win32Program
|
||||
{
|
||||
Name = "Microsoft Azure Command Prompt - v2.9",
|
||||
ExecutableName = "cmd.exe",
|
||||
FullPath = "c:\\windows\\system32\\cmd.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft azure\\microsoft azure sdk for .net\\v2.9\\microsoft azure command prompt - v2.9.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _visualStudioCommandPrompt = new Win32Program
|
||||
{
|
||||
Name = "x64 Native Tools Command Prompt for VS 2019",
|
||||
ExecutableName = "cmd.exe",
|
||||
FullPath = "c:\\windows\\system32\\cmd.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\visual studio 2019\\visual studio tools\\vc\\x64 native tools command prompt for vs 2019.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _commandPrompt = new Win32Program
|
||||
{
|
||||
Name = "Command Prompt",
|
||||
ExecutableName = "cmd.exe",
|
||||
FullPath = "c:\\windows\\system32\\cmd.exe",
|
||||
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\command prompt.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _fileExplorer = new Win32Program
|
||||
{
|
||||
Name = "File Explorer",
|
||||
ExecutableName = "File Explorer.lnk",
|
||||
FullPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\file explorer.lnk",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _wordpad = new Win32Program
|
||||
{
|
||||
Name = "Wordpad",
|
||||
ExecutableName = "wordpad.exe",
|
||||
FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\wordpad.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _wordpadDuplicate = new Win32Program
|
||||
{
|
||||
Name = "WORDPAD",
|
||||
ExecutableName = "WORDPAD.EXE",
|
||||
FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _twitterChromePwa = new Win32Program
|
||||
{
|
||||
Name = "Twitter",
|
||||
FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome_proxy.exe",
|
||||
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\chrome apps\\twitter.lnk",
|
||||
Arguments = " --profile-directory=Default --app-id=jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi",
|
||||
AppType = 0,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _pinnedWebpage = new Win32Program
|
||||
{
|
||||
Name = "Web page",
|
||||
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe",
|
||||
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\web page.lnk",
|
||||
Arguments = "--profile-directory=Default --app-id=homljgmgpmcbpjbnjpfijnhipfkiclkd",
|
||||
AppType = 0,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _edgeNamedPinnedWebpage = new Win32Program
|
||||
{
|
||||
Name = "edge - Bing",
|
||||
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe",
|
||||
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\edge - bing.lnk",
|
||||
Arguments = " --profile-directory=Default --app-id=aocfnapldcnfbofgmbbllojgocaelgdd",
|
||||
AppType = 0,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _msedge = new Win32Program
|
||||
{
|
||||
Name = "Microsoft Edge",
|
||||
ExecutableName = "msedge.exe",
|
||||
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft edge.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _chrome = new Win32Program
|
||||
{
|
||||
Name = "Google Chrome",
|
||||
ExecutableName = "chrome.exe",
|
||||
FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\google chrome.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _dummyProxyApp = new Win32Program
|
||||
{
|
||||
Name = "Proxy App",
|
||||
ExecutableName = "test_proxy.exe",
|
||||
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\test_proxy.exe",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\test proxy.lnk",
|
||||
AppType = 2,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _cmdRunCommand = new Win32Program
|
||||
{
|
||||
Name = "cmd",
|
||||
ExecutableName = "cmd.exe",
|
||||
FullPath = "c:\\windows\\system32\\cmd.exe",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 3, // Run command
|
||||
};
|
||||
|
||||
private static readonly Win32Program _cmderRunCommand = new Win32Program
|
||||
{
|
||||
Name = "Cmder",
|
||||
Description = "Cmder: Lovely Console Emulator",
|
||||
ExecutableName = "Cmder.exe",
|
||||
FullPath = "c:\\tools\\cmder\\cmder.exe",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 3, // Run command
|
||||
};
|
||||
|
||||
private static readonly Win32Program _dummyInternetShortcutApp = new Win32Program
|
||||
{
|
||||
Name = "Shop Titans",
|
||||
ExecutableName = "Shop Titans.url",
|
||||
FullPath = "steam://rungameid/1258080",
|
||||
ParentDirectory = "C:\\Users\\temp\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Steam",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 1,
|
||||
};
|
||||
|
||||
private static readonly Win32Program _dummyInternetShortcutAppDuplicate = new Win32Program
|
||||
{
|
||||
Name = "Shop Titans",
|
||||
ExecutableName = "Shop Titans.url",
|
||||
FullPath = "steam://rungameid/1258080",
|
||||
ParentDirectory = "C:\\Users\\temp\\Desktop",
|
||||
LnkResolvedPath = null,
|
||||
AppType = 1,
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void DedupFunctionWhenCalledMustRemoveDuplicateNotepads()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32Program> prgms = new List<Win32Program>
|
||||
{
|
||||
_notepadAppdata,
|
||||
_notepadUsers,
|
||||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DedupFunctionWhenCalledMustRemoveInternetShortcuts()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32Program> prgms = new List<Win32Program>
|
||||
{
|
||||
_dummyInternetShortcutApp,
|
||||
_dummyInternetShortcutAppDuplicate,
|
||||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DedupFunctionWhenCalledMustNotRemovelnkWhichdoesNotHaveExe()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32Program> prgms = new List<Win32Program>
|
||||
{
|
||||
_fileExplorer,
|
||||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DedupFunctionMustRemoveDuplicatesForExeExtensionsWithoutLnkResolvedPath()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32Program> prgms = new List<Win32Program>
|
||||
{
|
||||
_wordpad,
|
||||
_wordpadDuplicate,
|
||||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
Assert.IsTrue(!string.IsNullOrEmpty(apps[0].LnkResolvedPath));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DedupFunctionMustNotRemoveProgramsWithSameExeNameAndFullPath()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32Program> prgms = new List<Win32Program>
|
||||
{
|
||||
_azureCommandPrompt,
|
||||
_visualStudioCommandPrompt,
|
||||
_commandPrompt,
|
||||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FunctionIsWebApplicationShouldReturnTrueForWebApplications()
|
||||
{
|
||||
// The IsWebApplication(() function must return true for all PWAs and pinned web pages
|
||||
Assert.IsTrue(_twitterChromePwa.IsWebApplication());
|
||||
Assert.IsTrue(_pinnedWebpage.IsWebApplication());
|
||||
Assert.IsTrue(_edgeNamedPinnedWebpage.IsWebApplication());
|
||||
|
||||
// Should not filter apps whose executable name ends with proxy.exe
|
||||
Assert.IsFalse(_dummyProxyApp.IsWebApplication());
|
||||
}
|
||||
|
||||
[TestCase("ignore")]
|
||||
public void FunctionFilterWebApplicationShouldReturnFalseWhenSearchingForTheMainApp(string query)
|
||||
{
|
||||
// Irrespective of the query, the FilterWebApplication() Function must not filter main apps such as edge and chrome
|
||||
Assert.IsFalse(_msedge.FilterWebApplication(query));
|
||||
Assert.IsFalse(_chrome.FilterWebApplication(query));
|
||||
}
|
||||
|
||||
[TestCase("edge", ExpectedResult = true)]
|
||||
[TestCase("EDGE", ExpectedResult = true)]
|
||||
[TestCase("msedge", ExpectedResult = true)]
|
||||
[TestCase("Microsoft", ExpectedResult = true)]
|
||||
[TestCase("edg", ExpectedResult = true)]
|
||||
[TestCase("Edge page", ExpectedResult = false)]
|
||||
[TestCase("Edge Web page", ExpectedResult = false)]
|
||||
public bool EdgeWebSitesShouldBeFilteredWhenSearchingForEdge(string query)
|
||||
{
|
||||
return _pinnedWebpage.FilterWebApplication(query);
|
||||
}
|
||||
|
||||
[TestCase("chrome", ExpectedResult = true)]
|
||||
[TestCase("CHROME", ExpectedResult = true)]
|
||||
[TestCase("Google", ExpectedResult = true)]
|
||||
[TestCase("Google Chrome", ExpectedResult = true)]
|
||||
[TestCase("Google Chrome twitter", ExpectedResult = false)]
|
||||
public bool ChromeWebSitesShouldBeFilteredWhenSearchingForChrome(string query)
|
||||
{
|
||||
return _twitterChromePwa.FilterWebApplication(query);
|
||||
}
|
||||
|
||||
[TestCase("twitter", 0, ExpectedResult = false)]
|
||||
[TestCase("Twit", 0, ExpectedResult = false)]
|
||||
[TestCase("TWITTER", 0, ExpectedResult = false)]
|
||||
[TestCase("web", 1, ExpectedResult = false)]
|
||||
[TestCase("Page", 1, ExpectedResult = false)]
|
||||
[TestCase("WEB PAGE", 1, ExpectedResult = false)]
|
||||
[TestCase("edge", 2, ExpectedResult = false)]
|
||||
[TestCase("EDGE", 2, ExpectedResult = false)]
|
||||
public bool PinnedWebPagesShouldNotBeFilteredWhenSearchingForThem(string query, int scenario)
|
||||
{
|
||||
const int CASE_TWITTER = 0;
|
||||
const int CASE_WEB_PAGE = 1;
|
||||
const int CASE_EDGE_NAMED_WEBPAGE = 2;
|
||||
|
||||
// If the query is a part of the name of the web application, it should not be filtered,
|
||||
// even if the name is the same as that of the main application, eg: case 2 - edge
|
||||
switch (scenario)
|
||||
{
|
||||
case CASE_TWITTER:
|
||||
return _twitterChromePwa.FilterWebApplication(query);
|
||||
case CASE_WEB_PAGE:
|
||||
return _pinnedWebpage.FilterWebApplication(query);
|
||||
case CASE_EDGE_NAMED_WEBPAGE:
|
||||
return _edgeNamedPinnedWebpage.FilterWebApplication(query);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// unreachable code
|
||||
return true;
|
||||
}
|
||||
|
||||
[TestCase("Command Prompt")]
|
||||
[TestCase("cmd")]
|
||||
[TestCase("cmd.exe")]
|
||||
[TestCase("ignoreQueryText")]
|
||||
public void Win32ApplicationsShouldNotBeFilteredWhenFilteringRunCommands(string query)
|
||||
{
|
||||
// Even if there is an exact match in the name or exe name, win32 applications should never be filtered
|
||||
Assert.IsTrue(_commandPrompt.QueryEqualsNameForRunCommands(query));
|
||||
}
|
||||
|
||||
[TestCase("cmd")]
|
||||
[TestCase("Cmd")]
|
||||
[TestCase("CMD")]
|
||||
public void RunCommandsShouldNotBeFilteredOnExactMatch(string query)
|
||||
{
|
||||
// Partial matches should be filtered as cmd is not equal to cmder
|
||||
Assert.IsFalse(_cmderRunCommand.QueryEqualsNameForRunCommands(query));
|
||||
|
||||
// the query matches the name (cmd) and is therefore not filtered (case-insensitive)
|
||||
Assert.IsTrue(_cmdRunCommand.QueryEqualsNameForRunCommands(query));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WebApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mock = new Mock<IPublicAPI>();
|
||||
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
|
||||
|
||||
// Act
|
||||
List<ContextMenuResult> contextMenuResults = _pinnedWebpage.ContextMenus(mock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(contextMenuResults.Count, 3);
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InternetShortcutApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mock = new Mock<IPublicAPI>();
|
||||
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
|
||||
|
||||
// Act
|
||||
List<ContextMenuResult> contextMenuResults = _dummyInternetShortcutApp.ContextMenus(mock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(contextMenuResults.Count, 2);
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Win32ApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mock = new Mock<IPublicAPI>();
|
||||
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
|
||||
|
||||
// Act
|
||||
List<ContextMenuResult> contextMenuResults = _chrome.ContextMenus(mock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(contextMenuResults.Count, 3);
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RunCommandShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mock = new Mock<IPublicAPI>();
|
||||
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
|
||||
|
||||
// Act
|
||||
List<ContextMenuResult> contextMenuResults = _cmdRunCommand.ContextMenus(mock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(contextMenuResults.Count, 3);
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
|
||||
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Win32AppsShouldSetNameAsTitleWhileCreatingResult()
|
||||
{
|
||||
var mock = new Mock<IPublicAPI>();
|
||||
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
|
||||
StringMatcher.Instance = new StringMatcher();
|
||||
|
||||
// Act
|
||||
var result = _cmderRunCommand.Result("cmder", mock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.Title.Equals(_cmderRunCommand.Name, StringComparison.Ordinal));
|
||||
Assert.IsFalse(result.Title.Equals(_cmderRunCommand.Description, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +1,136 @@
|
||||
// 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.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Wox.Infrastructure.Storage;
|
||||
|
||||
namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
{
|
||||
[TestFixture]
|
||||
public class ListRepositoryTests
|
||||
{
|
||||
[Test]
|
||||
public void ContainsShouldReturnTrueWhenListIsInitializedWithItem()
|
||||
{
|
||||
// Arrange
|
||||
var itemName = "originalItem1";
|
||||
IRepository<string> repository = new ListRepository<string>() { itemName };
|
||||
|
||||
// Act
|
||||
var result = repository.Contains(itemName);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ContainsShouldReturnTrueWhenListIsUpdatedWithAdd()
|
||||
{
|
||||
// Arrange
|
||||
IRepository<string> repository = new ListRepository<string>();
|
||||
|
||||
// Act
|
||||
var itemName = "newItem";
|
||||
repository.Add(itemName);
|
||||
var result = repository.Contains(itemName);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ContainsShouldReturnFalseWhenListIsUpdatedWithRemove()
|
||||
{
|
||||
// Arrange
|
||||
var itemName = "originalItem1";
|
||||
IRepository<string> repository = new ListRepository<string>() { itemName };
|
||||
|
||||
// Act
|
||||
repository.Remove(itemName);
|
||||
var result = repository.Contains(itemName);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AddShouldNotThrowWhenBeingIterated()
|
||||
{
|
||||
// Arrange
|
||||
ListRepository<string> repository = new ListRepository<string>();
|
||||
var numItems = 1000;
|
||||
for (var i = 0; i < numItems; ++i)
|
||||
{
|
||||
repository.Add($"OriginalItem_{i}");
|
||||
}
|
||||
|
||||
// Act - Begin iterating on one thread
|
||||
var iterationTask = Task.Run(() =>
|
||||
{
|
||||
var remainingIterations = 10000;
|
||||
while (remainingIterations > 0)
|
||||
{
|
||||
foreach (var item in repository)
|
||||
{
|
||||
// keep iterating
|
||||
}
|
||||
|
||||
--remainingIterations;
|
||||
}
|
||||
});
|
||||
|
||||
// Act - Insert on another thread
|
||||
var addTask = Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < numItems; ++i)
|
||||
{
|
||||
repository.Add($"NewItem_{i}");
|
||||
}
|
||||
});
|
||||
|
||||
// Assert that this does not throw. Collections that aren't syncronized will throw an invalidoperatioexception if the list is modified while enumerating
|
||||
await Task.WhenAll(new Task[] { iterationTask, addTask }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RemoveShouldNotThrowWhenBeingIterated()
|
||||
{
|
||||
// Arrange
|
||||
ListRepository<string> repository = new ListRepository<string>();
|
||||
var numItems = 1000;
|
||||
for (var i = 0; i < numItems; ++i)
|
||||
{
|
||||
repository.Add($"OriginalItem_{i}");
|
||||
}
|
||||
|
||||
// Act - Begin iterating on one thread
|
||||
var iterationTask = Task.Run(() =>
|
||||
{
|
||||
var remainingIterations = 10000;
|
||||
while (remainingIterations > 0)
|
||||
{
|
||||
foreach (var item in repository)
|
||||
{
|
||||
// keep iterating
|
||||
}
|
||||
|
||||
--remainingIterations;
|
||||
}
|
||||
});
|
||||
|
||||
// Act - Remove on another thread
|
||||
var addTask = Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < numItems; ++i)
|
||||
{
|
||||
repository.Remove($"OriginalItem_{i}");
|
||||
}
|
||||
});
|
||||
|
||||
// Assert that this does not throw. Collections that aren't syncronized will throw an invalidoperatioexception if the list is modified while enumerating
|
||||
await Task.WhenAll(new Task[] { iterationTask, addTask }).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Wox.Infrastructure.Storage;
|
||||
|
||||
namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
{
|
||||
[TestFixture]
|
||||
public class ListRepositoryTests
|
||||
{
|
||||
[Test]
|
||||
public void ContainsShouldReturnTrueWhenListIsInitializedWithItem()
|
||||
{
|
||||
// Arrange
|
||||
var itemName = "originalItem1";
|
||||
IRepository<string> repository = new ListRepository<string>() { itemName };
|
||||
|
||||
// Act
|
||||
var result = repository.Contains(itemName);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ContainsShouldReturnTrueWhenListIsUpdatedWithAdd()
|
||||
{
|
||||
// Arrange
|
||||
IRepository<string> repository = new ListRepository<string>();
|
||||
|
||||
// Act
|
||||
var itemName = "newItem";
|
||||
repository.Add(itemName);
|
||||
var result = repository.Contains(itemName);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ContainsShouldReturnFalseWhenListIsUpdatedWithRemove()
|
||||
{
|
||||
// Arrange
|
||||
var itemName = "originalItem1";
|
||||
IRepository<string> repository = new ListRepository<string>() { itemName };
|
||||
|
||||
// Act
|
||||
repository.Remove(itemName);
|
||||
var result = repository.Contains(itemName);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AddShouldNotThrowWhenBeingIterated()
|
||||
{
|
||||
// Arrange
|
||||
ListRepository<string> repository = new ListRepository<string>();
|
||||
var numItems = 1000;
|
||||
for (var i = 0; i < numItems; ++i)
|
||||
{
|
||||
repository.Add($"OriginalItem_{i}");
|
||||
}
|
||||
|
||||
// Act - Begin iterating on one thread
|
||||
var iterationTask = Task.Run(() =>
|
||||
{
|
||||
var remainingIterations = 10000;
|
||||
while (remainingIterations > 0)
|
||||
{
|
||||
foreach (var item in repository)
|
||||
{
|
||||
// keep iterating
|
||||
}
|
||||
|
||||
--remainingIterations;
|
||||
}
|
||||
});
|
||||
|
||||
// Act - Insert on another thread
|
||||
var addTask = Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < numItems; ++i)
|
||||
{
|
||||
repository.Add($"NewItem_{i}");
|
||||
}
|
||||
});
|
||||
|
||||
// Assert that this does not throw. Collections that aren't syncronized will throw an invalidoperatioexception if the list is modified while enumerating
|
||||
await Task.WhenAll(new Task[] { iterationTask, addTask }).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RemoveShouldNotThrowWhenBeingIterated()
|
||||
{
|
||||
// Arrange
|
||||
ListRepository<string> repository = new ListRepository<string>();
|
||||
var numItems = 1000;
|
||||
for (var i = 0; i < numItems; ++i)
|
||||
{
|
||||
repository.Add($"OriginalItem_{i}");
|
||||
}
|
||||
|
||||
// Act - Begin iterating on one thread
|
||||
var iterationTask = Task.Run(() =>
|
||||
{
|
||||
var remainingIterations = 10000;
|
||||
while (remainingIterations > 0)
|
||||
{
|
||||
foreach (var item in repository)
|
||||
{
|
||||
// keep iterating
|
||||
}
|
||||
|
||||
--remainingIterations;
|
||||
}
|
||||
});
|
||||
|
||||
// Act - Remove on another thread
|
||||
var addTask = Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < numItems; ++i)
|
||||
{
|
||||
repository.Remove($"OriginalItem_{i}");
|
||||
}
|
||||
});
|
||||
|
||||
// Assert that this does not throw. Collections that aren't syncronized will throw an invalidoperatioexception if the list is modified while enumerating
|
||||
await Task.WhenAll(new Task[] { iterationTask, addTask }).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// 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.Plugin.Program.UnitTests.Storage
|
||||
{
|
||||
public class PackageRepositoryTest
|
||||
{
|
||||
}
|
||||
}
|
||||
// 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.Plugin.Program.UnitTests.Storage
|
||||
{
|
||||
public class PackageRepositoryTest
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// 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;
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Plugin.Program.Views;
|
||||
using Wox.Plugin;
|
||||
|
||||
|
||||
namespace Microsoft.Plugin.Program
|
||||
{
|
||||
/// <summary>
|
||||
@@ -39,8 +39,8 @@ namespace Microsoft.Plugin.Program
|
||||
|
||||
private void BrowseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
using (var dialog = new FolderBrowserDialog())
|
||||
{
|
||||
using (var dialog = new FolderBrowserDialog())
|
||||
{
|
||||
DialogResult result = dialog.ShowDialog();
|
||||
if (result == System.Windows.Forms.DialogResult.OK)
|
||||
{
|
||||
@@ -57,7 +57,7 @@ namespace Microsoft.Plugin.Program
|
||||
System.Windows.MessageBox.Show(_context.API.GetTranslation("wox_plugin_program_invalid_path"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (_editing == null)
|
||||
{
|
||||
if (!ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == Directory.Text))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -32,18 +32,18 @@ namespace Microsoft.Plugin.Program.Logger
|
||||
}
|
||||
|
||||
var configuration = new LoggingConfiguration();
|
||||
using (var target = new FileTarget())
|
||||
using (var target = new FileTarget())
|
||||
{
|
||||
configuration.AddTarget("file", target);
|
||||
target.FileName = path.Replace(@"\", "/", StringComparison.Ordinal) + "/${shortdate}.txt";
|
||||
target.FileName = path.Replace(@"\", "/", StringComparison.Ordinal) + "/${shortdate}.txt";
|
||||
#if DEBUG
|
||||
var rule = new LoggingRule("*", LogLevel.Debug, target);
|
||||
var rule = new LoggingRule("*", LogLevel.Debug, target);
|
||||
#else
|
||||
var rule = new LoggingRule("*", LogLevel.Error, target);
|
||||
#endif
|
||||
configuration.LoggingRules.Add(rule);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LogManager.Configuration = configuration;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Microsoft.Plugin.Program.Logger
|
||||
|
||||
innerExceptionNumber++;
|
||||
e = e.InnerException;
|
||||
}
|
||||
}
|
||||
while (e != null);
|
||||
|
||||
logger.Error("------------- END Microsoft.Plugin.Program exception -------------");
|
||||
@@ -121,16 +121,16 @@ namespace Microsoft.Plugin.Program.Logger
|
||||
|
||||
private static bool IsKnownWinProgramError(Exception e, string callingMethodName)
|
||||
{
|
||||
if (e.TargetSite?.Name == "GetDescription" && callingMethodName == "LnkProgram")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (e is SecurityException || e is UnauthorizedAccessException || e is DirectoryNotFoundException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (e.TargetSite?.Name == "GetDescription" && callingMethodName == "LnkProgram")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (e is SecurityException || e is UnauthorizedAccessException || e is DirectoryNotFoundException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -138,17 +138,17 @@ namespace Microsoft.Plugin.Program.Logger
|
||||
{
|
||||
if (((e.HResult == -2147024774 || e.HResult == -2147009769) && callingMethodName == "ResourceFromPri")
|
||||
|| (e.HResult == -2147024894 && (callingMethodName == "LogoPathFromUri" || callingMethodName == "ImageFromPath"))
|
||||
|| (e.HResult == -2147024864 && callingMethodName == "InitializeAppInfo"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (callingMethodName == "XmlNamespaces")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|| (e.HResult == -2147024864 && callingMethodName == "InitializeAppInfo"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (callingMethodName == "XmlNamespaces")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,184 +1,184 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Plugin.Program.Programs;
|
||||
using Microsoft.Plugin.Program.Storage;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Plugin;
|
||||
using Stopwatch = Wox.Infrastructure.Stopwatch;
|
||||
|
||||
namespace Microsoft.Plugin.Program
|
||||
{
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Plugin.Program.Programs;
|
||||
using Microsoft.Plugin.Program.Storage;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Plugin;
|
||||
using Stopwatch = Wox.Infrastructure.Stopwatch;
|
||||
|
||||
namespace Microsoft.Plugin.Program
|
||||
{
|
||||
public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable, IReloadable, IDisposable
|
||||
{
|
||||
internal static ProgramPluginSettings Settings { get; set; }
|
||||
|
||||
private static bool IsStartupIndexProgramsRequired => Settings.LastIndexTime.AddDays(3) < DateTime.Today;
|
||||
|
||||
private static PluginInitContext _context;
|
||||
|
||||
private readonly PluginJsonStorage<ProgramPluginSettings> _settingsStorage;
|
||||
{
|
||||
internal static ProgramPluginSettings Settings { get; set; }
|
||||
|
||||
private static bool IsStartupIndexProgramsRequired => Settings.LastIndexTime.AddDays(3) < DateTime.Today;
|
||||
|
||||
private static PluginInitContext _context;
|
||||
|
||||
private readonly PluginJsonStorage<ProgramPluginSettings> _settingsStorage;
|
||||
private bool _disposed = false;
|
||||
private PackageRepository _packageRepository = new PackageRepository(new PackageCatalogWrapper(), new BinaryStorage<IList<UWPApplication>>("UWP"));
|
||||
private static Win32ProgramFileSystemWatchers _win32ProgramRepositoryHelper;
|
||||
private static Win32ProgramRepository _win32ProgramRepository;
|
||||
|
||||
public Main()
|
||||
{
|
||||
_settingsStorage = new PluginJsonStorage<ProgramPluginSettings>();
|
||||
Settings = _settingsStorage.Load();
|
||||
|
||||
// This helper class initializes the file system watchers based on the locations to watch
|
||||
_win32ProgramRepositoryHelper = new Win32ProgramFileSystemWatchers();
|
||||
|
||||
// Initialize the Win32ProgramRepository with the settings object
|
||||
_win32ProgramRepository = new Win32ProgramRepository(_win32ProgramRepositoryHelper.FileSystemWatchers.Cast<IFileSystemWatcherWrapper>().ToList(), new BinaryStorage<IList<Programs.Win32Program>>("Win32"), Settings, _win32ProgramRepositoryHelper.PathsToWatch);
|
||||
|
||||
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Preload programs cost", () =>
|
||||
{
|
||||
_win32ProgramRepository.Load();
|
||||
_packageRepository.Load();
|
||||
});
|
||||
Log.Info($"|Microsoft.Plugin.Program.Main|Number of preload win32 programs <{_win32ProgramRepository.Count()}>");
|
||||
|
||||
var a = Task.Run(() =>
|
||||
{
|
||||
if (IsStartupIndexProgramsRequired || !_win32ProgramRepository.Any())
|
||||
{
|
||||
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", _win32ProgramRepository.IndexPrograms);
|
||||
}
|
||||
});
|
||||
|
||||
var b = Task.Run(() =>
|
||||
{
|
||||
if (IsStartupIndexProgramsRequired || !_packageRepository.Any())
|
||||
{
|
||||
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", _packageRepository.IndexPrograms);
|
||||
}
|
||||
});
|
||||
|
||||
Task.WaitAll(a, b);
|
||||
|
||||
Settings.LastIndexTime = DateTime.Today;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_settingsStorage.Save();
|
||||
_win32ProgramRepository.Save();
|
||||
_packageRepository.Save();
|
||||
}
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
var results1 = _win32ProgramRepository.AsParallel()
|
||||
.Where(p => p.Enabled)
|
||||
.Select(p => p.Result(query.Search, _context.API));
|
||||
|
||||
var results2 = _packageRepository.AsParallel()
|
||||
.Where(p => p.Enabled)
|
||||
.Select(p => p.Result(query.Search, _context.API));
|
||||
|
||||
var result = results1.Concat(results2).Where(r => r != null && r.Score > 0);
|
||||
var maxScore = result.Max(x => x.Score);
|
||||
result = result.Where(x => x.Score > Settings.MinScoreThreshold * maxScore);
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
_context.API.ThemeChanged += OnThemeChanged;
|
||||
|
||||
UpdateUWPIconPath(_context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
public void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
{
|
||||
UpdateUWPIconPath(newTheme);
|
||||
}
|
||||
|
||||
public void UpdateUWPIconPath(Theme theme)
|
||||
{
|
||||
foreach (UWPApplication app in _packageRepository)
|
||||
{
|
||||
app.UpdatePath(theme);
|
||||
}
|
||||
}
|
||||
|
||||
public void IndexPrograms()
|
||||
{
|
||||
var t1 = Task.Run(() => _win32ProgramRepository.IndexPrograms());
|
||||
var t2 = Task.Run(() => _packageRepository.IndexPrograms());
|
||||
|
||||
Task.WaitAll(t1, t2);
|
||||
|
||||
Settings.LastIndexTime = DateTime.Today;
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_program_plugin_name");
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_program_plugin_description");
|
||||
}
|
||||
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
if (selectedResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selectedResult));
|
||||
}
|
||||
|
||||
var menuOptions = new List<ContextMenuResult>();
|
||||
if (selectedResult.ContextData is Programs.IProgram program)
|
||||
{
|
||||
menuOptions = program.ContextMenus(_context.API);
|
||||
}
|
||||
|
||||
return menuOptions;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive and show the user a warning message")]
|
||||
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (runProcess == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(runProcess));
|
||||
}
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
|
||||
runProcess(info);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
var name = "Plugin: Program";
|
||||
var message = $"Unable to start: {info.FileName}";
|
||||
_context.API.ShowMsg(name, message, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadData()
|
||||
{
|
||||
IndexPrograms();
|
||||
}
|
||||
private static Win32ProgramRepository _win32ProgramRepository;
|
||||
|
||||
public Main()
|
||||
{
|
||||
_settingsStorage = new PluginJsonStorage<ProgramPluginSettings>();
|
||||
Settings = _settingsStorage.Load();
|
||||
|
||||
// This helper class initializes the file system watchers based on the locations to watch
|
||||
_win32ProgramRepositoryHelper = new Win32ProgramFileSystemWatchers();
|
||||
|
||||
// Initialize the Win32ProgramRepository with the settings object
|
||||
_win32ProgramRepository = new Win32ProgramRepository(_win32ProgramRepositoryHelper.FileSystemWatchers.Cast<IFileSystemWatcherWrapper>().ToList(), new BinaryStorage<IList<Programs.Win32Program>>("Win32"), Settings, _win32ProgramRepositoryHelper.PathsToWatch);
|
||||
|
||||
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Preload programs cost", () =>
|
||||
{
|
||||
_win32ProgramRepository.Load();
|
||||
_packageRepository.Load();
|
||||
});
|
||||
Log.Info($"|Microsoft.Plugin.Program.Main|Number of preload win32 programs <{_win32ProgramRepository.Count()}>");
|
||||
|
||||
var a = Task.Run(() =>
|
||||
{
|
||||
if (IsStartupIndexProgramsRequired || !_win32ProgramRepository.Any())
|
||||
{
|
||||
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", _win32ProgramRepository.IndexPrograms);
|
||||
}
|
||||
});
|
||||
|
||||
var b = Task.Run(() =>
|
||||
{
|
||||
if (IsStartupIndexProgramsRequired || !_packageRepository.Any())
|
||||
{
|
||||
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", _packageRepository.IndexPrograms);
|
||||
}
|
||||
});
|
||||
|
||||
Task.WaitAll(a, b);
|
||||
|
||||
Settings.LastIndexTime = DateTime.Today;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_settingsStorage.Save();
|
||||
_win32ProgramRepository.Save();
|
||||
_packageRepository.Save();
|
||||
}
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
var results1 = _win32ProgramRepository.AsParallel()
|
||||
.Where(p => p.Enabled)
|
||||
.Select(p => p.Result(query.Search, _context.API));
|
||||
|
||||
var results2 = _packageRepository.AsParallel()
|
||||
.Where(p => p.Enabled)
|
||||
.Select(p => p.Result(query.Search, _context.API));
|
||||
|
||||
var result = results1.Concat(results2).Where(r => r != null && r.Score > 0);
|
||||
var maxScore = result.Max(x => x.Score);
|
||||
result = result.Where(x => x.Score > Settings.MinScoreThreshold * maxScore);
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
_context.API.ThemeChanged += OnThemeChanged;
|
||||
|
||||
UpdateUWPIconPath(_context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
public void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
{
|
||||
UpdateUWPIconPath(newTheme);
|
||||
}
|
||||
|
||||
public void UpdateUWPIconPath(Theme theme)
|
||||
{
|
||||
foreach (UWPApplication app in _packageRepository)
|
||||
{
|
||||
app.UpdatePath(theme);
|
||||
}
|
||||
}
|
||||
|
||||
public void IndexPrograms()
|
||||
{
|
||||
var t1 = Task.Run(() => _win32ProgramRepository.IndexPrograms());
|
||||
var t2 = Task.Run(() => _packageRepository.IndexPrograms());
|
||||
|
||||
Task.WaitAll(t1, t2);
|
||||
|
||||
Settings.LastIndexTime = DateTime.Today;
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_program_plugin_name");
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_program_plugin_description");
|
||||
}
|
||||
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
if (selectedResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selectedResult));
|
||||
}
|
||||
|
||||
var menuOptions = new List<ContextMenuResult>();
|
||||
if (selectedResult.ContextData is Programs.IProgram program)
|
||||
{
|
||||
menuOptions = program.ContextMenus(_context.API);
|
||||
}
|
||||
|
||||
return menuOptions;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive and show the user a warning message")]
|
||||
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (runProcess == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(runProcess));
|
||||
}
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
|
||||
runProcess(info);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
var name = "Plugin: Program";
|
||||
var message = $"Unable to start: {info.FileName}";
|
||||
_context.API.ShowMsg(name, message, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadData()
|
||||
{
|
||||
IndexPrograms();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -198,5 +198,5 @@ namespace Microsoft.Plugin.Program
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -10,11 +10,11 @@ namespace Microsoft.Plugin.Program
|
||||
public class ProgramPluginSettings
|
||||
{
|
||||
public DateTime LastIndexTime { get; set; }
|
||||
|
||||
|
||||
public List<ProgramSource> ProgramSources { get; } = new List<ProgramSource>();
|
||||
|
||||
|
||||
public List<DisabledProgramSource> DisabledProgramSources { get; } = new List<DisabledProgramSource>();
|
||||
|
||||
|
||||
public List<string> ProgramSuffixes { get; } = new List<string>() { "bat", "appref-ms", "exe", "lnk", "url" };
|
||||
|
||||
public bool EnableStartMenuSource { get; set; } = true;
|
||||
@@ -28,5 +28,5 @@ namespace Microsoft.Plugin.Program
|
||||
public double MinScoreThreshold { get; set; } = 0.75;
|
||||
|
||||
internal const char SuffixSeparator = ';';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// 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;
|
||||
// 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;
|
||||
using System.Windows;
|
||||
using Wox.Plugin;
|
||||
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
// 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.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using static Microsoft.Plugin.Program.Programs.UWP;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
public static class AppxPackageHelper
|
||||
{
|
||||
// This function returns a list of attributes of applications
|
||||
public static List<IAppxManifestApplication> GetAppsFromManifest(IStream stream)
|
||||
{
|
||||
List<IAppxManifestApplication> apps = new List<IAppxManifestApplication>();
|
||||
var appxFactory = new AppxFactory();
|
||||
var reader = ((IAppxFactory)appxFactory).CreateManifestReader(stream);
|
||||
var manifestApps = reader.GetApplications();
|
||||
|
||||
while (manifestApps.GetHasCurrent())
|
||||
{
|
||||
var manifestApp = manifestApps.GetCurrent();
|
||||
var hr = manifestApp.GetStringValue("AppListEntry", out var appListEntry);
|
||||
_ = CheckHRAndReturnOrThrow(hr, appListEntry);
|
||||
if (appListEntry != "none")
|
||||
{
|
||||
apps.Add(manifestApp);
|
||||
}
|
||||
|
||||
manifestApps.MoveNext();
|
||||
}
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
public static T CheckHRAndReturnOrThrow<T>(Hresult hr, T result)
|
||||
{
|
||||
if (hr != Hresult.Ok)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR((int)hr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using static Microsoft.Plugin.Program.Programs.UWP;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
public static class AppxPackageHelper
|
||||
{
|
||||
// This function returns a list of attributes of applications
|
||||
public static List<IAppxManifestApplication> GetAppsFromManifest(IStream stream)
|
||||
{
|
||||
List<IAppxManifestApplication> apps = new List<IAppxManifestApplication>();
|
||||
var appxFactory = new AppxFactory();
|
||||
var reader = ((IAppxFactory)appxFactory).CreateManifestReader(stream);
|
||||
var manifestApps = reader.GetApplications();
|
||||
|
||||
while (manifestApps.GetHasCurrent())
|
||||
{
|
||||
var manifestApp = manifestApps.GetCurrent();
|
||||
var hr = manifestApp.GetStringValue("AppListEntry", out var appListEntry);
|
||||
_ = CheckHRAndReturnOrThrow(hr, appListEntry);
|
||||
if (appListEntry != "none")
|
||||
{
|
||||
apps.Add(manifestApp);
|
||||
}
|
||||
|
||||
manifestApps.MoveNext();
|
||||
}
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
public static T CheckHRAndReturnOrThrow<T>(Hresult hr, T result)
|
||||
{
|
||||
if (hr != Hresult.Ok)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR((int)hr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// 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 Windows.ApplicationModel;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
internal interface IPackageCatalog
|
||||
{
|
||||
event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling;
|
||||
|
||||
event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling;
|
||||
|
||||
event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating;
|
||||
}
|
||||
}
|
||||
// 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 Windows.ApplicationModel;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
internal interface IPackageCatalog
|
||||
{
|
||||
event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling;
|
||||
|
||||
event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling;
|
||||
|
||||
event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
// 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 Windows.ApplicationModel;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a simple wrapper class around the PackageCatalog to facilitate unit testing.
|
||||
/// </summary>
|
||||
internal class PackageCatalogWrapper : IPackageCatalog
|
||||
{
|
||||
private PackageCatalog _packageCatalog;
|
||||
|
||||
public PackageCatalogWrapper()
|
||||
{
|
||||
// Opens the catalog of packages that is available for the current user.
|
||||
_packageCatalog = PackageCatalog.OpenForCurrentUser();
|
||||
}
|
||||
|
||||
// Summary: Indicates that an app package is installing.
|
||||
public event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling
|
||||
{
|
||||
add
|
||||
{
|
||||
_packageCatalog.PackageInstalling += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
_packageCatalog.PackageInstalling -= value;
|
||||
}
|
||||
}
|
||||
|
||||
// Summary: Indicates that an app package is uninstalling.
|
||||
public event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling
|
||||
{
|
||||
add
|
||||
{
|
||||
_packageCatalog.PackageUninstalling += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
_packageCatalog.PackageUninstalling -= value;
|
||||
}
|
||||
}
|
||||
|
||||
// Summary: Indicates that an app package is updating.
|
||||
public event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating
|
||||
{
|
||||
add
|
||||
{
|
||||
_packageCatalog.PackageUpdating += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
_packageCatalog.PackageUpdating -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 Windows.ApplicationModel;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a simple wrapper class around the PackageCatalog to facilitate unit testing.
|
||||
/// </summary>
|
||||
internal class PackageCatalogWrapper : IPackageCatalog
|
||||
{
|
||||
private PackageCatalog _packageCatalog;
|
||||
|
||||
public PackageCatalogWrapper()
|
||||
{
|
||||
// Opens the catalog of packages that is available for the current user.
|
||||
_packageCatalog = PackageCatalog.OpenForCurrentUser();
|
||||
}
|
||||
|
||||
// Summary: Indicates that an app package is installing.
|
||||
public event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling
|
||||
{
|
||||
add
|
||||
{
|
||||
_packageCatalog.PackageInstalling += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
_packageCatalog.PackageInstalling -= value;
|
||||
}
|
||||
}
|
||||
|
||||
// Summary: Indicates that an app package is uninstalling.
|
||||
public event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling
|
||||
{
|
||||
add
|
||||
{
|
||||
_packageCatalog.PackageUninstalling += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
_packageCatalog.PackageUninstalling -= value;
|
||||
}
|
||||
}
|
||||
|
||||
// Summary: Indicates that an app package is updating.
|
||||
public event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating
|
||||
{
|
||||
add
|
||||
{
|
||||
_packageCatalog.PackageUpdating += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
_packageCatalog.PackageUpdating -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
using System.IO;
|
||||
using Microsoft.Plugin.Program.Logger;
|
||||
@@ -23,9 +23,9 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
public string InstalledLocation { get; } = string.Empty;
|
||||
|
||||
public PackageWrapper()
|
||||
{
|
||||
}
|
||||
public PackageWrapper()
|
||||
{
|
||||
}
|
||||
|
||||
public PackageWrapper(string name, string fullName, string familyName, bool isFramework, bool isDevelopmentMode, string installedLocation)
|
||||
{
|
||||
@@ -39,9 +39,9 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
public static PackageWrapper GetWrapperFromPackage(Package package)
|
||||
{
|
||||
if (package == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
if (package == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
}
|
||||
|
||||
string path;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
@@ -24,7 +24,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")]
|
||||
private struct WIN32_FIND_DATAW
|
||||
{
|
||||
{
|
||||
public uint dwFileAttributes;
|
||||
public long ftCreationTime;
|
||||
public long ftLastAccessTime;
|
||||
@@ -39,8 +39,8 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
public string cAlternateFileName;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Represents flags specified in IShellLink interface")]
|
||||
[Flags]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Represents flags specified in IShellLink interface")]
|
||||
public enum SLR_FLAGS
|
||||
{
|
||||
SLR_NO_UI = 0x1,
|
||||
@@ -52,71 +52,71 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
SLR_NOLINKINFO = 0x40,
|
||||
SLR_INVOKE_MSI = 0x80,
|
||||
}
|
||||
|
||||
|
||||
// Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW
|
||||
|
||||
|
||||
// The IShellLink interface allows Shell links to be created, modified, and resolved
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("000214F9-0000-0000-C000-000000000046")]
|
||||
private interface IShellLinkW
|
||||
{
|
||||
/// <summary>Retrieves the path and file name of a Shell link object</summary>
|
||||
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
|
||||
|
||||
|
||||
/// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
|
||||
void GetIDList(out IntPtr ppidl);
|
||||
|
||||
|
||||
/// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary>
|
||||
void SetIDList(IntPtr pidl);
|
||||
|
||||
|
||||
/// <summary>Retrieves the description string for a Shell link object</summary>
|
||||
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
||||
|
||||
|
||||
/// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary>
|
||||
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||
|
||||
|
||||
/// <summary>Retrieves the name of the working directory for a Shell link object</summary>
|
||||
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
||||
|
||||
|
||||
/// <summary>Sets the name of the working directory for a Shell link object</summary>
|
||||
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||
|
||||
|
||||
/// <summary>Retrieves the command-line arguments associated with a Shell link object</summary>
|
||||
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
|
||||
|
||||
|
||||
/// <summary>Sets the command-line arguments for a Shell link object</summary>
|
||||
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
||||
|
||||
|
||||
/// <summary>Retrieves the hot key for a Shell link object</summary>
|
||||
void GetHotkey(out short pwHotkey);
|
||||
|
||||
|
||||
/// <summary>Sets a hot key for a Shell link object</summary>
|
||||
void SetHotkey(short wHotkey);
|
||||
|
||||
|
||||
/// <summary>Retrieves the show command for a Shell link object</summary>
|
||||
void GetShowCmd(out int piShowCmd);
|
||||
|
||||
|
||||
/// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary>
|
||||
void SetShowCmd(int iShowCmd);
|
||||
|
||||
|
||||
/// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary>
|
||||
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
||||
|
||||
|
||||
/// <summary>Sets the location (path and index) of the icon for a Shell link object</summary>
|
||||
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||
|
||||
|
||||
/// <summary>Sets the relative path to the Shell link object</summary>
|
||||
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
||||
|
||||
|
||||
/// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary>
|
||||
void Resolve(ref Accessibility._RemotableHandle hwnd, SLR_FLAGS fFlags);
|
||||
|
||||
|
||||
/// <summary>Sets the path and file name of a Shell link object</summary>
|
||||
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[ComImport]
|
||||
[Guid("00021401-0000-0000-C000-000000000046")]
|
||||
private class ShellLink
|
||||
{
|
||||
@@ -127,7 +127,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
// Contains the arguments to the app
|
||||
public string Arguments { get; set; } = string.Empty;
|
||||
|
||||
|
||||
public bool HasArguments { get; set; } = false;
|
||||
|
||||
// Retrieve the target path using Shell Link
|
||||
@@ -173,8 +173,8 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
HasArguments = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -10,20 +10,20 @@ using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Plugin.Program.Logger;
|
||||
using Microsoft.Plugin.Program.Win32;
|
||||
using Wox.Infrastructure.Logger;
|
||||
|
||||
using Microsoft.Plugin.Program.Win32;
|
||||
using Wox.Infrastructure.Logger;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
[Serializable]
|
||||
public partial class UWP
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
|
||||
public string FullName { get; }
|
||||
|
||||
public string FamilyName { get; }
|
||||
|
||||
|
||||
public string FamilyName { get; }
|
||||
|
||||
public string Location { get; set; }
|
||||
|
||||
public IList<UWPApplication> Apps { get; private set; }
|
||||
@@ -33,12 +33,12 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
public static IPackageManager PackageManagerWrapper { get; set; } = new PackageManagerWrapper();
|
||||
|
||||
public UWP(IPackage package)
|
||||
{
|
||||
if (package == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
}
|
||||
|
||||
{
|
||||
if (package == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
}
|
||||
|
||||
Name = package.Name;
|
||||
FullName = package.FullName;
|
||||
FamilyName = package.FamilyName;
|
||||
@@ -58,36 +58,36 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
if (hResult == Hresult.Ok)
|
||||
{
|
||||
var apps = new List<UWPApplication>();
|
||||
|
||||
var apps = new List<UWPApplication>();
|
||||
|
||||
List<IAppxManifestApplication> appsViaManifests = AppxPackageHelper.GetAppsFromManifest(stream);
|
||||
foreach (var appInManifest in appsViaManifests)
|
||||
{
|
||||
var app = new UWPApplication(appInManifest, this);
|
||||
apps.Add(app);
|
||||
}
|
||||
|
||||
Apps = apps.Where(a =>
|
||||
{
|
||||
var valid =
|
||||
!string.IsNullOrEmpty(a.UserModelId) &&
|
||||
!string.IsNullOrEmpty(a.DisplayName) &&
|
||||
}
|
||||
|
||||
Apps = apps.Where(a =>
|
||||
{
|
||||
var valid =
|
||||
!string.IsNullOrEmpty(a.UserModelId) &&
|
||||
!string.IsNullOrEmpty(a.DisplayName) &&
|
||||
a.AppListEntry != "none";
|
||||
|
||||
return valid;
|
||||
return valid;
|
||||
}).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
var e = Marshal.GetExceptionForHR((int)hResult);
|
||||
ProgramLogger.LogException(
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|InitializeAppInfo|{path}" +
|
||||
"|Error caused while trying to get the details of the UWP program", e);
|
||||
|
||||
Apps = new List<UWPApplication>().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
|
||||
private static string[] XmlNamespaces(string path)
|
||||
{
|
||||
@@ -128,15 +128,15 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
}
|
||||
|
||||
ProgramLogger.LogException(
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|XmlNamespaces|{Location}" +
|
||||
"|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version "
|
||||
+ $"{FullName} from location {Location} is returned.", new FormatException());
|
||||
|
||||
Version = PackageVersion.Unknown;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive.")]
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive.")]
|
||||
public static UWPApplication[] All()
|
||||
{
|
||||
var windows10 = new Version(10, 0);
|
||||
@@ -153,12 +153,12 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.LogException(
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and "
|
||||
+ $"unable to convert Package to UWP for {p.FullName}", e);
|
||||
return Array.Empty<UWPApplication>();
|
||||
}
|
||||
|
||||
|
||||
return u.Apps;
|
||||
}).ToArray();
|
||||
|
||||
@@ -192,8 +192,8 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
ProgramLogger.LogException("UWP", "CurrentUserPackages", $"id", "An unexpected error occurred and unable to verify if package is valid", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return valid;
|
||||
});
|
||||
|
||||
@@ -230,8 +230,8 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
Unknown,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1714:Flags enums should have plural names", Justification = "This name is consistent with the corresponding win32 flags: https://docs.microsoft.com/en-us/windows/win32/stg/stgm-constants ")]
|
||||
[Flags]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1714:Flags enums should have plural names", Justification = "This name is consistent with the corresponding win32 flags: https://docs.microsoft.com/en-us/windows/win32/stg/stgm-constants ")]
|
||||
public enum Stgm : long
|
||||
{
|
||||
Read = 0x00000000L,
|
||||
@@ -240,6 +240,6 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
public enum Hresult : int
|
||||
{
|
||||
Ok = 0x0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -16,481 +16,481 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.Plugin.Program.Logger;
|
||||
using Microsoft.Plugin.Program.Win32;
|
||||
using Microsoft.Plugin.Program.Win32;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Image;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Image;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Plugin;
|
||||
using Wox.Plugin.SharedCommands;
|
||||
using static Microsoft.Plugin.Program.Programs.UWP;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
[Serializable]
|
||||
public class UWPApplication : IProgram
|
||||
{
|
||||
public string AppListEntry { get; set; }
|
||||
|
||||
public string UniqueIdentifier { get; set; }
|
||||
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string UserModelId { get; set; }
|
||||
|
||||
public string BackgroundColor { get; set; }
|
||||
|
||||
public string EntryPoint { get; set; }
|
||||
|
||||
public string Name => DisplayName;
|
||||
|
||||
public string Location => Package.Location;
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public bool CanRunElevated { get; set; }
|
||||
|
||||
public string LogoPath { get; set; }
|
||||
|
||||
public UWP Package { get; set; }
|
||||
|
||||
private string logoUri;
|
||||
|
||||
// Function to calculate the score of a result
|
||||
private int Score(string query)
|
||||
{
|
||||
var displayNameMatch = StringMatcher.FuzzySearch(query, DisplayName);
|
||||
var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
|
||||
var score = new[] { displayNameMatch.Score, descriptionMatch.Score / 2 }.Max();
|
||||
return score;
|
||||
}
|
||||
|
||||
// Function to set the subtitle based on the Type of application
|
||||
private static string SetSubtitle(IPublicAPI api)
|
||||
{
|
||||
return api.GetTranslation("powertoys_run_plugin_program_packaged_application");
|
||||
}
|
||||
|
||||
public Result Result(string query, IPublicAPI api)
|
||||
{
|
||||
if (api == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(api));
|
||||
}
|
||||
|
||||
var score = Score(query);
|
||||
if (score <= 0)
|
||||
{ // no need to create result if score is 0
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new Result
|
||||
{
|
||||
SubTitle = SetSubtitle(api),
|
||||
Icon = Logo,
|
||||
Score = score,
|
||||
ContextData = this,
|
||||
Action = e =>
|
||||
{
|
||||
Launch(api);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
// To set the title to always be the displayname of the packaged application
|
||||
result.Title = DisplayName;
|
||||
result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData;
|
||||
|
||||
var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_name"), result.Title);
|
||||
var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_path"), Package.Location);
|
||||
result.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive.")]
|
||||
public List<ContextMenuResult> ContextMenus(IPublicAPI api)
|
||||
{
|
||||
if (api == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(api));
|
||||
}
|
||||
|
||||
var contextMenus = new List<ContextMenuResult>();
|
||||
|
||||
if (CanRunElevated)
|
||||
{
|
||||
contextMenus.Add(
|
||||
new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = api.GetTranslation("wox_plugin_program_run_as_administrator"),
|
||||
Glyph = "\xE7EF",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.Enter,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = _ =>
|
||||
{
|
||||
string command = "shell:AppsFolder\\" + UniqueIdentifier;
|
||||
command = Environment.ExpandEnvironmentVariables(command.Trim());
|
||||
|
||||
var info = ShellCommand.SetProcessStartInfo(command, verb: "runas");
|
||||
info.UseShellExecute = true;
|
||||
|
||||
Process.Start(info);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
contextMenus.Add(
|
||||
new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = api.GetTranslation("wox_plugin_program_open_containing_folder"),
|
||||
Glyph = "\xE838",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.E,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = _ =>
|
||||
{
|
||||
Main.StartProcess(Process.Start, new ProcessStartInfo("explorer", Package.Location));
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
contextMenus.Add(new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = api.GetTranslation("wox_plugin_program_open_in_console"),
|
||||
Glyph = "\xE756",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = (context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Helper.OpenInConsole(Package.Location);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Program.UWP.ContextMenu| Failed to open {Name} in console, {e.Message}", e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return contextMenus;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive, and showing the user an error message")]
|
||||
private async void Launch(IPublicAPI api)
|
||||
{
|
||||
var appManager = new ApplicationActivationHelper.ApplicationActivationManager();
|
||||
const string noArgs = "";
|
||||
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
appManager.ActivateApplication(UserModelId, noArgs, noFlags, out uint unusedPid);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
var name = "Plugin: Program";
|
||||
var message = $"Can't start UWP: {DisplayName}";
|
||||
api.ShowMsg(name, message, string.Empty);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public UWPApplication(IAppxManifestApplication manifestApp, UWP package)
|
||||
{
|
||||
if (manifestApp == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(manifestApp));
|
||||
}
|
||||
|
||||
var hr = manifestApp.GetAppUserModelId(out var tmpUserModelId);
|
||||
UserModelId = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUserModelId);
|
||||
|
||||
hr = manifestApp.GetAppUserModelId(out var tmpUniqueIdentifier);
|
||||
UniqueIdentifier = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUniqueIdentifier);
|
||||
|
||||
hr = manifestApp.GetStringValue("DisplayName", out var tmpDisplayName);
|
||||
DisplayName = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDisplayName);
|
||||
|
||||
hr = manifestApp.GetStringValue("Description", out var tmpDescription);
|
||||
Description = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDescription);
|
||||
|
||||
hr = manifestApp.GetStringValue("BackgroundColor", out var tmpBackgroundColor);
|
||||
BackgroundColor = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpBackgroundColor);
|
||||
|
||||
hr = manifestApp.GetStringValue("EntryPoint", out var tmpEntryPoint);
|
||||
EntryPoint = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpEntryPoint);
|
||||
using static Microsoft.Plugin.Program.Programs.UWP;
|
||||
|
||||
Package = package ?? throw new ArgumentNullException(nameof(package));
|
||||
|
||||
DisplayName = ResourceFromPri(package.FullName, DisplayName);
|
||||
Description = ResourceFromPri(package.FullName, Description);
|
||||
logoUri = LogoUriFromManifest(manifestApp);
|
||||
|
||||
Enabled = true;
|
||||
CanRunElevated = IfApplicationcanRunElevated();
|
||||
}
|
||||
|
||||
private bool IfApplicationcanRunElevated()
|
||||
{
|
||||
if (EntryPoint == "Windows.FullTrustApplication")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var manifest = Package.Location + "\\AppxManifest.xml";
|
||||
if (File.Exists(manifest))
|
||||
{
|
||||
var file = File.ReadAllText(manifest);
|
||||
if (file.Contains("TrustLevel=\"mediumIL\"", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal string ResourceFromPri(string packageFullName, string resourceReference)
|
||||
{
|
||||
const string prefix = "ms-resource:";
|
||||
if (!string.IsNullOrWhiteSpace(resourceReference) && resourceReference.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// magic comes from @talynone
|
||||
// https://github.com/talynone/Wox.Plugin.WindowsUniversalAppLauncher/blob/master/StoreAppLauncher/Helpers/NativeApiHelper.cs#L139-L153
|
||||
string key = resourceReference.Substring(prefix.Length);
|
||||
string parsed;
|
||||
if (key.StartsWith("//", StringComparison.Ordinal))
|
||||
{
|
||||
parsed = prefix + key;
|
||||
}
|
||||
else if (key.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
parsed = prefix + "//" + key;
|
||||
}
|
||||
else if (key.Contains("resources", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
parsed = prefix + key;
|
||||
}
|
||||
else
|
||||
{
|
||||
parsed = prefix + "///resources/" + key;
|
||||
}
|
||||
|
||||
var outBuffer = new StringBuilder(128);
|
||||
string source = $"@{{{packageFullName}? {parsed}}}";
|
||||
var capacity = (uint)outBuffer.Capacity;
|
||||
var hResult = NativeMethods.SHLoadIndirectString(source, outBuffer, capacity, IntPtr.Zero);
|
||||
if (hResult == Hresult.Ok)
|
||||
{
|
||||
var loaded = outBuffer.ToString();
|
||||
if (!string.IsNullOrEmpty(loaded))
|
||||
{
|
||||
return loaded;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|ResourceFromPri|{Package.Location}|Can't load null or empty result "
|
||||
+ $"pri {source} in uwp location {Package.Location}", new NullReferenceException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// https://github.com/Wox-launcher/Wox/issues/964
|
||||
// known hresult 2147942522:
|
||||
// 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'.
|
||||
// for
|
||||
// Microsoft.MicrosoftOfficeHub_17.7608.23501.0_x64__8wekyb3d8bbwe: ms-resource://Microsoft.MicrosoftOfficeHub/officehubintl/AppManifest_GetOffice_Description
|
||||
// Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription
|
||||
var e = Marshal.GetExceptionForHR((int)hResult);
|
||||
ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Load pri failed {source} with HResult {hResult} and location {Package.Location}", e);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return resourceReference;
|
||||
}
|
||||
}
|
||||
|
||||
internal string LogoUriFromManifest(IAppxManifestApplication app)
|
||||
{
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
[Serializable]
|
||||
public class UWPApplication : IProgram
|
||||
{
|
||||
public string AppListEntry { get; set; }
|
||||
|
||||
public string UniqueIdentifier { get; set; }
|
||||
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string UserModelId { get; set; }
|
||||
|
||||
public string BackgroundColor { get; set; }
|
||||
|
||||
public string EntryPoint { get; set; }
|
||||
|
||||
public string Name => DisplayName;
|
||||
|
||||
public string Location => Package.Location;
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public bool CanRunElevated { get; set; }
|
||||
|
||||
public string LogoPath { get; set; }
|
||||
|
||||
public UWP Package { get; set; }
|
||||
|
||||
private string logoUri;
|
||||
|
||||
// Function to calculate the score of a result
|
||||
private int Score(string query)
|
||||
{
|
||||
var displayNameMatch = StringMatcher.FuzzySearch(query, DisplayName);
|
||||
var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
|
||||
var score = new[] { displayNameMatch.Score, descriptionMatch.Score / 2 }.Max();
|
||||
return score;
|
||||
}
|
||||
|
||||
// Function to set the subtitle based on the Type of application
|
||||
private static string SetSubtitle(IPublicAPI api)
|
||||
{
|
||||
return api.GetTranslation("powertoys_run_plugin_program_packaged_application");
|
||||
}
|
||||
|
||||
public Result Result(string query, IPublicAPI api)
|
||||
{
|
||||
if (api == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(api));
|
||||
}
|
||||
|
||||
var score = Score(query);
|
||||
if (score <= 0)
|
||||
{ // no need to create result if score is 0
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new Result
|
||||
{
|
||||
SubTitle = SetSubtitle(api),
|
||||
Icon = Logo,
|
||||
Score = score,
|
||||
ContextData = this,
|
||||
Action = e =>
|
||||
{
|
||||
Launch(api);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
// To set the title to always be the displayname of the packaged application
|
||||
result.Title = DisplayName;
|
||||
result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData;
|
||||
|
||||
var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_name"), result.Title);
|
||||
var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_path"), Package.Location);
|
||||
result.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive.")]
|
||||
public List<ContextMenuResult> ContextMenus(IPublicAPI api)
|
||||
{
|
||||
if (api == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(api));
|
||||
}
|
||||
|
||||
var contextMenus = new List<ContextMenuResult>();
|
||||
|
||||
if (CanRunElevated)
|
||||
{
|
||||
contextMenus.Add(
|
||||
new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = api.GetTranslation("wox_plugin_program_run_as_administrator"),
|
||||
Glyph = "\xE7EF",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.Enter,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = _ =>
|
||||
{
|
||||
string command = "shell:AppsFolder\\" + UniqueIdentifier;
|
||||
command = Environment.ExpandEnvironmentVariables(command.Trim());
|
||||
|
||||
var info = ShellCommand.SetProcessStartInfo(command, verb: "runas");
|
||||
info.UseShellExecute = true;
|
||||
|
||||
Process.Start(info);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
contextMenus.Add(
|
||||
new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = api.GetTranslation("wox_plugin_program_open_containing_folder"),
|
||||
Glyph = "\xE838",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.E,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = _ =>
|
||||
{
|
||||
Main.StartProcess(Process.Start, new ProcessStartInfo("explorer", Package.Location));
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
contextMenus.Add(new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = api.GetTranslation("wox_plugin_program_open_in_console"),
|
||||
Glyph = "\xE756",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.C,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = (context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Helper.OpenInConsole(Package.Location);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Program.UWP.ContextMenu| Failed to open {Name} in console, {e.Message}", e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return contextMenus;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive, and showing the user an error message")]
|
||||
private async void Launch(IPublicAPI api)
|
||||
{
|
||||
var appManager = new ApplicationActivationHelper.ApplicationActivationManager();
|
||||
const string noArgs = "";
|
||||
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
appManager.ActivateApplication(UserModelId, noArgs, noFlags, out uint unusedPid);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
var name = "Plugin: Program";
|
||||
var message = $"Can't start UWP: {DisplayName}";
|
||||
api.ShowMsg(name, message, string.Empty);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public UWPApplication(IAppxManifestApplication manifestApp, UWP package)
|
||||
{
|
||||
if (manifestApp == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(manifestApp));
|
||||
}
|
||||
|
||||
var hr = manifestApp.GetAppUserModelId(out var tmpUserModelId);
|
||||
UserModelId = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUserModelId);
|
||||
|
||||
hr = manifestApp.GetAppUserModelId(out var tmpUniqueIdentifier);
|
||||
UniqueIdentifier = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUniqueIdentifier);
|
||||
|
||||
hr = manifestApp.GetStringValue("DisplayName", out var tmpDisplayName);
|
||||
DisplayName = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDisplayName);
|
||||
|
||||
hr = manifestApp.GetStringValue("Description", out var tmpDescription);
|
||||
Description = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDescription);
|
||||
|
||||
hr = manifestApp.GetStringValue("BackgroundColor", out var tmpBackgroundColor);
|
||||
BackgroundColor = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpBackgroundColor);
|
||||
|
||||
hr = manifestApp.GetStringValue("EntryPoint", out var tmpEntryPoint);
|
||||
EntryPoint = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpEntryPoint);
|
||||
|
||||
Package = package ?? throw new ArgumentNullException(nameof(package));
|
||||
|
||||
DisplayName = ResourceFromPri(package.FullName, DisplayName);
|
||||
Description = ResourceFromPri(package.FullName, Description);
|
||||
logoUri = LogoUriFromManifest(manifestApp);
|
||||
|
||||
Enabled = true;
|
||||
CanRunElevated = IfApplicationcanRunElevated();
|
||||
}
|
||||
|
||||
private bool IfApplicationcanRunElevated()
|
||||
{
|
||||
if (EntryPoint == "Windows.FullTrustApplication")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var manifest = Package.Location + "\\AppxManifest.xml";
|
||||
if (File.Exists(manifest))
|
||||
{
|
||||
var file = File.ReadAllText(manifest);
|
||||
if (file.Contains("TrustLevel=\"mediumIL\"", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal string ResourceFromPri(string packageFullName, string resourceReference)
|
||||
{
|
||||
const string prefix = "ms-resource:";
|
||||
if (!string.IsNullOrWhiteSpace(resourceReference) && resourceReference.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// magic comes from @talynone
|
||||
// https://github.com/talynone/Wox.Plugin.WindowsUniversalAppLauncher/blob/master/StoreAppLauncher/Helpers/NativeApiHelper.cs#L139-L153
|
||||
string key = resourceReference.Substring(prefix.Length);
|
||||
string parsed;
|
||||
if (key.StartsWith("//", StringComparison.Ordinal))
|
||||
{
|
||||
parsed = prefix + key;
|
||||
}
|
||||
else if (key.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
parsed = prefix + "//" + key;
|
||||
}
|
||||
else if (key.Contains("resources", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
parsed = prefix + key;
|
||||
}
|
||||
else
|
||||
{
|
||||
parsed = prefix + "///resources/" + key;
|
||||
}
|
||||
|
||||
var outBuffer = new StringBuilder(128);
|
||||
string source = $"@{{{packageFullName}? {parsed}}}";
|
||||
var capacity = (uint)outBuffer.Capacity;
|
||||
var hResult = NativeMethods.SHLoadIndirectString(source, outBuffer, capacity, IntPtr.Zero);
|
||||
if (hResult == Hresult.Ok)
|
||||
{
|
||||
var loaded = outBuffer.ToString();
|
||||
if (!string.IsNullOrEmpty(loaded))
|
||||
{
|
||||
return loaded;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|ResourceFromPri|{Package.Location}|Can't load null or empty result "
|
||||
+ $"pri {source} in uwp location {Package.Location}", new NullReferenceException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// https://github.com/Wox-launcher/Wox/issues/964
|
||||
// known hresult 2147942522:
|
||||
// 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'.
|
||||
// for
|
||||
// Microsoft.MicrosoftOfficeHub_17.7608.23501.0_x64__8wekyb3d8bbwe: ms-resource://Microsoft.MicrosoftOfficeHub/officehubintl/AppManifest_GetOffice_Description
|
||||
// Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription
|
||||
var e = Marshal.GetExceptionForHR((int)hResult);
|
||||
ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Load pri failed {source} with HResult {hResult} and location {Package.Location}", e);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return resourceReference;
|
||||
}
|
||||
}
|
||||
|
||||
internal string LogoUriFromManifest(IAppxManifestApplication app)
|
||||
{
|
||||
var logoKeyFromVersion = new Dictionary<PackageVersion, string>
|
||||
{
|
||||
{ PackageVersion.Windows10, "Square44x44Logo" },
|
||||
{ PackageVersion.Windows81, "Square30x30Logo" },
|
||||
{ PackageVersion.Windows8, "SmallLogo" },
|
||||
};
|
||||
if (logoKeyFromVersion.ContainsKey(Package.Version))
|
||||
{
|
||||
var key = logoKeyFromVersion[Package.Version];
|
||||
var hr = app.GetStringValue(key, out var logoUri);
|
||||
_ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUri);
|
||||
return logoUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
LogoPath = LogoPathFromUri(logoUri, "contrast-white");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogoPath = LogoPathFromUri(logoUri, "contrast-black");
|
||||
}
|
||||
}
|
||||
|
||||
internal string LogoPathFromUri(string uri, string theme)
|
||||
{
|
||||
// all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
|
||||
// windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
|
||||
// windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
|
||||
// windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
|
||||
string path;
|
||||
if (uri.Contains("\\", StringComparison.Ordinal))
|
||||
{
|
||||
path = Path.Combine(Package.Location, uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// for C:\Windows\MiracastView etc
|
||||
path = Path.Combine(Package.Location, "Assets", uri);
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(path);
|
||||
if (extension != null)
|
||||
{
|
||||
var end = path.Length - extension.Length;
|
||||
var prefix = path.Substring(0, end);
|
||||
var paths = new List<string> { path };
|
||||
|
||||
};
|
||||
if (logoKeyFromVersion.ContainsKey(Package.Version))
|
||||
{
|
||||
var key = logoKeyFromVersion[Package.Version];
|
||||
var hr = app.GetStringValue(key, out var logoUri);
|
||||
_ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUri);
|
||||
return logoUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
LogoPath = LogoPathFromUri(logoUri, "contrast-white");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogoPath = LogoPathFromUri(logoUri, "contrast-black");
|
||||
}
|
||||
}
|
||||
|
||||
internal string LogoPathFromUri(string uri, string theme)
|
||||
{
|
||||
// all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
|
||||
// windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
|
||||
// windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
|
||||
// windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
|
||||
string path;
|
||||
if (uri.Contains("\\", StringComparison.Ordinal))
|
||||
{
|
||||
path = Path.Combine(Package.Location, uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// for C:\Windows\MiracastView etc
|
||||
path = Path.Combine(Package.Location, "Assets", uri);
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(path);
|
||||
if (extension != null)
|
||||
{
|
||||
var end = path.Length - extension.Length;
|
||||
var prefix = path.Substring(0, end);
|
||||
var paths = new List<string> { path };
|
||||
|
||||
var scaleFactors = new Dictionary<PackageVersion, List<int>>
|
||||
{
|
||||
// scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables,
|
||||
{ PackageVersion.Windows10, new List<int> { 100, 125, 150, 200, 400 } },
|
||||
{ PackageVersion.Windows81, new List<int> { 100, 120, 140, 160, 180 } },
|
||||
{ PackageVersion.Windows8, new List<int> { 100 } },
|
||||
};
|
||||
|
||||
if (scaleFactors.ContainsKey(Package.Version))
|
||||
{
|
||||
foreach (var factor in scaleFactors[Package.Version])
|
||||
{
|
||||
paths.Add($"{prefix}.scale-{factor}{extension}");
|
||||
paths.Add($"{prefix}.scale-{factor}_{theme}{extension}");
|
||||
paths.Add($"{prefix}.{theme}_scale-{factor}{extension}");
|
||||
}
|
||||
}
|
||||
|
||||
paths = paths.OrderByDescending(x => x.Contains(theme, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
var selected = paths.FirstOrDefault(File.Exists);
|
||||
if (!string.IsNullOrEmpty(selected))
|
||||
{
|
||||
return selected;
|
||||
}
|
||||
else
|
||||
{
|
||||
int appIconSize = 36;
|
||||
var targetSizes = new List<int> { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }.AsParallel();
|
||||
Dictionary<string, int> pathFactorPairs = new Dictionary<string, int>();
|
||||
|
||||
foreach (var factor in targetSizes)
|
||||
{
|
||||
string simplePath = $"{prefix}.targetsize-{factor}{extension}";
|
||||
string suffixThemePath = $"{prefix}.targetsize-{factor}_{theme}{extension}";
|
||||
string prefixThemePath = $"{prefix}.{theme}_targetsize-{factor}{extension}";
|
||||
|
||||
paths.Add(simplePath);
|
||||
paths.Add(suffixThemePath);
|
||||
paths.Add(prefixThemePath);
|
||||
|
||||
pathFactorPairs.Add(simplePath, factor);
|
||||
pathFactorPairs.Add(suffixThemePath, factor);
|
||||
pathFactorPairs.Add(prefixThemePath, factor);
|
||||
}
|
||||
|
||||
paths = paths.OrderByDescending(x => x.Contains(theme, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
var selectedIconPath = paths.OrderBy(x => Math.Abs(pathFactorPairs.GetValueOrDefault(x) - appIconSize)).FirstOrDefault(File.Exists);
|
||||
if (!string.IsNullOrEmpty(selectedIconPath))
|
||||
{
|
||||
return selectedIconPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
$"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
$"|Unable to find extension from {uri} for {UserModelId} " +
|
||||
$"in package location {Package.Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public ImageSource Logo()
|
||||
{
|
||||
var logo = ImageFromPath(LogoPath);
|
||||
return logo;
|
||||
}
|
||||
|
||||
private BitmapImage ImageFromPath(string path)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
MemoryStream memoryStream = new MemoryStream();
|
||||
|
||||
byte[] fileBytes = File.ReadAllBytes(path);
|
||||
memoryStream.Write(fileBytes, 0, fileBytes.Length);
|
||||
memoryStream.Position = 0;
|
||||
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.StreamSource = memoryStream;
|
||||
image.EndInit();
|
||||
return image;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|ImageFromPath|{path}" +
|
||||
$"|Unable to get logo for {UserModelId} from {path} and" +
|
||||
$" located in {Package.Location}", new FileNotFoundException());
|
||||
return new BitmapImage(new Uri(ImageLoader.ErrorIconPath));
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{DisplayName}: {Description}";
|
||||
}
|
||||
};
|
||||
|
||||
if (scaleFactors.ContainsKey(Package.Version))
|
||||
{
|
||||
foreach (var factor in scaleFactors[Package.Version])
|
||||
{
|
||||
paths.Add($"{prefix}.scale-{factor}{extension}");
|
||||
paths.Add($"{prefix}.scale-{factor}_{theme}{extension}");
|
||||
paths.Add($"{prefix}.{theme}_scale-{factor}{extension}");
|
||||
}
|
||||
}
|
||||
|
||||
paths = paths.OrderByDescending(x => x.Contains(theme, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
var selected = paths.FirstOrDefault(File.Exists);
|
||||
if (!string.IsNullOrEmpty(selected))
|
||||
{
|
||||
return selected;
|
||||
}
|
||||
else
|
||||
{
|
||||
int appIconSize = 36;
|
||||
var targetSizes = new List<int> { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }.AsParallel();
|
||||
Dictionary<string, int> pathFactorPairs = new Dictionary<string, int>();
|
||||
|
||||
foreach (var factor in targetSizes)
|
||||
{
|
||||
string simplePath = $"{prefix}.targetsize-{factor}{extension}";
|
||||
string suffixThemePath = $"{prefix}.targetsize-{factor}_{theme}{extension}";
|
||||
string prefixThemePath = $"{prefix}.{theme}_targetsize-{factor}{extension}";
|
||||
|
||||
paths.Add(simplePath);
|
||||
paths.Add(suffixThemePath);
|
||||
paths.Add(prefixThemePath);
|
||||
|
||||
pathFactorPairs.Add(simplePath, factor);
|
||||
pathFactorPairs.Add(suffixThemePath, factor);
|
||||
pathFactorPairs.Add(prefixThemePath, factor);
|
||||
}
|
||||
|
||||
paths = paths.OrderByDescending(x => x.Contains(theme, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
var selectedIconPath = paths.OrderBy(x => Math.Abs(pathFactorPairs.GetValueOrDefault(x) - appIconSize)).FirstOrDefault(File.Exists);
|
||||
if (!string.IsNullOrEmpty(selectedIconPath))
|
||||
{
|
||||
return selectedIconPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
$"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
$"|Unable to find extension from {uri} for {UserModelId} " +
|
||||
$"in package location {Package.Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public ImageSource Logo()
|
||||
{
|
||||
var logo = ImageFromPath(LogoPath);
|
||||
return logo;
|
||||
}
|
||||
|
||||
private BitmapImage ImageFromPath(string path)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
MemoryStream memoryStream = new MemoryStream();
|
||||
|
||||
byte[] fileBytes = File.ReadAllBytes(path);
|
||||
memoryStream.Write(fileBytes, 0, fileBytes.Length);
|
||||
memoryStream.Position = 0;
|
||||
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.StreamSource = memoryStream;
|
||||
image.EndInit();
|
||||
return image;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException(
|
||||
$"|UWP|ImageFromPath|{path}" +
|
||||
$"|Unable to get logo for {UserModelId} from {path} and" +
|
||||
$" located in {Package.Location}", new FileNotFoundException());
|
||||
return new BitmapImage(new Uri(ImageLoader.ErrorIconPath));
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{DisplayName}: {Description}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
// 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.Plugin.Program.Storage
|
||||
{
|
||||
internal interface IProgramRepository
|
||||
{
|
||||
void IndexPrograms();
|
||||
|
||||
void Load();
|
||||
|
||||
void Save();
|
||||
}
|
||||
}
|
||||
// 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.Plugin.Program.Storage
|
||||
{
|
||||
internal interface IProgramRepository
|
||||
{
|
||||
void IndexPrograms();
|
||||
|
||||
void Load();
|
||||
|
||||
void Save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +1,96 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Plugin.Program.Logger;
|
||||
using Microsoft.Plugin.Program.Programs;
|
||||
using Windows.ApplicationModel;
|
||||
using Wox.Infrastructure.Storage;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// A repository for storing packaged applications such as UWP apps or appx packaged desktop apps.
|
||||
/// This repository will also monitor for changes to the PackageCatelog and update the repository accordingly
|
||||
/// </summary>
|
||||
internal class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
|
||||
{
|
||||
private IStorage<IList<UWPApplication>> _storage;
|
||||
|
||||
private IPackageCatalog _packageCatalog;
|
||||
|
||||
public PackageRepository(IPackageCatalog packageCatalog, IStorage<IList<UWPApplication>> storage)
|
||||
{
|
||||
_storage = storage ?? throw new ArgumentNullException(nameof(storage), "StorageRepository requires an initialized storage interface");
|
||||
_packageCatalog = packageCatalog ?? throw new ArgumentNullException(nameof(packageCatalog), "PackageRepository expects an interface to be able to subscribe to package events");
|
||||
_packageCatalog.PackageInstalling += OnPackageInstalling;
|
||||
_packageCatalog.PackageUninstalling += OnPackageUninstalling;
|
||||
}
|
||||
|
||||
public void OnPackageInstalling(PackageCatalog p, PackageInstallingEventArgs args)
|
||||
{
|
||||
if (args.IsComplete)
|
||||
{
|
||||
try
|
||||
{
|
||||
var packageWrapper = PackageWrapper.GetWrapperFromPackage(args.Package);
|
||||
if (!string.IsNullOrEmpty(packageWrapper.InstalledLocation))
|
||||
{
|
||||
var uwp = new UWP(packageWrapper);
|
||||
uwp.InitializeAppInfo(packageWrapper.InstalledLocation);
|
||||
foreach (var app in uwp.Apps)
|
||||
{
|
||||
Add(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeAppInfo will throw if there is no AppxManifest.xml for the package.
|
||||
// Note there are sometimes multiple packages per product and this doesn't necessarily mean that we haven't found the app.
|
||||
// eg. "Could not find file 'C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminalPreview_2020.616.45.0_neutral_~_8wekyb3d8bbwe\\AppxManifest.xml'."
|
||||
catch (System.IO.FileNotFoundException e)
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|OnPackageInstalling|{args.Package.InstalledLocation}|{e.Message}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPackageUninstalling(PackageCatalog p, PackageUninstallingEventArgs args)
|
||||
{
|
||||
if (args.Progress == 0)
|
||||
{
|
||||
// find apps associated with this package.
|
||||
var packageWrapper = PackageWrapper.GetWrapperFromPackage(args.Package);
|
||||
var uwp = new UWP(packageWrapper);
|
||||
var apps = Items.Where(a => a.Package.Equals(uwp)).ToArray();
|
||||
foreach (var app in apps)
|
||||
{
|
||||
Remove(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void IndexPrograms()
|
||||
{
|
||||
var windows10 = new Version(10, 0);
|
||||
var support = Environment.OSVersion.Version.Major >= windows10.Major;
|
||||
|
||||
var applications = support ? Programs.UWP.All() : Array.Empty<UWPApplication>();
|
||||
Set(applications);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_storage.Save(Items);
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
var items = _storage.TryLoad(Array.Empty<UWPApplication>());
|
||||
Set(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Plugin.Program.Logger;
|
||||
using Microsoft.Plugin.Program.Programs;
|
||||
using Windows.ApplicationModel;
|
||||
using Wox.Infrastructure.Storage;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Storage
|
||||
{
|
||||
/// <summary>
|
||||
/// A repository for storing packaged applications such as UWP apps or appx packaged desktop apps.
|
||||
/// This repository will also monitor for changes to the PackageCatelog and update the repository accordingly
|
||||
/// </summary>
|
||||
internal class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
|
||||
{
|
||||
private IStorage<IList<UWPApplication>> _storage;
|
||||
|
||||
private IPackageCatalog _packageCatalog;
|
||||
|
||||
public PackageRepository(IPackageCatalog packageCatalog, IStorage<IList<UWPApplication>> storage)
|
||||
{
|
||||
_storage = storage ?? throw new ArgumentNullException(nameof(storage), "StorageRepository requires an initialized storage interface");
|
||||
_packageCatalog = packageCatalog ?? throw new ArgumentNullException(nameof(packageCatalog), "PackageRepository expects an interface to be able to subscribe to package events");
|
||||
_packageCatalog.PackageInstalling += OnPackageInstalling;
|
||||
_packageCatalog.PackageUninstalling += OnPackageUninstalling;
|
||||
}
|
||||
|
||||
public void OnPackageInstalling(PackageCatalog p, PackageInstallingEventArgs args)
|
||||
{
|
||||
if (args.IsComplete)
|
||||
{
|
||||
try
|
||||
{
|
||||
var packageWrapper = PackageWrapper.GetWrapperFromPackage(args.Package);
|
||||
if (!string.IsNullOrEmpty(packageWrapper.InstalledLocation))
|
||||
{
|
||||
var uwp = new UWP(packageWrapper);
|
||||
uwp.InitializeAppInfo(packageWrapper.InstalledLocation);
|
||||
foreach (var app in uwp.Apps)
|
||||
{
|
||||
Add(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeAppInfo will throw if there is no AppxManifest.xml for the package.
|
||||
// Note there are sometimes multiple packages per product and this doesn't necessarily mean that we haven't found the app.
|
||||
// eg. "Could not find file 'C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminalPreview_2020.616.45.0_neutral_~_8wekyb3d8bbwe\\AppxManifest.xml'."
|
||||
catch (System.IO.FileNotFoundException e)
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|OnPackageInstalling|{args.Package.InstalledLocation}|{e.Message}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPackageUninstalling(PackageCatalog p, PackageUninstallingEventArgs args)
|
||||
{
|
||||
if (args.Progress == 0)
|
||||
{
|
||||
// find apps associated with this package.
|
||||
var packageWrapper = PackageWrapper.GetWrapperFromPackage(args.Package);
|
||||
var uwp = new UWP(packageWrapper);
|
||||
var apps = Items.Where(a => a.Package.Equals(uwp)).ToArray();
|
||||
foreach (var app in apps)
|
||||
{
|
||||
Remove(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void IndexPrograms()
|
||||
{
|
||||
var windows10 = new Version(10, 0);
|
||||
var support = Environment.OSVersion.Version.Major >= windows10.Major;
|
||||
|
||||
var applications = support ? Programs.UWP.All() : Array.Empty<UWPApplication>();
|
||||
Set(applications);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_storage.Save(Items);
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
var items = _storage.TryLoad(Array.Empty<UWPApplication>());
|
||||
Set(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Win32Program = Microsoft.Plugin.Program.Programs.Win32Program;
|
||||
|
||||
|
||||
namespace Microsoft.Plugin.Program.Storage
|
||||
{
|
||||
internal class Win32ProgramRepository : ListRepository<Programs.Win32Program>, IProgramRepository
|
||||
@@ -60,9 +60,9 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
// Enable it to search in sub folders as well
|
||||
_fileSystemWatcherHelpers[index].IncludeSubdirectories = true;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive>")]
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive>")]
|
||||
private void OnAppRenamed(object sender, RenamedEventArgs e)
|
||||
{
|
||||
string oldPath = e.OldFullPath;
|
||||
@@ -95,7 +95,7 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
{
|
||||
Log.Info($"|Win32ProgramRepository|OnAppRenamed-{extension}Program|{oldPath}|Unable to create program from {oldPath}| {ex.Message}");
|
||||
}
|
||||
|
||||
|
||||
// To remove the old app which has been renamed and to add the new application.
|
||||
if (oldApp != null)
|
||||
{
|
||||
@@ -106,9 +106,9 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
{
|
||||
Add(newApp);
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentionally keeping the process alive")]
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentionally keeping the process alive")]
|
||||
private void OnAppDeleted(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
string path = e.FullPath;
|
||||
@@ -152,7 +152,7 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// 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.
|
||||
|
||||
// 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.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -14,7 +14,7 @@ using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Plugin.Program.Views.Commands;
|
||||
using Wox.Plugin;
|
||||
|
||||
|
||||
namespace Microsoft.Plugin.Program.Views
|
||||
{
|
||||
/// <summary>
|
||||
@@ -217,11 +217,11 @@ namespace Microsoft.Plugin.Program.Views
|
||||
ProgramSettingDisplayList.RemoveDisabledFromSettings();
|
||||
}
|
||||
|
||||
if (selectedItems.IsReindexRequired())
|
||||
{
|
||||
ReIndexing();
|
||||
}
|
||||
|
||||
if (selectedItems.IsReindexRequired())
|
||||
{
|
||||
ReIndexing();
|
||||
}
|
||||
|
||||
programSourceView.SelectedItems.Clear();
|
||||
|
||||
programSourceView.Items.Refresh();
|
||||
@@ -309,4 +309,4 @@ namespace Microsoft.Plugin.Program.Views
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
using static Microsoft.Plugin.Program.Programs.UWP;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Win32
|
||||
{
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
internal static extern Hresult SHCreateStreamOnFileEx(string fileName, Stgm grfMode, uint attributes, bool create, IStream reserved, out IStream stream);
|
||||
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
internal static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
using static Microsoft.Plugin.Program.Programs.UWP;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Win32
|
||||
{
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
internal static extern Hresult SHCreateStreamOnFileEx(string fileName, Stgm grfMode, uint attributes, bool create, IStream reserved, out IStream stream);
|
||||
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
internal static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,321 +1,321 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Plugin;
|
||||
using Wox.Plugin.SharedCommands;
|
||||
using Control = System.Windows.Controls.Control;
|
||||
|
||||
namespace Microsoft.Plugin.Shell
|
||||
{
|
||||
public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, ISavable
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private readonly PluginJsonStorage<Settings> _storage;
|
||||
|
||||
private string IconPath { get; set; }
|
||||
|
||||
private PluginInitContext _context;
|
||||
|
||||
public Main()
|
||||
{
|
||||
_storage = new PluginJsonStorage<Settings>();
|
||||
_settings = _storage.Load();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_storage.Save();
|
||||
}
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
List<Result> results = new List<Result>();
|
||||
string cmd = query.Search;
|
||||
if (string.IsNullOrEmpty(cmd))
|
||||
{
|
||||
return ResultsFromlHistory();
|
||||
}
|
||||
else
|
||||
{
|
||||
var queryCmd = GetCurrentCmd(cmd);
|
||||
results.Add(queryCmd);
|
||||
var history = GetHistoryCmds(cmd, queryCmd);
|
||||
results.AddRange(history);
|
||||
|
||||
try
|
||||
{
|
||||
List<Result> folderPluginResults = Folder.Main.GetFolderPluginResults(query);
|
||||
results.AddRange(folderPluginResults);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Shell.Main.Query|Exception when query for <{query}>", e);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Result> GetHistoryCmds(string cmd, Result result)
|
||||
{
|
||||
IEnumerable<Result> history = _settings.Count.Where(o => o.Key.Contains(cmd))
|
||||
.OrderByDescending(o => o.Value)
|
||||
.Select(m =>
|
||||
{
|
||||
if (m.Key == cmd)
|
||||
{
|
||||
result.SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value);
|
||||
return null;
|
||||
}
|
||||
|
||||
var ret = new Result
|
||||
{
|
||||
Title = m.Key,
|
||||
SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value),
|
||||
IcoPath = IconPath,
|
||||
Action = c =>
|
||||
{
|
||||
Execute(Process.Start, PrepareProcessStartInfo(m.Key));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return ret;
|
||||
}).Where(o => o != null).Take(4);
|
||||
return history.ToList();
|
||||
}
|
||||
|
||||
private Result GetCurrentCmd(string cmd)
|
||||
{
|
||||
Result result = new Result
|
||||
{
|
||||
Title = cmd,
|
||||
Score = 5000,
|
||||
SubTitle = "Shell: " + _context.API.GetTranslation("wox_plugin_cmd_execute_through_shell"),
|
||||
IcoPath = IconPath,
|
||||
Action = c =>
|
||||
{
|
||||
Execute(Process.Start, PrepareProcessStartInfo(cmd));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Result> ResultsFromlHistory()
|
||||
{
|
||||
IEnumerable<Result> history = _settings.Count.OrderByDescending(o => o.Value)
|
||||
.Select(m => new Result
|
||||
{
|
||||
Title = m.Key,
|
||||
SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value),
|
||||
IcoPath = IconPath,
|
||||
Action = c =>
|
||||
{
|
||||
Execute(Process.Start, PrepareProcessStartInfo(m.Key));
|
||||
return true;
|
||||
},
|
||||
}).Take(5);
|
||||
return history.ToList();
|
||||
}
|
||||
|
||||
private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false)
|
||||
{
|
||||
command = command.Trim();
|
||||
command = Environment.ExpandEnvironmentVariables(command);
|
||||
var workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var runAsAdministratorArg = !runAsAdministrator && !_settings.RunAsAdministrator ? string.Empty : "runas";
|
||||
|
||||
ProcessStartInfo info;
|
||||
if (_settings.Shell == Shell.Cmd)
|
||||
{
|
||||
var arguments = _settings.LeaveShellOpen ? $"/k \"{command}\"" : $"/c \"{command}\" & pause";
|
||||
|
||||
info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, arguments, runAsAdministratorArg);
|
||||
}
|
||||
else if (_settings.Shell == Shell.Powershell)
|
||||
{
|
||||
string arguments;
|
||||
if (_settings.LeaveShellOpen)
|
||||
{
|
||||
arguments = $"-NoExit \"{command}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
arguments = $"\"{command} ; Read-Host -Prompt \\\"Press Enter to continue\\\"\"";
|
||||
}
|
||||
|
||||
info = ShellCommand.SetProcessStartInfo("powershell.exe", workingDirectory, arguments, runAsAdministratorArg);
|
||||
}
|
||||
else if (_settings.Shell == Shell.RunCommand)
|
||||
{
|
||||
// Open explorer if the path is a file or directory
|
||||
if (Directory.Exists(command) || File.Exists(command))
|
||||
{
|
||||
info = ShellCommand.SetProcessStartInfo("explorer.exe", arguments: command, verb: runAsAdministratorArg);
|
||||
}
|
||||
else
|
||||
{
|
||||
var parts = command.Split(new[] { ' ' }, 2);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var filename = parts[0];
|
||||
if (ExistInPath(filename))
|
||||
{
|
||||
var arguments = parts[1];
|
||||
info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsAdministratorArg);
|
||||
}
|
||||
else
|
||||
{
|
||||
info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
info.UseShellExecute = true;
|
||||
|
||||
_settings.AddCmdHistory(command);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private void Execute(Func<ProcessStartInfo, Process> startProcess, ProcessStartInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
startProcess(info);
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
var name = "Plugin: Shell";
|
||||
var message = $"Command not found: {e.Message}";
|
||||
_context.API.ShowMsg(name, message);
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
var name = "Plugin: Shell";
|
||||
var message = $"Error running the command: {e.Message}";
|
||||
_context.API.ShowMsg(name, message);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ExistInPath(string filename)
|
||||
{
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var values = Environment.GetEnvironmentVariable("PATH");
|
||||
if (values != null)
|
||||
{
|
||||
foreach (var path in values.Split(';'))
|
||||
{
|
||||
var path1 = Path.Combine(path, filename);
|
||||
var path2 = Path.Combine(path, filename + ".exe");
|
||||
if (File.Exists(path1) || File.Exists(path2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
this._context = context;
|
||||
_context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateIconPath(_context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
// Todo : Update with theme based IconPath
|
||||
private void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
IconPath = "Images/shell.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
IconPath = "Images/shell.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
{
|
||||
UpdateIconPath(newTheme);
|
||||
}
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
return new CMDSetting(_settings);
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_cmd_plugin_name");
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_cmd_plugin_description");
|
||||
}
|
||||
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
var resultlist = new List<ContextMenuResult>
|
||||
{
|
||||
new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_administrator"),
|
||||
Glyph = "\xE7EF",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.Enter,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = c =>
|
||||
{
|
||||
Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true));
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return resultlist;
|
||||
}
|
||||
|
||||
public void UpdateSettings(PowerLauncherSettings settings)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.PowerToys.Settings.UI.Lib;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Plugin;
|
||||
using Wox.Plugin.SharedCommands;
|
||||
using Control = System.Windows.Controls.Control;
|
||||
|
||||
namespace Microsoft.Plugin.Shell
|
||||
{
|
||||
public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, ISavable
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private readonly PluginJsonStorage<Settings> _storage;
|
||||
|
||||
private string IconPath { get; set; }
|
||||
|
||||
private PluginInitContext _context;
|
||||
|
||||
public Main()
|
||||
{
|
||||
_storage = new PluginJsonStorage<Settings>();
|
||||
_settings = _storage.Load();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_storage.Save();
|
||||
}
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
List<Result> results = new List<Result>();
|
||||
string cmd = query.Search;
|
||||
if (string.IsNullOrEmpty(cmd))
|
||||
{
|
||||
return ResultsFromlHistory();
|
||||
}
|
||||
else
|
||||
{
|
||||
var queryCmd = GetCurrentCmd(cmd);
|
||||
results.Add(queryCmd);
|
||||
var history = GetHistoryCmds(cmd, queryCmd);
|
||||
results.AddRange(history);
|
||||
|
||||
try
|
||||
{
|
||||
List<Result> folderPluginResults = Folder.Main.GetFolderPluginResults(query);
|
||||
results.AddRange(folderPluginResults);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Microsoft.Plugin.Shell.Main.Query|Exception when query for <{query}>", e);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Result> GetHistoryCmds(string cmd, Result result)
|
||||
{
|
||||
IEnumerable<Result> history = _settings.Count.Where(o => o.Key.Contains(cmd))
|
||||
.OrderByDescending(o => o.Value)
|
||||
.Select(m =>
|
||||
{
|
||||
if (m.Key == cmd)
|
||||
{
|
||||
result.SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value);
|
||||
return null;
|
||||
}
|
||||
|
||||
var ret = new Result
|
||||
{
|
||||
Title = m.Key,
|
||||
SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value),
|
||||
IcoPath = IconPath,
|
||||
Action = c =>
|
||||
{
|
||||
Execute(Process.Start, PrepareProcessStartInfo(m.Key));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return ret;
|
||||
}).Where(o => o != null).Take(4);
|
||||
return history.ToList();
|
||||
}
|
||||
|
||||
private Result GetCurrentCmd(string cmd)
|
||||
{
|
||||
Result result = new Result
|
||||
{
|
||||
Title = cmd,
|
||||
Score = 5000,
|
||||
SubTitle = "Shell: " + _context.API.GetTranslation("wox_plugin_cmd_execute_through_shell"),
|
||||
IcoPath = IconPath,
|
||||
Action = c =>
|
||||
{
|
||||
Execute(Process.Start, PrepareProcessStartInfo(cmd));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Result> ResultsFromlHistory()
|
||||
{
|
||||
IEnumerable<Result> history = _settings.Count.OrderByDescending(o => o.Value)
|
||||
.Select(m => new Result
|
||||
{
|
||||
Title = m.Key,
|
||||
SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value),
|
||||
IcoPath = IconPath,
|
||||
Action = c =>
|
||||
{
|
||||
Execute(Process.Start, PrepareProcessStartInfo(m.Key));
|
||||
return true;
|
||||
},
|
||||
}).Take(5);
|
||||
return history.ToList();
|
||||
}
|
||||
|
||||
private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false)
|
||||
{
|
||||
command = command.Trim();
|
||||
command = Environment.ExpandEnvironmentVariables(command);
|
||||
var workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var runAsAdministratorArg = !runAsAdministrator && !_settings.RunAsAdministrator ? string.Empty : "runas";
|
||||
|
||||
ProcessStartInfo info;
|
||||
if (_settings.Shell == Shell.Cmd)
|
||||
{
|
||||
var arguments = _settings.LeaveShellOpen ? $"/k \"{command}\"" : $"/c \"{command}\" & pause";
|
||||
|
||||
info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, arguments, runAsAdministratorArg);
|
||||
}
|
||||
else if (_settings.Shell == Shell.Powershell)
|
||||
{
|
||||
string arguments;
|
||||
if (_settings.LeaveShellOpen)
|
||||
{
|
||||
arguments = $"-NoExit \"{command}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
arguments = $"\"{command} ; Read-Host -Prompt \\\"Press Enter to continue\\\"\"";
|
||||
}
|
||||
|
||||
info = ShellCommand.SetProcessStartInfo("powershell.exe", workingDirectory, arguments, runAsAdministratorArg);
|
||||
}
|
||||
else if (_settings.Shell == Shell.RunCommand)
|
||||
{
|
||||
// Open explorer if the path is a file or directory
|
||||
if (Directory.Exists(command) || File.Exists(command))
|
||||
{
|
||||
info = ShellCommand.SetProcessStartInfo("explorer.exe", arguments: command, verb: runAsAdministratorArg);
|
||||
}
|
||||
else
|
||||
{
|
||||
var parts = command.Split(new[] { ' ' }, 2);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var filename = parts[0];
|
||||
if (ExistInPath(filename))
|
||||
{
|
||||
var arguments = parts[1];
|
||||
info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsAdministratorArg);
|
||||
}
|
||||
else
|
||||
{
|
||||
info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
info.UseShellExecute = true;
|
||||
|
||||
_settings.AddCmdHistory(command);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private void Execute(Func<ProcessStartInfo, Process> startProcess, ProcessStartInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
startProcess(info);
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
var name = "Plugin: Shell";
|
||||
var message = $"Command not found: {e.Message}";
|
||||
_context.API.ShowMsg(name, message);
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
var name = "Plugin: Shell";
|
||||
var message = $"Error running the command: {e.Message}";
|
||||
_context.API.ShowMsg(name, message);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ExistInPath(string filename)
|
||||
{
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var values = Environment.GetEnvironmentVariable("PATH");
|
||||
if (values != null)
|
||||
{
|
||||
foreach (var path in values.Split(';'))
|
||||
{
|
||||
var path1 = Path.Combine(path, filename);
|
||||
var path2 = Path.Combine(path, filename + ".exe");
|
||||
if (File.Exists(path1) || File.Exists(path2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
this._context = context;
|
||||
_context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateIconPath(_context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
// Todo : Update with theme based IconPath
|
||||
private void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
IconPath = "Images/shell.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
IconPath = "Images/shell.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
{
|
||||
UpdateIconPath(newTheme);
|
||||
}
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
return new CMDSetting(_settings);
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_cmd_plugin_name");
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginDescription()
|
||||
{
|
||||
return _context.API.GetTranslation("wox_plugin_cmd_plugin_description");
|
||||
}
|
||||
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
var resultlist = new List<ContextMenuResult>
|
||||
{
|
||||
new ContextMenuResult
|
||||
{
|
||||
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
||||
Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_administrator"),
|
||||
Glyph = "\xE7EF",
|
||||
FontFamily = "Segoe MDL2 Assets",
|
||||
AcceleratorKey = Key.Enter,
|
||||
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Action = c =>
|
||||
{
|
||||
Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true));
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return resultlist;
|
||||
}
|
||||
|
||||
public void UpdateSettings(PowerLauncherSettings settings)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,124 +1,124 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Class housing fuzzy matching methods
|
||||
/// </summary>
|
||||
public class FuzzyMatching
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds the best match (the one with the most
|
||||
/// number of letters adjacent to each other) and
|
||||
/// returns the index location of each of the letters
|
||||
/// of the matches
|
||||
/// </summary>
|
||||
/// <param name="text">The text to search inside of</param>
|
||||
/// <param name="searchText">the text to search for</param>
|
||||
/// <returns>returns the index location of each of the letters of the matches</returns>
|
||||
public static List<int> FindBestFuzzyMatch(string text, string searchText)
|
||||
{
|
||||
searchText = searchText.ToLower();
|
||||
text = text.ToLower();
|
||||
|
||||
// Create a grid to march matches like
|
||||
// eg.
|
||||
// a b c a d e c f g
|
||||
// a x x
|
||||
// c x x
|
||||
bool[,] matches = new bool[text.Length, searchText.Length];
|
||||
for (int firstIndex = 0; firstIndex < text.Length; firstIndex++)
|
||||
{
|
||||
for (int secondIndex = 0; secondIndex < searchText.Length; secondIndex++)
|
||||
{
|
||||
matches[firstIndex, secondIndex] =
|
||||
searchText[secondIndex] == text[firstIndex] ?
|
||||
true :
|
||||
false;
|
||||
}
|
||||
}
|
||||
|
||||
// use this table to get all the possible matches
|
||||
List<List<int>> allMatches = GetAllMatchIndexes(matches);
|
||||
|
||||
// return the score that is the max
|
||||
int maxScore = allMatches.Count > 0 ? CalculateScoreForMatches(allMatches[0]) : 0;
|
||||
List<int> bestMatch = allMatches.Count > 0 ? allMatches[0] : new List<int>();
|
||||
|
||||
foreach (var match in allMatches)
|
||||
{
|
||||
int score = CalculateScoreForMatches(match);
|
||||
if (score > maxScore)
|
||||
{
|
||||
bestMatch = match;
|
||||
maxScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the possible matches to the search string with in the text
|
||||
/// </summary>
|
||||
/// <param name="matches"> a table showing the matches as generated by
|
||||
/// a two dimensional array with the first dimension the text and the second
|
||||
/// one the search string and each cell marked as an intersection between the two</param>
|
||||
/// <returns>a list of the possible combinations that match the search text</returns>
|
||||
public static List<List<int>> GetAllMatchIndexes(bool[,] matches)
|
||||
{
|
||||
List<List<int>> results = new List<List<int>>();
|
||||
|
||||
for (int secondIndex = 0; secondIndex < matches.GetLength(1); secondIndex++)
|
||||
{
|
||||
for (int firstIndex = 0; firstIndex < matches.GetLength(0); firstIndex++)
|
||||
{
|
||||
if (secondIndex == 0 && matches[firstIndex, secondIndex])
|
||||
{
|
||||
results.Add(new List<int> { firstIndex });
|
||||
}
|
||||
else if (matches[firstIndex, secondIndex])
|
||||
{
|
||||
var tempList = results.Where(x => x.Count == secondIndex && x[x.Count - 1] < firstIndex).Select(x => x.ToList()).ToList();
|
||||
|
||||
foreach (var pathSofar in tempList)
|
||||
{
|
||||
pathSofar.Add(firstIndex);
|
||||
}
|
||||
|
||||
results.AddRange(tempList);
|
||||
}
|
||||
}
|
||||
|
||||
results = results.Where(x => x.Count == secondIndex + 1).ToList();
|
||||
}
|
||||
|
||||
return results.Where(x => x.Count == matches.GetLength(1)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the score for a string
|
||||
/// </summary>
|
||||
/// <param name="matches">the index of the matches</param>
|
||||
/// <returns>an integer representing the score</returns>
|
||||
public static int CalculateScoreForMatches(List<int> matches)
|
||||
{
|
||||
var score = 0;
|
||||
|
||||
for (int currentIndex = 1; currentIndex < matches.Count; currentIndex++)
|
||||
{
|
||||
var previousIndex = currentIndex - 1;
|
||||
|
||||
score -= matches[currentIndex] - matches[previousIndex];
|
||||
}
|
||||
|
||||
return score == 0 ? -10000 : score;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Class housing fuzzy matching methods
|
||||
/// </summary>
|
||||
public class FuzzyMatching
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds the best match (the one with the most
|
||||
/// number of letters adjacent to each other) and
|
||||
/// returns the index location of each of the letters
|
||||
/// of the matches
|
||||
/// </summary>
|
||||
/// <param name="text">The text to search inside of</param>
|
||||
/// <param name="searchText">the text to search for</param>
|
||||
/// <returns>returns the index location of each of the letters of the matches</returns>
|
||||
public static List<int> FindBestFuzzyMatch(string text, string searchText)
|
||||
{
|
||||
searchText = searchText.ToLower();
|
||||
text = text.ToLower();
|
||||
|
||||
// Create a grid to march matches like
|
||||
// eg.
|
||||
// a b c a d e c f g
|
||||
// a x x
|
||||
// c x x
|
||||
bool[,] matches = new bool[text.Length, searchText.Length];
|
||||
for (int firstIndex = 0; firstIndex < text.Length; firstIndex++)
|
||||
{
|
||||
for (int secondIndex = 0; secondIndex < searchText.Length; secondIndex++)
|
||||
{
|
||||
matches[firstIndex, secondIndex] =
|
||||
searchText[secondIndex] == text[firstIndex] ?
|
||||
true :
|
||||
false;
|
||||
}
|
||||
}
|
||||
|
||||
// use this table to get all the possible matches
|
||||
List<List<int>> allMatches = GetAllMatchIndexes(matches);
|
||||
|
||||
// return the score that is the max
|
||||
int maxScore = allMatches.Count > 0 ? CalculateScoreForMatches(allMatches[0]) : 0;
|
||||
List<int> bestMatch = allMatches.Count > 0 ? allMatches[0] : new List<int>();
|
||||
|
||||
foreach (var match in allMatches)
|
||||
{
|
||||
int score = CalculateScoreForMatches(match);
|
||||
if (score > maxScore)
|
||||
{
|
||||
bestMatch = match;
|
||||
maxScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the possible matches to the search string with in the text
|
||||
/// </summary>
|
||||
/// <param name="matches"> a table showing the matches as generated by
|
||||
/// a two dimensional array with the first dimension the text and the second
|
||||
/// one the search string and each cell marked as an intersection between the two</param>
|
||||
/// <returns>a list of the possible combinations that match the search text</returns>
|
||||
public static List<List<int>> GetAllMatchIndexes(bool[,] matches)
|
||||
{
|
||||
List<List<int>> results = new List<List<int>>();
|
||||
|
||||
for (int secondIndex = 0; secondIndex < matches.GetLength(1); secondIndex++)
|
||||
{
|
||||
for (int firstIndex = 0; firstIndex < matches.GetLength(0); firstIndex++)
|
||||
{
|
||||
if (secondIndex == 0 && matches[firstIndex, secondIndex])
|
||||
{
|
||||
results.Add(new List<int> { firstIndex });
|
||||
}
|
||||
else if (matches[firstIndex, secondIndex])
|
||||
{
|
||||
var tempList = results.Where(x => x.Count == secondIndex && x[x.Count - 1] < firstIndex).Select(x => x.ToList()).ToList();
|
||||
|
||||
foreach (var pathSofar in tempList)
|
||||
{
|
||||
pathSofar.Add(firstIndex);
|
||||
}
|
||||
|
||||
results.AddRange(tempList);
|
||||
}
|
||||
}
|
||||
|
||||
results = results.Where(x => x.Count == secondIndex + 1).ToList();
|
||||
}
|
||||
|
||||
return results.Where(x => x.Count == matches.GetLength(1)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the score for a string
|
||||
/// </summary>
|
||||
/// <param name="matches">the index of the matches</param>
|
||||
/// <returns>an integer representing the score</returns>
|
||||
public static int CalculateScoreForMatches(List<int> matches)
|
||||
{
|
||||
var score = 0;
|
||||
|
||||
for (int currentIndex = 1; currentIndex < matches.Count; currentIndex++)
|
||||
{
|
||||
var previousIndex = currentIndex - 1;
|
||||
|
||||
score -= matches[currentIndex] - matches[previousIndex];
|
||||
}
|
||||
|
||||
return score == 0 ? -10000 : score;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,58 +1,58 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing methods to control the live preview
|
||||
/// </summary>
|
||||
internal class LivePreview
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes sure that a window is excluded from the live preview
|
||||
/// </summary>
|
||||
/// <param name="hwnd">handle to the window to exclude</param>
|
||||
public static void SetWindowExclusionFromLivePreview(IntPtr hwnd)
|
||||
{
|
||||
int renderPolicy = (int)InteropAndHelpers.DwmNCRenderingPolicy.Enabled;
|
||||
|
||||
InteropAndHelpers.DwmSetWindowAttribute(
|
||||
hwnd,
|
||||
12,
|
||||
ref renderPolicy,
|
||||
sizeof(int));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the live preview
|
||||
/// </summary>
|
||||
/// <param name="targetWindow">the window to show by making all other windows transparent</param>
|
||||
/// <param name="windowToSpare">the window which should not be transparent but is not the target window</param>
|
||||
public static void ActivateLivePreview(IntPtr targetWindow, IntPtr windowToSpare)
|
||||
{
|
||||
InteropAndHelpers.DwmpActivateLivePreview(
|
||||
true,
|
||||
targetWindow,
|
||||
windowToSpare,
|
||||
InteropAndHelpers.LivePreviewTrigger.Superbar,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates the live preview
|
||||
/// </summary>
|
||||
public static void DeactivateLivePreview()
|
||||
{
|
||||
InteropAndHelpers.DwmpActivateLivePreview(
|
||||
false,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
InteropAndHelpers.LivePreviewTrigger.AltTab,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing methods to control the live preview
|
||||
/// </summary>
|
||||
internal class LivePreview
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes sure that a window is excluded from the live preview
|
||||
/// </summary>
|
||||
/// <param name="hwnd">handle to the window to exclude</param>
|
||||
public static void SetWindowExclusionFromLivePreview(IntPtr hwnd)
|
||||
{
|
||||
int renderPolicy = (int)InteropAndHelpers.DwmNCRenderingPolicy.Enabled;
|
||||
|
||||
InteropAndHelpers.DwmSetWindowAttribute(
|
||||
hwnd,
|
||||
12,
|
||||
ref renderPolicy,
|
||||
sizeof(int));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the live preview
|
||||
/// </summary>
|
||||
/// <param name="targetWindow">the window to show by making all other windows transparent</param>
|
||||
/// <param name="windowToSpare">the window which should not be transparent but is not the target window</param>
|
||||
public static void ActivateLivePreview(IntPtr targetWindow, IntPtr windowToSpare)
|
||||
{
|
||||
InteropAndHelpers.DwmpActivateLivePreview(
|
||||
true,
|
||||
targetWindow,
|
||||
windowToSpare,
|
||||
InteropAndHelpers.LivePreviewTrigger.Superbar,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates the live preview
|
||||
/// </summary>
|
||||
public static void DeactivateLivePreview()
|
||||
{
|
||||
InteropAndHelpers.DwmpActivateLivePreview(
|
||||
false,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
InteropAndHelpers.LivePreviewTrigger.AltTab,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,106 +1,106 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that represents the state of the desktops windows
|
||||
/// </summary>
|
||||
internal class OpenWindows
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate handler for open windows updates
|
||||
/// </summary>
|
||||
public delegate void OpenWindowsUpdateHandler(object sender, SearchController.SearchResultUpdateEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when there is an update to the list of open windows
|
||||
/// </summary>
|
||||
public event OpenWindowsUpdateHandler OnOpenWindowsUpdate
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of all the open windows
|
||||
/// </summary>
|
||||
private readonly List<Window> windows = new List<Window>();
|
||||
|
||||
/// <summary>
|
||||
/// An instance of the class OpenWindows
|
||||
/// </summary>
|
||||
private static OpenWindows instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all open windows
|
||||
/// </summary>
|
||||
public List<Window> Windows
|
||||
{
|
||||
get { return new List<Window>(windows); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance property of this class that makes sure that
|
||||
/// the first instance gets created and that all the requests
|
||||
/// end up at that one instance
|
||||
/// </summary>
|
||||
public static OpenWindows Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new OpenWindows();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OpenWindows"/> class.
|
||||
/// Private constructor to make sure there is never
|
||||
/// more than one instance of this class
|
||||
/// </summary>
|
||||
private OpenWindows()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the list of open windows
|
||||
/// </summary>
|
||||
public void UpdateOpenWindowsList()
|
||||
{
|
||||
windows.Clear();
|
||||
InteropAndHelpers.CallBackPtr callbackptr = new InteropAndHelpers.CallBackPtr(WindowEnumerationCallBack);
|
||||
InteropAndHelpers.EnumWindows(callbackptr, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call back method for window enumeration
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the current window being enumerated</param>
|
||||
/// <param name="lParam">Value being passed from the caller (we don't use this but might come in handy
|
||||
/// in the future</param>
|
||||
/// <returns>true to make sure to continue enumeration</returns>
|
||||
public bool WindowEnumerationCallBack(IntPtr hwnd, IntPtr lParam)
|
||||
{
|
||||
Window newWindow = new Window(hwnd);
|
||||
|
||||
if (newWindow.IsWindow && newWindow.Visible && newWindow.IsOwner &&
|
||||
(!newWindow.IsToolWindow || newWindow.IsAppWindow) && !newWindow.TaskListDeleted &&
|
||||
newWindow.ClassName != "Windows.UI.Core.CoreWindow")
|
||||
{
|
||||
windows.Add(newWindow);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that represents the state of the desktops windows
|
||||
/// </summary>
|
||||
internal class OpenWindows
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate handler for open windows updates
|
||||
/// </summary>
|
||||
public delegate void OpenWindowsUpdateHandler(object sender, SearchController.SearchResultUpdateEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when there is an update to the list of open windows
|
||||
/// </summary>
|
||||
public event OpenWindowsUpdateHandler OnOpenWindowsUpdate
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of all the open windows
|
||||
/// </summary>
|
||||
private readonly List<Window> windows = new List<Window>();
|
||||
|
||||
/// <summary>
|
||||
/// An instance of the class OpenWindows
|
||||
/// </summary>
|
||||
private static OpenWindows instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all open windows
|
||||
/// </summary>
|
||||
public List<Window> Windows
|
||||
{
|
||||
get { return new List<Window>(windows); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance property of this class that makes sure that
|
||||
/// the first instance gets created and that all the requests
|
||||
/// end up at that one instance
|
||||
/// </summary>
|
||||
public static OpenWindows Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new OpenWindows();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OpenWindows"/> class.
|
||||
/// Private constructor to make sure there is never
|
||||
/// more than one instance of this class
|
||||
/// </summary>
|
||||
private OpenWindows()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the list of open windows
|
||||
/// </summary>
|
||||
public void UpdateOpenWindowsList()
|
||||
{
|
||||
windows.Clear();
|
||||
InteropAndHelpers.CallBackPtr callbackptr = new InteropAndHelpers.CallBackPtr(WindowEnumerationCallBack);
|
||||
InteropAndHelpers.EnumWindows(callbackptr, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call back method for window enumeration
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the current window being enumerated</param>
|
||||
/// <param name="lParam">Value being passed from the caller (we don't use this but might come in handy
|
||||
/// in the future</param>
|
||||
/// <returns>true to make sure to continue enumeration</returns>
|
||||
public bool WindowEnumerationCallBack(IntPtr hwnd, IntPtr lParam)
|
||||
{
|
||||
Window newWindow = new Window(hwnd);
|
||||
|
||||
if (newWindow.IsWindow && newWindow.Visible && newWindow.IsOwner &&
|
||||
(!newWindow.IsToolWindow || newWindow.IsAppWindow) && !newWindow.TaskListDeleted &&
|
||||
newWindow.ClassName != "Windows.UI.Core.CoreWindow")
|
||||
{
|
||||
windows.Add(newWindow);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,186 +1,186 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for searching and finding matches for the strings provided.
|
||||
/// Essentially the UI independent model of the application
|
||||
/// </summary>
|
||||
internal class SearchController
|
||||
{
|
||||
/// <summary>
|
||||
/// the current search text
|
||||
/// </summary>
|
||||
private string searchText;
|
||||
|
||||
/// <summary>
|
||||
/// Open window search results
|
||||
/// </summary
|
||||
private List<SearchResult> searchMatches;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton pattern
|
||||
/// </summary>
|
||||
private static SearchController instance;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate handler for open windows updates
|
||||
/// </summary>
|
||||
public delegate void SearchResultUpdateHandler(object sender, SearchResultUpdateEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when there is an update to the list of open windows
|
||||
/// </summary>
|
||||
public event SearchResultUpdateHandler OnSearchResultUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current search text
|
||||
/// </summary>
|
||||
public string SearchText
|
||||
{
|
||||
get
|
||||
{
|
||||
return searchText;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
searchText = value.ToLower().Trim();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the open window search results
|
||||
/// </summary>
|
||||
public List<SearchResult> SearchMatches
|
||||
{
|
||||
get { return new List<SearchResult>(searchMatches).OrderByDescending(x => x.Score).ToList(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets singleton Pattern
|
||||
/// </summary>
|
||||
public static SearchController Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new SearchController();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchController"/> class.
|
||||
/// Initializes the search controller object
|
||||
/// </summary>
|
||||
private SearchController()
|
||||
{
|
||||
searchText = string.Empty;
|
||||
OpenWindows.Instance.OnOpenWindowsUpdate += OpenWindowsUpdateHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for when the search text has been updated
|
||||
/// </summary>
|
||||
public async Task UpdateSearchText(string searchText)
|
||||
{
|
||||
SearchText = searchText;
|
||||
await SyncOpenWindowsWithModelAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler called when the OpenWindows list changes
|
||||
/// </summary>
|
||||
/// <param name="sender">sending item</param>
|
||||
/// <param name="e">event arg</param>
|
||||
public async void OpenWindowsUpdateHandler(object sender, SearchResultUpdateEventArgs e)
|
||||
{
|
||||
await SyncOpenWindowsWithModelAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Syncs the open windows with the OpenWindows Model
|
||||
/// </summary>
|
||||
public async Task SyncOpenWindowsWithModelAsync()
|
||||
{
|
||||
System.Diagnostics.Debug.Print("Syncing WindowSearch result with OpenWindows Model");
|
||||
|
||||
List<Window> snapshotOfOpenWindows = OpenWindows.Instance.Windows;
|
||||
|
||||
if (SearchText == string.Empty)
|
||||
{
|
||||
searchMatches = new List<SearchResult>();
|
||||
}
|
||||
else
|
||||
{
|
||||
searchMatches = await FuzzySearchOpenWindowsAsync(snapshotOfOpenWindows);
|
||||
}
|
||||
|
||||
OnSearchResultUpdate?.Invoke(this, new SearchResultUpdateEventArgs());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redirecting method for Fuzzy searching
|
||||
/// </summary>
|
||||
/// <param name="openWindows">what windows are open</param>
|
||||
/// <returns>Returns search results</returns>
|
||||
private Task<List<SearchResult>> FuzzySearchOpenWindowsAsync(List<Window> openWindows)
|
||||
{
|
||||
return Task.Run(
|
||||
() =>
|
||||
FuzzySearchOpenWindows(openWindows));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search method that matches the title of windows with the user search text
|
||||
/// </summary>
|
||||
/// <param name="openWindows">what windows are open</param>
|
||||
/// <returns>Returns search results</returns>
|
||||
private List<SearchResult> FuzzySearchOpenWindows(List<Window> openWindows)
|
||||
{
|
||||
List<SearchResult> result = new List<SearchResult>();
|
||||
List<SearchString> searchStrings = new List<SearchString>();
|
||||
|
||||
searchStrings.Add(new SearchString(searchText, SearchResult.SearchType.Fuzzy));
|
||||
|
||||
foreach (var searchString in searchStrings)
|
||||
{
|
||||
foreach (var window in openWindows)
|
||||
{
|
||||
var titleMatch = FuzzyMatching.FindBestFuzzyMatch(window.Title, searchString.SearchText);
|
||||
var processMatch = FuzzyMatching.FindBestFuzzyMatch(window.ProcessName, searchString.SearchText);
|
||||
|
||||
if ((titleMatch.Count != 0 || processMatch.Count != 0) &&
|
||||
window.Title.Length != 0)
|
||||
{
|
||||
var temp = new SearchResult(window, titleMatch, processMatch, searchString.SearchType);
|
||||
result.Add(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.Print("Found " + result.Count + " windows that match the search text");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event args for a window list update event
|
||||
/// </summary>
|
||||
public class SearchResultUpdateEventArgs : EventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for searching and finding matches for the strings provided.
|
||||
/// Essentially the UI independent model of the application
|
||||
/// </summary>
|
||||
internal class SearchController
|
||||
{
|
||||
/// <summary>
|
||||
/// the current search text
|
||||
/// </summary>
|
||||
private string searchText;
|
||||
|
||||
/// <summary>
|
||||
/// Open window search results
|
||||
/// </summary
|
||||
private List<SearchResult> searchMatches;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton pattern
|
||||
/// </summary>
|
||||
private static SearchController instance;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate handler for open windows updates
|
||||
/// </summary>
|
||||
public delegate void SearchResultUpdateHandler(object sender, SearchResultUpdateEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when there is an update to the list of open windows
|
||||
/// </summary>
|
||||
public event SearchResultUpdateHandler OnSearchResultUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current search text
|
||||
/// </summary>
|
||||
public string SearchText
|
||||
{
|
||||
get
|
||||
{
|
||||
return searchText;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
searchText = value.ToLower().Trim();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the open window search results
|
||||
/// </summary>
|
||||
public List<SearchResult> SearchMatches
|
||||
{
|
||||
get { return new List<SearchResult>(searchMatches).OrderByDescending(x => x.Score).ToList(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets singleton Pattern
|
||||
/// </summary>
|
||||
public static SearchController Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new SearchController();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchController"/> class.
|
||||
/// Initializes the search controller object
|
||||
/// </summary>
|
||||
private SearchController()
|
||||
{
|
||||
searchText = string.Empty;
|
||||
OpenWindows.Instance.OnOpenWindowsUpdate += OpenWindowsUpdateHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for when the search text has been updated
|
||||
/// </summary>
|
||||
public async Task UpdateSearchText(string searchText)
|
||||
{
|
||||
SearchText = searchText;
|
||||
await SyncOpenWindowsWithModelAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler called when the OpenWindows list changes
|
||||
/// </summary>
|
||||
/// <param name="sender">sending item</param>
|
||||
/// <param name="e">event arg</param>
|
||||
public async void OpenWindowsUpdateHandler(object sender, SearchResultUpdateEventArgs e)
|
||||
{
|
||||
await SyncOpenWindowsWithModelAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Syncs the open windows with the OpenWindows Model
|
||||
/// </summary>
|
||||
public async Task SyncOpenWindowsWithModelAsync()
|
||||
{
|
||||
System.Diagnostics.Debug.Print("Syncing WindowSearch result with OpenWindows Model");
|
||||
|
||||
List<Window> snapshotOfOpenWindows = OpenWindows.Instance.Windows;
|
||||
|
||||
if (SearchText == string.Empty)
|
||||
{
|
||||
searchMatches = new List<SearchResult>();
|
||||
}
|
||||
else
|
||||
{
|
||||
searchMatches = await FuzzySearchOpenWindowsAsync(snapshotOfOpenWindows);
|
||||
}
|
||||
|
||||
OnSearchResultUpdate?.Invoke(this, new SearchResultUpdateEventArgs());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redirecting method for Fuzzy searching
|
||||
/// </summary>
|
||||
/// <param name="openWindows">what windows are open</param>
|
||||
/// <returns>Returns search results</returns>
|
||||
private Task<List<SearchResult>> FuzzySearchOpenWindowsAsync(List<Window> openWindows)
|
||||
{
|
||||
return Task.Run(
|
||||
() =>
|
||||
FuzzySearchOpenWindows(openWindows));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search method that matches the title of windows with the user search text
|
||||
/// </summary>
|
||||
/// <param name="openWindows">what windows are open</param>
|
||||
/// <returns>Returns search results</returns>
|
||||
private List<SearchResult> FuzzySearchOpenWindows(List<Window> openWindows)
|
||||
{
|
||||
List<SearchResult> result = new List<SearchResult>();
|
||||
List<SearchString> searchStrings = new List<SearchString>();
|
||||
|
||||
searchStrings.Add(new SearchString(searchText, SearchResult.SearchType.Fuzzy));
|
||||
|
||||
foreach (var searchString in searchStrings)
|
||||
{
|
||||
foreach (var window in openWindows)
|
||||
{
|
||||
var titleMatch = FuzzyMatching.FindBestFuzzyMatch(window.Title, searchString.SearchText);
|
||||
var processMatch = FuzzyMatching.FindBestFuzzyMatch(window.ProcessName, searchString.SearchText);
|
||||
|
||||
if ((titleMatch.Count != 0 || processMatch.Count != 0) &&
|
||||
window.Title.Length != 0)
|
||||
{
|
||||
var temp = new SearchResult(window, titleMatch, processMatch, searchString.SearchType);
|
||||
result.Add(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.Print("Found " + result.Count + " windows that match the search text");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event args for a window list update event
|
||||
/// </summary>
|
||||
public class SearchResultUpdateEventArgs : EventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +1,136 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains search result windows with each window including the reason why the result was included
|
||||
/// </summary>
|
||||
public class SearchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the actual window reference for the search result
|
||||
/// </summary>
|
||||
public Window Result
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the title window
|
||||
/// </summary>
|
||||
public List<int> SearchMatchesInTitle
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the
|
||||
/// name of the process
|
||||
/// </summary>
|
||||
public List<int> SearchMatchesInProcessName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of match (shortcut, fuzzy or nothing)
|
||||
/// </summary>
|
||||
public SearchType SearchResultMatchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a score indicating how well this matches what we are looking for
|
||||
/// </summary>
|
||||
public int Score
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source of where the best score was found
|
||||
/// </summary>
|
||||
public TextType BestScoreSource
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchResult"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
|
||||
{
|
||||
Result = window;
|
||||
SearchMatchesInTitle = matchesInTitle;
|
||||
SearchMatchesInProcessName = matchesInProcessName;
|
||||
SearchResultMatchType = matchType;
|
||||
CalculateScore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the score for how closely this window matches the search string
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Higher Score is better
|
||||
/// </remarks>
|
||||
private void CalculateScore()
|
||||
{
|
||||
if (FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName) >
|
||||
FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle))
|
||||
{
|
||||
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName);
|
||||
BestScoreSource = TextType.ProcessName;
|
||||
}
|
||||
else
|
||||
{
|
||||
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle);
|
||||
BestScoreSource = TextType.WindowTitle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of text that a string represents
|
||||
/// </summary>
|
||||
public enum TextType
|
||||
{
|
||||
ProcessName,
|
||||
WindowTitle,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of search
|
||||
/// </summary>
|
||||
public enum SearchType
|
||||
{
|
||||
/// <summary>
|
||||
/// the search string is empty, which means all open windows are
|
||||
/// going to be returned
|
||||
/// </summary>
|
||||
Empty,
|
||||
|
||||
/// <summary>
|
||||
/// Regular fuzzy match search
|
||||
/// </summary>
|
||||
Fuzzy,
|
||||
|
||||
/// <summary>
|
||||
/// The user has entered text that has been matched to a shortcut
|
||||
/// and the shortcut is now being searched
|
||||
/// </summary>
|
||||
Shortcut,
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains search result windows with each window including the reason why the result was included
|
||||
/// </summary>
|
||||
public class SearchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the actual window reference for the search result
|
||||
/// </summary>
|
||||
public Window Result
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the title window
|
||||
/// </summary>
|
||||
public List<int> SearchMatchesInTitle
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of indexes of the matching characters for the search in the
|
||||
/// name of the process
|
||||
/// </summary>
|
||||
public List<int> SearchMatchesInProcessName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of match (shortcut, fuzzy or nothing)
|
||||
/// </summary>
|
||||
public SearchType SearchResultMatchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a score indicating how well this matches what we are looking for
|
||||
/// </summary>
|
||||
public int Score
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source of where the best score was found
|
||||
/// </summary>
|
||||
public TextType BestScoreSource
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchResult"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
|
||||
{
|
||||
Result = window;
|
||||
SearchMatchesInTitle = matchesInTitle;
|
||||
SearchMatchesInProcessName = matchesInProcessName;
|
||||
SearchResultMatchType = matchType;
|
||||
CalculateScore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the score for how closely this window matches the search string
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Higher Score is better
|
||||
/// </remarks>
|
||||
private void CalculateScore()
|
||||
{
|
||||
if (FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName) >
|
||||
FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle))
|
||||
{
|
||||
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName);
|
||||
BestScoreSource = TextType.ProcessName;
|
||||
}
|
||||
else
|
||||
{
|
||||
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle);
|
||||
BestScoreSource = TextType.WindowTitle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of text that a string represents
|
||||
/// </summary>
|
||||
public enum TextType
|
||||
{
|
||||
ProcessName,
|
||||
WindowTitle,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of search
|
||||
/// </summary>
|
||||
public enum SearchType
|
||||
{
|
||||
/// <summary>
|
||||
/// the search string is empty, which means all open windows are
|
||||
/// going to be returned
|
||||
/// </summary>
|
||||
Empty,
|
||||
|
||||
/// <summary>
|
||||
/// Regular fuzzy match search
|
||||
/// </summary>
|
||||
Fuzzy,
|
||||
|
||||
/// <summary>
|
||||
/// The user has entered text that has been matched to a shortcut
|
||||
/// and the shortcut is now being searched
|
||||
/// </summary>
|
||||
Shortcut,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to represent a search string
|
||||
/// </summary>
|
||||
/// <remarks>Class was added inorder to be able to attach various context data to
|
||||
/// a search string</remarks>
|
||||
internal class SearchString
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets where is the search string coming from (is it a shortcut
|
||||
/// or direct string, etc...)
|
||||
/// </summary>
|
||||
public SearchResult.SearchType SearchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual text we are searching for
|
||||
/// </summary>
|
||||
public string SearchText
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchString"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="searchText">text from search</param>
|
||||
/// <param name="searchType">type of search</param>
|
||||
public SearchString(string searchText, SearchResult.SearchType searchType)
|
||||
{
|
||||
SearchText = searchText;
|
||||
SearchType = searchType;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to represent a search string
|
||||
/// </summary>
|
||||
/// <remarks>Class was added inorder to be able to attach various context data to
|
||||
/// a search string</remarks>
|
||||
internal class SearchString
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets where is the search string coming from (is it a shortcut
|
||||
/// or direct string, etc...)
|
||||
/// </summary>
|
||||
public SearchResult.SearchType SearchType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual text we are searching for
|
||||
/// </summary>
|
||||
public string SearchText
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SearchString"/> class.
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="searchText">text from search</param>
|
||||
/// <param name="searchType">type of search</param>
|
||||
public SearchString(string searchText, SearchResult.SearchType searchType)
|
||||
{
|
||||
SearchText = searchText;
|
||||
SearchType = searchType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,402 +1,402 @@
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a specific open window
|
||||
/// </summary>
|
||||
public class Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum size of a file name
|
||||
/// </summary>
|
||||
private const int MaximumFileNameLength = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// The list of owners of a window so that we don't have to
|
||||
/// constantly query for the process owning a specific window
|
||||
/// </summary>
|
||||
private static readonly Dictionary<IntPtr, string> _handlesToProcessCache = new Dictionary<IntPtr, string>();
|
||||
|
||||
/// <summary>
|
||||
/// The list of icons from process so that we don't have to keep
|
||||
/// loading them from disk
|
||||
/// </summary>
|
||||
private static readonly Dictionary<uint, ImageSource> _processIdsToIconsCache = new Dictionary<uint, ImageSource>();
|
||||
|
||||
/// <summary>
|
||||
/// The handle to the window
|
||||
/// </summary>
|
||||
private readonly IntPtr hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the window (the string displayed at the top of the window)
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
int sizeOfTitle = InteropAndHelpers.GetWindowTextLength(hwnd);
|
||||
if (sizeOfTitle++ > 0)
|
||||
{
|
||||
StringBuilder titleBuffer = new StringBuilder(sizeOfTitle);
|
||||
InteropAndHelpers.GetWindowText(hwnd, titleBuffer, sizeOfTitle);
|
||||
return titleBuffer.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the window
|
||||
/// </summary>
|
||||
public IntPtr Hwnd
|
||||
{
|
||||
get { return hwnd; }
|
||||
}
|
||||
|
||||
public uint ProcessID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets returns the name of the process
|
||||
/// </summary>
|
||||
public string ProcessName
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_handlesToProcessCache)
|
||||
{
|
||||
if (_handlesToProcessCache.Count > 7000)
|
||||
{
|
||||
Debug.Print("Clearing Process Cache because it's size is " + _handlesToProcessCache.Count);
|
||||
_handlesToProcessCache.Clear();
|
||||
}
|
||||
|
||||
if (!_handlesToProcessCache.ContainsKey(Hwnd))
|
||||
{
|
||||
var processName = GetProcessNameFromWindowHandle(Hwnd);
|
||||
|
||||
if (processName.Length != 0)
|
||||
{
|
||||
_handlesToProcessCache.Add(
|
||||
Hwnd,
|
||||
processName.ToString().Split('\\').Reverse().ToArray()[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_handlesToProcessCache.Add(Hwnd, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
if (_handlesToProcessCache[hwnd].ToLower() == "applicationframehost.exe")
|
||||
{
|
||||
new Task(() =>
|
||||
{
|
||||
InteropAndHelpers.CallBackPtr callbackptr = new InteropAndHelpers.CallBackPtr((IntPtr hwnd, IntPtr lParam) =>
|
||||
{
|
||||
var childProcessId = GetProcessIDFromWindowHandle(hwnd);
|
||||
if (childProcessId != ProcessID)
|
||||
{
|
||||
_handlesToProcessCache[Hwnd] = GetProcessNameFromWindowHandle(hwnd);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
InteropAndHelpers.EnumChildWindows(Hwnd, callbackptr, 0);
|
||||
}).Start();
|
||||
}
|
||||
|
||||
return _handlesToProcessCache[hwnd];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets returns the name of the class for the window represented
|
||||
/// </summary>
|
||||
public string ClassName
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder windowClassName = new StringBuilder(300);
|
||||
InteropAndHelpers.GetClassName(Hwnd, windowClassName, windowClassName.MaxCapacity);
|
||||
|
||||
return windowClassName.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets represents the Window Icon for the specified window
|
||||
/// </summary>
|
||||
public ImageSource WindowIcon
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_processIdsToIconsCache)
|
||||
{
|
||||
InteropAndHelpers.GetWindowThreadProcessId(Hwnd, out uint processId);
|
||||
|
||||
if (!_processIdsToIconsCache.ContainsKey(processId))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process process = Process.GetProcessById((int)processId);
|
||||
Icon tempIcon = Icon.ExtractAssociatedIcon(process.Modules[0].FileName);
|
||||
_processIdsToIconsCache.Add(processId, Imaging.CreateBitmapSourceFromHIcon(
|
||||
tempIcon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions()));
|
||||
}
|
||||
catch
|
||||
{
|
||||
BitmapImage failedImage = new BitmapImage(new Uri(@"Images\failedIcon.jpg", UriKind.Relative));
|
||||
_processIdsToIconsCache.Add(processId, failedImage);
|
||||
}
|
||||
}
|
||||
|
||||
return _processIdsToIconsCache[processId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether is the window visible (might return false if it is a hidden IE tab)
|
||||
/// </summary>
|
||||
public bool Visible
|
||||
{
|
||||
get
|
||||
{
|
||||
return InteropAndHelpers.IsWindowVisible(Hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether determines whether the specified window handle identifies an existing window.
|
||||
/// </summary>
|
||||
public bool IsWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
return InteropAndHelpers.IsWindow(Hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether get a value indicating whether is the window GWL_EX_STYLE is a toolwindow
|
||||
/// </summary>
|
||||
public bool IsToolWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
return (InteropAndHelpers.GetWindowLong(Hwnd, InteropAndHelpers.GWL_EXSTYLE) &
|
||||
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_TOOLWINDOW) ==
|
||||
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_TOOLWINDOW;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether get a value indicating whether the window GWL_EX_STYLE is an appwindow
|
||||
/// </summary>
|
||||
public bool IsAppWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
return (InteropAndHelpers.GetWindowLong(Hwnd, InteropAndHelpers.GWL_EXSTYLE) &
|
||||
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_APPWINDOW) ==
|
||||
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_APPWINDOW;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether get a value indicating whether the window has ITaskList_Deleted property
|
||||
/// </summary>
|
||||
public bool TaskListDeleted
|
||||
{
|
||||
get
|
||||
{
|
||||
return InteropAndHelpers.GetProp(Hwnd, "ITaskList_Deleted") != IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether get a value indicating whether the app is a cloaked UWP app
|
||||
/// </summary>
|
||||
public bool IsUWPCloaked
|
||||
{
|
||||
get
|
||||
{
|
||||
return IsWindowCloaked() && ClassName == "ApplicationFrameWindow";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether determines whether the specified windows is the owner
|
||||
/// </summary>
|
||||
public bool IsOwner
|
||||
{
|
||||
get
|
||||
{
|
||||
return InteropAndHelpers.GetWindow(Hwnd, InteropAndHelpers.GetWindowCmd.GW_OWNER) != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether is the window cloaked. To detect UWP apps in background or win32 apps running in another virtual desktop
|
||||
/// </summary>
|
||||
public bool IsWindowCloaked()
|
||||
{
|
||||
int isCloaked = 0;
|
||||
const int DWMWA_CLOAKED = 14;
|
||||
InteropAndHelpers.DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, out isCloaked, sizeof(int));
|
||||
return isCloaked != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether returns true if the window is minimized
|
||||
/// </summary>
|
||||
public bool Minimized
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetWindowSizeState() == WindowSizeState.Minimized;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Window"/> class.
|
||||
/// Initializes a new Window representation
|
||||
/// </summary>
|
||||
/// <param name="hwnd">the handle to the window we are representing</param>
|
||||
public Window(IntPtr hwnd)
|
||||
{
|
||||
// TODO: Add verification as to whether the window handle is valid
|
||||
this.hwnd = hwnd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Highlights a window to help the user identify the window that has been selected
|
||||
/// </summary>
|
||||
public void HighlightWindow()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switches desktop focus to the window
|
||||
/// </summary>
|
||||
public void SwitchToWindow()
|
||||
{
|
||||
// The following block is necessary because
|
||||
// 1) There is a weird flashing behavior when trying
|
||||
// to use ShowWindow for switching tabs in IE
|
||||
// 2) SetForegroundWindow fails on minimized windows
|
||||
if (ProcessName.ToLower().Equals("iexplore.exe") || !Minimized)
|
||||
{
|
||||
InteropAndHelpers.SetForegroundWindow(Hwnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
InteropAndHelpers.ShowWindow(Hwnd, InteropAndHelpers.ShowWindowCommands.Restore);
|
||||
}
|
||||
|
||||
InteropAndHelpers.FlashWindow(Hwnd, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the window name to string along with the process name
|
||||
/// </summary>
|
||||
/// <returns>The title of the window</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Title + " (" + ProcessName.ToUpper() + ")";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns what the window size is
|
||||
/// </summary>
|
||||
/// <returns>The state (minimized, maximized, etc..) of the window</returns>
|
||||
public WindowSizeState GetWindowSizeState()
|
||||
{
|
||||
InteropAndHelpers.GetWindowPlacement(Hwnd, out InteropAndHelpers.WINDOWPLACEMENT placement);
|
||||
|
||||
switch (placement.ShowCmd)
|
||||
{
|
||||
case InteropAndHelpers.ShowWindowCommands.Normal:
|
||||
return WindowSizeState.Normal;
|
||||
case InteropAndHelpers.ShowWindowCommands.Minimize:
|
||||
case InteropAndHelpers.ShowWindowCommands.ShowMinimized:
|
||||
return WindowSizeState.Minimized;
|
||||
case InteropAndHelpers.ShowWindowCommands.Maximize: // No need for ShowMaximized here since its also of value 3
|
||||
return WindowSizeState.Maximized;
|
||||
default:
|
||||
// throw new Exception("Don't know how to handle window state = " + placement.ShowCmd);
|
||||
return WindowSizeState.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to simplify the state of the window
|
||||
/// </summary>
|
||||
public enum WindowSizeState
|
||||
{
|
||||
Normal,
|
||||
Minimized,
|
||||
Maximized,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the process using the window handle
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the window</param>
|
||||
/// <returns>A string representing the process name or an empty string if the function fails</returns>
|
||||
private string GetProcessNameFromWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
uint processId = GetProcessIDFromWindowHandle(hwnd);
|
||||
ProcessID = processId;
|
||||
IntPtr processHandle = InteropAndHelpers.OpenProcess(InteropAndHelpers.ProcessAccessFlags.AllAccess, true, (int)processId);
|
||||
StringBuilder processName = new StringBuilder(MaximumFileNameLength);
|
||||
|
||||
if (InteropAndHelpers.GetProcessImageFileName(processHandle, processName, MaximumFileNameLength) != 0)
|
||||
{
|
||||
return processName.ToString().Split('\\').Reverse().ToArray()[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process ID for the Window handle
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the window</param>
|
||||
/// <returns>The process ID</returns>
|
||||
private uint GetProcessIDFromWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
InteropAndHelpers.GetWindowThreadProcessId(hwnd, out uint processId);
|
||||
return processId;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
|
||||
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Microsoft.Plugin.WindowWalker.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a specific open window
|
||||
/// </summary>
|
||||
public class Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum size of a file name
|
||||
/// </summary>
|
||||
private const int MaximumFileNameLength = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// The list of owners of a window so that we don't have to
|
||||
/// constantly query for the process owning a specific window
|
||||
/// </summary>
|
||||
private static readonly Dictionary<IntPtr, string> _handlesToProcessCache = new Dictionary<IntPtr, string>();
|
||||
|
||||
/// <summary>
|
||||
/// The list of icons from process so that we don't have to keep
|
||||
/// loading them from disk
|
||||
/// </summary>
|
||||
private static readonly Dictionary<uint, ImageSource> _processIdsToIconsCache = new Dictionary<uint, ImageSource>();
|
||||
|
||||
/// <summary>
|
||||
/// The handle to the window
|
||||
/// </summary>
|
||||
private readonly IntPtr hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the window (the string displayed at the top of the window)
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
int sizeOfTitle = InteropAndHelpers.GetWindowTextLength(hwnd);
|
||||
if (sizeOfTitle++ > 0)
|
||||
{
|
||||
StringBuilder titleBuffer = new StringBuilder(sizeOfTitle);
|
||||
InteropAndHelpers.GetWindowText(hwnd, titleBuffer, sizeOfTitle);
|
||||
return titleBuffer.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the window
|
||||
/// </summary>
|
||||
public IntPtr Hwnd
|
||||
{
|
||||
get { return hwnd; }
|
||||
}
|
||||
|
||||
public uint ProcessID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets returns the name of the process
|
||||
/// </summary>
|
||||
public string ProcessName
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_handlesToProcessCache)
|
||||
{
|
||||
if (_handlesToProcessCache.Count > 7000)
|
||||
{
|
||||
Debug.Print("Clearing Process Cache because it's size is " + _handlesToProcessCache.Count);
|
||||
_handlesToProcessCache.Clear();
|
||||
}
|
||||
|
||||
if (!_handlesToProcessCache.ContainsKey(Hwnd))
|
||||
{
|
||||
var processName = GetProcessNameFromWindowHandle(Hwnd);
|
||||
|
||||
if (processName.Length != 0)
|
||||
{
|
||||
_handlesToProcessCache.Add(
|
||||
Hwnd,
|
||||
processName.ToString().Split('\\').Reverse().ToArray()[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_handlesToProcessCache.Add(Hwnd, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
if (_handlesToProcessCache[hwnd].ToLower() == "applicationframehost.exe")
|
||||
{
|
||||
new Task(() =>
|
||||
{
|
||||
InteropAndHelpers.CallBackPtr callbackptr = new InteropAndHelpers.CallBackPtr((IntPtr hwnd, IntPtr lParam) =>
|
||||
{
|
||||
var childProcessId = GetProcessIDFromWindowHandle(hwnd);
|
||||
if (childProcessId != ProcessID)
|
||||
{
|
||||
_handlesToProcessCache[Hwnd] = GetProcessNameFromWindowHandle(hwnd);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
InteropAndHelpers.EnumChildWindows(Hwnd, callbackptr, 0);
|
||||
}).Start();
|
||||
}
|
||||
|
||||
return _handlesToProcessCache[hwnd];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets returns the name of the class for the window represented
|
||||
/// </summary>
|
||||
public string ClassName
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder windowClassName = new StringBuilder(300);
|
||||
InteropAndHelpers.GetClassName(Hwnd, windowClassName, windowClassName.MaxCapacity);
|
||||
|
||||
return windowClassName.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets represents the Window Icon for the specified window
|
||||
/// </summary>
|
||||
public ImageSource WindowIcon
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_processIdsToIconsCache)
|
||||
{
|
||||
InteropAndHelpers.GetWindowThreadProcessId(Hwnd, out uint processId);
|
||||
|
||||
if (!_processIdsToIconsCache.ContainsKey(processId))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process process = Process.GetProcessById((int)processId);
|
||||
Icon tempIcon = Icon.ExtractAssociatedIcon(process.Modules[0].FileName);
|
||||
_processIdsToIconsCache.Add(processId, Imaging.CreateBitmapSourceFromHIcon(
|
||||
tempIcon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions()));
|
||||
}
|
||||
catch
|
||||
{
|
||||
BitmapImage failedImage = new BitmapImage(new Uri(@"Images\failedIcon.jpg", UriKind.Relative));
|
||||
_processIdsToIconsCache.Add(processId, failedImage);
|
||||
}
|
||||
}
|
||||
|
||||
return _processIdsToIconsCache[processId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether is the window visible (might return false if it is a hidden IE tab)
|
||||
/// </summary>
|
||||
public bool Visible
|
||||
{
|
||||
get
|
||||
{
|
||||
return InteropAndHelpers.IsWindowVisible(Hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether determines whether the specified window handle identifies an existing window.
|
||||
/// </summary>
|
||||
public bool IsWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
return InteropAndHelpers.IsWindow(Hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether get a value indicating whether is the window GWL_EX_STYLE is a toolwindow
|
||||
/// </summary>
|
||||
public bool IsToolWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
return (InteropAndHelpers.GetWindowLong(Hwnd, InteropAndHelpers.GWL_EXSTYLE) &
|
||||
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_TOOLWINDOW) ==
|
||||
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_TOOLWINDOW;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether get a value indicating whether the window GWL_EX_STYLE is an appwindow
|
||||
/// </summary>
|
||||
public bool IsAppWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
return (InteropAndHelpers.GetWindowLong(Hwnd, InteropAndHelpers.GWL_EXSTYLE) &
|
||||
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_APPWINDOW) ==
|
||||
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_APPWINDOW;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether get a value indicating whether the window has ITaskList_Deleted property
|
||||
/// </summary>
|
||||
public bool TaskListDeleted
|
||||
{
|
||||
get
|
||||
{
|
||||
return InteropAndHelpers.GetProp(Hwnd, "ITaskList_Deleted") != IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether get a value indicating whether the app is a cloaked UWP app
|
||||
/// </summary>
|
||||
public bool IsUWPCloaked
|
||||
{
|
||||
get
|
||||
{
|
||||
return IsWindowCloaked() && ClassName == "ApplicationFrameWindow";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether determines whether the specified windows is the owner
|
||||
/// </summary>
|
||||
public bool IsOwner
|
||||
{
|
||||
get
|
||||
{
|
||||
return InteropAndHelpers.GetWindow(Hwnd, InteropAndHelpers.GetWindowCmd.GW_OWNER) != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether is the window cloaked. To detect UWP apps in background or win32 apps running in another virtual desktop
|
||||
/// </summary>
|
||||
public bool IsWindowCloaked()
|
||||
{
|
||||
int isCloaked = 0;
|
||||
const int DWMWA_CLOAKED = 14;
|
||||
InteropAndHelpers.DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, out isCloaked, sizeof(int));
|
||||
return isCloaked != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether returns true if the window is minimized
|
||||
/// </summary>
|
||||
public bool Minimized
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetWindowSizeState() == WindowSizeState.Minimized;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Window"/> class.
|
||||
/// Initializes a new Window representation
|
||||
/// </summary>
|
||||
/// <param name="hwnd">the handle to the window we are representing</param>
|
||||
public Window(IntPtr hwnd)
|
||||
{
|
||||
// TODO: Add verification as to whether the window handle is valid
|
||||
this.hwnd = hwnd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Highlights a window to help the user identify the window that has been selected
|
||||
/// </summary>
|
||||
public void HighlightWindow()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switches desktop focus to the window
|
||||
/// </summary>
|
||||
public void SwitchToWindow()
|
||||
{
|
||||
// The following block is necessary because
|
||||
// 1) There is a weird flashing behavior when trying
|
||||
// to use ShowWindow for switching tabs in IE
|
||||
// 2) SetForegroundWindow fails on minimized windows
|
||||
if (ProcessName.ToLower().Equals("iexplore.exe") || !Minimized)
|
||||
{
|
||||
InteropAndHelpers.SetForegroundWindow(Hwnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
InteropAndHelpers.ShowWindow(Hwnd, InteropAndHelpers.ShowWindowCommands.Restore);
|
||||
}
|
||||
|
||||
InteropAndHelpers.FlashWindow(Hwnd, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the window name to string along with the process name
|
||||
/// </summary>
|
||||
/// <returns>The title of the window</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Title + " (" + ProcessName.ToUpper() + ")";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns what the window size is
|
||||
/// </summary>
|
||||
/// <returns>The state (minimized, maximized, etc..) of the window</returns>
|
||||
public WindowSizeState GetWindowSizeState()
|
||||
{
|
||||
InteropAndHelpers.GetWindowPlacement(Hwnd, out InteropAndHelpers.WINDOWPLACEMENT placement);
|
||||
|
||||
switch (placement.ShowCmd)
|
||||
{
|
||||
case InteropAndHelpers.ShowWindowCommands.Normal:
|
||||
return WindowSizeState.Normal;
|
||||
case InteropAndHelpers.ShowWindowCommands.Minimize:
|
||||
case InteropAndHelpers.ShowWindowCommands.ShowMinimized:
|
||||
return WindowSizeState.Minimized;
|
||||
case InteropAndHelpers.ShowWindowCommands.Maximize: // No need for ShowMaximized here since its also of value 3
|
||||
return WindowSizeState.Maximized;
|
||||
default:
|
||||
// throw new Exception("Don't know how to handle window state = " + placement.ShowCmd);
|
||||
return WindowSizeState.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to simplify the state of the window
|
||||
/// </summary>
|
||||
public enum WindowSizeState
|
||||
{
|
||||
Normal,
|
||||
Minimized,
|
||||
Maximized,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the process using the window handle
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the window</param>
|
||||
/// <returns>A string representing the process name or an empty string if the function fails</returns>
|
||||
private string GetProcessNameFromWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
uint processId = GetProcessIDFromWindowHandle(hwnd);
|
||||
ProcessID = processId;
|
||||
IntPtr processHandle = InteropAndHelpers.OpenProcess(InteropAndHelpers.ProcessAccessFlags.AllAccess, true, (int)processId);
|
||||
StringBuilder processName = new StringBuilder(MaximumFileNameLength);
|
||||
|
||||
if (InteropAndHelpers.GetProcessImageFileName(processHandle, processName, MaximumFileNameLength) != 0)
|
||||
{
|
||||
return processName.ToString().Split('\\').Reverse().ToArray()[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process ID for the Window handle
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle to the window</param>
|
||||
/// <returns>The process ID</returns>
|
||||
private uint GetProcessIDFromWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
InteropAndHelpers.GetWindowThreadProcessId(hwnd, out uint processId);
|
||||
return processId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
// 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.Plugin.WindowWalker.Components
|
||||
{
|
||||
internal class WindowResult : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of letters in between constant for when
|
||||
/// the result hasn't been set yet
|
||||
/// </summary>
|
||||
public const int NoResult = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets properties that signify how many characters (including spaces)
|
||||
/// were found when matching the results
|
||||
/// </summary>
|
||||
public int LettersInBetweenScore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowResult"/> class.
|
||||
/// Constructor for WindowResult
|
||||
/// </summary>
|
||||
public WindowResult(Window window)
|
||||
: base(window.Hwnd)
|
||||
{
|
||||
LettersInBetweenScore = NoResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.Plugin.WindowWalker.Components
|
||||
{
|
||||
internal class WindowResult : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of letters in between constant for when
|
||||
/// the result hasn't been set yet
|
||||
/// </summary>
|
||||
public const int NoResult = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets properties that signify how many characters (including spaces)
|
||||
/// were found when matching the results
|
||||
/// </summary>
|
||||
public int LettersInBetweenScore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowResult"/> class.
|
||||
/// Constructor for WindowResult
|
||||
/// </summary>
|
||||
public WindowResult(Window window)
|
||||
: base(window.Hwnd)
|
||||
{
|
||||
LettersInBetweenScore = NoResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.
|
||||
|
||||
// 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.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Plugin.WindowWalker.Components;
|
||||
@@ -12,9 +12,9 @@ namespace Microsoft.Plugin.WindowWalker
|
||||
public class Main : IPlugin, IPluginI18n
|
||||
{
|
||||
private static List<SearchResult> _results = new List<SearchResult>();
|
||||
|
||||
|
||||
private string IconPath { get; set; }
|
||||
|
||||
|
||||
private PluginInitContext Context { get; set; }
|
||||
|
||||
static Main()
|
||||
@@ -24,45 +24,45 @@ namespace Microsoft.Plugin.WindowWalker
|
||||
}
|
||||
|
||||
public List<Result> Query(Query query)
|
||||
{
|
||||
SearchController.Instance.UpdateSearchText(query.RawQuery).Wait();
|
||||
{
|
||||
SearchController.Instance.UpdateSearchText(query.RawQuery).Wait();
|
||||
OpenWindows.Instance.UpdateOpenWindowsList();
|
||||
return _results.Select(x => new Result()
|
||||
{
|
||||
Title = x.Result.Title,
|
||||
IcoPath = IconPath,
|
||||
SubTitle = "Running: " + x.Result.ProcessName,
|
||||
Action = c =>
|
||||
{
|
||||
x.Result.SwitchToWindow();
|
||||
return true;
|
||||
Action = c =>
|
||||
{
|
||||
x.Result.SwitchToWindow();
|
||||
return true;
|
||||
},
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
Context = context;
|
||||
Context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateIconPath(Context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
// Todo : Update with theme based IconPath
|
||||
private void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
IconPath = "Images/windowwalker.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
IconPath = "Images/windowwalker.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
{
|
||||
UpdateIconPath(newTheme);
|
||||
Context = context;
|
||||
Context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateIconPath(Context.API.GetCurrentTheme());
|
||||
}
|
||||
|
||||
// Todo : Update with theme based IconPath
|
||||
private void UpdateIconPath(Theme theme)
|
||||
{
|
||||
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
|
||||
{
|
||||
IconPath = "Images/windowwalker.light.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
IconPath = "Images/windowwalker.dark.png";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
{
|
||||
UpdateIconPath(newTheme);
|
||||
}
|
||||
|
||||
public string GetTranslatedPluginTitle()
|
||||
@@ -75,9 +75,9 @@ namespace Microsoft.Plugin.WindowWalker
|
||||
return Context.API.GetTranslation("wox_plugin_windowwalker_plugin_description");
|
||||
}
|
||||
|
||||
private static void SearchResultUpdated(object sender, SearchController.SearchResultUpdateEventArgs e)
|
||||
{
|
||||
_results = SearchController.Instance.SearchMatches;
|
||||
private static void SearchResultUpdated(object sender, SearchController.SearchResultUpdateEventArgs e)
|
||||
{
|
||||
_results = SearchController.Instance.SearchMatches;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class LauncherBootEvent : EventBase, IEvent
|
||||
{
|
||||
public double BootTimeMs { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
|
||||
}
|
||||
}
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class LauncherBootEvent : EventBase, IEvent
|
||||
{
|
||||
public double BootTimeMs { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class LauncherFirstDeleteEvent : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class LauncherFirstDeleteEvent : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class LauncherHideEvent : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class LauncherHideEvent : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
/// <summary>
|
||||
/// ETW Event for when the user initiates a query
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class LauncherQueryEvent : EventBase, IEvent
|
||||
{
|
||||
public double QueryTimeMs { get; set; }
|
||||
|
||||
public int QueryLength { get; set; }
|
||||
|
||||
public int NumResults { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
|
||||
}
|
||||
}
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
/// <summary>
|
||||
/// ETW Event for when the user initiates a query
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class LauncherQueryEvent : EventBase, IEvent
|
||||
{
|
||||
public double QueryTimeMs { get; set; }
|
||||
|
||||
public int QueryLength { get; set; }
|
||||
|
||||
public int NumResults { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
/// <summary>
|
||||
/// ETW event for when a result is actioned.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class LauncherResultActionEvent : EventBase, IEvent
|
||||
{
|
||||
public enum TriggerType
|
||||
{
|
||||
Click,
|
||||
KeyboardShortcut,
|
||||
}
|
||||
|
||||
public string Trigger { get; set; }
|
||||
|
||||
public string PluginName { get; set; }
|
||||
|
||||
public string ActionName { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
/// <summary>
|
||||
/// ETW event for when a result is actioned.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class LauncherResultActionEvent : EventBase, IEvent
|
||||
{
|
||||
public enum TriggerType
|
||||
{
|
||||
Click,
|
||||
KeyboardShortcut,
|
||||
}
|
||||
|
||||
public string Trigger { get; set; }
|
||||
|
||||
public string PluginName { get; set; }
|
||||
|
||||
public string ActionName { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class LauncherShowEvent : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.PowerLauncher.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class LauncherShowEvent : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,213 +1,213 @@
|
||||
// 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;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerLauncher.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using PowerLauncher.Helper;
|
||||
using PowerLauncher.ViewModel;
|
||||
using Wox;
|
||||
using Wox.Core.Plugin;
|
||||
using Wox.Core.Resource;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Http;
|
||||
using Wox.Infrastructure.Image;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
using Wox.Plugin;
|
||||
using Stopwatch = Wox.Infrastructure.Stopwatch;
|
||||
|
||||
namespace PowerLauncher
|
||||
{
|
||||
public partial class App : IDisposable, ISingleInstanceApp
|
||||
{
|
||||
public static PublicAPIInstance API { get; private set; }
|
||||
|
||||
private readonly Alphabet _alphabet = new Alphabet();
|
||||
|
||||
private const string Unique = "PowerLauncher_Unique_Application_Mutex";
|
||||
private static bool _disposed = false;
|
||||
private static int _powerToysPid;
|
||||
private Settings _settings;
|
||||
private MainViewModel _mainVM;
|
||||
private MainWindow _mainWindow;
|
||||
private ThemeManager _themeManager;
|
||||
private SettingWindowViewModel _settingsVM;
|
||||
private StringMatcher _stringMatcher;
|
||||
private SettingsWatcher _settingsWatcher;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (SingleInstance<App>.InitializeAsFirstInstance(Unique))
|
||||
{
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
_ = int.TryParse(args[0], out _powerToysPid);
|
||||
}
|
||||
|
||||
using (var application = new App())
|
||||
{
|
||||
application.InitializeComponent();
|
||||
application.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartup(object sender, StartupEventArgs e)
|
||||
{
|
||||
RunnerHelper.WaitForPowerToysRunner(_powerToysPid, () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
var bootTime = new System.Diagnostics.Stopwatch();
|
||||
bootTime.Start();
|
||||
Stopwatch.Normal("|App.OnStartup|Startup cost", () =>
|
||||
{
|
||||
Log.Info("|App.OnStartup|Begin PowerToys Run startup ----------------------------------------------------");
|
||||
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
|
||||
RegisterAppDomainExceptions();
|
||||
RegisterDispatcherUnhandledException();
|
||||
|
||||
_themeManager = new ThemeManager(this);
|
||||
ImageLoader.Initialize(_themeManager.GetCurrentTheme());
|
||||
|
||||
_settingsVM = new SettingWindowViewModel();
|
||||
_settings = _settingsVM.Settings;
|
||||
|
||||
_alphabet.Initialize(_settings);
|
||||
_stringMatcher = new StringMatcher(_alphabet);
|
||||
StringMatcher.Instance = _stringMatcher;
|
||||
_stringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision;
|
||||
|
||||
PluginManager.LoadPlugins(_settings.PluginSettings);
|
||||
_mainVM = new MainViewModel(_settings);
|
||||
_mainWindow = new MainWindow(_settings, _mainVM);
|
||||
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet, _themeManager);
|
||||
PluginManager.InitializePlugins(API);
|
||||
|
||||
Current.MainWindow = _mainWindow;
|
||||
Current.MainWindow.Title = Constant.ExeFileName;
|
||||
|
||||
// happlebao todo temp fix for instance code logic
|
||||
// load plugin before change language, because plugin language also needs be changed
|
||||
InternationalizationManager.Instance.Settings = _settings;
|
||||
InternationalizationManager.Instance.ChangeLanguage(_settings.Language);
|
||||
|
||||
// main windows needs initialized before theme change because of blur settings
|
||||
Http.Proxy = _settings.Proxy;
|
||||
|
||||
RegisterExitEvents();
|
||||
|
||||
_settingsWatcher = new SettingsWatcher(_settings);
|
||||
|
||||
_mainVM.MainWindowVisibility = Visibility.Visible;
|
||||
_mainVM.ColdStartFix();
|
||||
_themeManager.ThemeChanged += OnThemeChanged;
|
||||
Log.Info("|App.OnStartup|End PowerToys Run startup ---------------------------------------------------- ");
|
||||
|
||||
bootTime.Stop();
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new LauncherBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });
|
||||
|
||||
// [Conditional("RELEASE")]
|
||||
// check update every 5 hours
|
||||
|
||||
// check updates on startup
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterExitEvents()
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose();
|
||||
Current.Exit += (s, e) => Dispose();
|
||||
Current.SessionEnding += (s, e) => Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback when windows theme is changed.
|
||||
/// </summary>
|
||||
/// <param name="oldTheme">Previous Theme</param>
|
||||
/// <param name="newTheme">Current Theme</param>
|
||||
private void OnThemeChanged(Theme oldTheme, Theme newTheme)
|
||||
{
|
||||
ImageLoader.UpdateIconPath(newTheme);
|
||||
_mainVM.Query();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// let exception throw as normal is better for Debug
|
||||
/// </summary>
|
||||
[Conditional("RELEASE")]
|
||||
private void RegisterDispatcherUnhandledException()
|
||||
{
|
||||
DispatcherUnhandledException += ErrorReporting.DispatcherUnhandledException;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// let exception throw as normal is better for Debug
|
||||
/// </summary>
|
||||
[Conditional("RELEASE")]
|
||||
private static void RegisterAppDomainExceptions()
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
|
||||
}
|
||||
|
||||
public void OnSecondAppStarted()
|
||||
{
|
||||
Current.MainWindow.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Stopwatch.Normal("|App.OnExit|Exit cost", () =>
|
||||
{
|
||||
Log.Info("|App.OnExit| Start PowerToys Run Exit---------------------------------------------------- ");
|
||||
if (disposing)
|
||||
{
|
||||
_themeManager.ThemeChanged -= OnThemeChanged;
|
||||
API.SaveAppAllSettings();
|
||||
PluginManager.Dispose();
|
||||
_mainWindow.Dispose();
|
||||
API.Dispose();
|
||||
_mainVM.Dispose();
|
||||
_themeManager.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
_disposed = true;
|
||||
Log.Info("|App.OnExit| End PowerToys Run Exit ---------------------------------------------------- ");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
// ~App()
|
||||
// {
|
||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
// Dispose(disposing: false);
|
||||
// }
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerLauncher.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using PowerLauncher.Helper;
|
||||
using PowerLauncher.ViewModel;
|
||||
using Wox;
|
||||
using Wox.Core.Plugin;
|
||||
using Wox.Core.Resource;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Http;
|
||||
using Wox.Infrastructure.Image;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
using Wox.Plugin;
|
||||
using Stopwatch = Wox.Infrastructure.Stopwatch;
|
||||
|
||||
namespace PowerLauncher
|
||||
{
|
||||
public partial class App : IDisposable, ISingleInstanceApp
|
||||
{
|
||||
public static PublicAPIInstance API { get; private set; }
|
||||
|
||||
private readonly Alphabet _alphabet = new Alphabet();
|
||||
|
||||
private const string Unique = "PowerLauncher_Unique_Application_Mutex";
|
||||
private static bool _disposed = false;
|
||||
private static int _powerToysPid;
|
||||
private Settings _settings;
|
||||
private MainViewModel _mainVM;
|
||||
private MainWindow _mainWindow;
|
||||
private ThemeManager _themeManager;
|
||||
private SettingWindowViewModel _settingsVM;
|
||||
private StringMatcher _stringMatcher;
|
||||
private SettingsWatcher _settingsWatcher;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (SingleInstance<App>.InitializeAsFirstInstance(Unique))
|
||||
{
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
_ = int.TryParse(args[0], out _powerToysPid);
|
||||
}
|
||||
|
||||
using (var application = new App())
|
||||
{
|
||||
application.InitializeComponent();
|
||||
application.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartup(object sender, StartupEventArgs e)
|
||||
{
|
||||
RunnerHelper.WaitForPowerToysRunner(_powerToysPid, () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
var bootTime = new System.Diagnostics.Stopwatch();
|
||||
bootTime.Start();
|
||||
Stopwatch.Normal("|App.OnStartup|Startup cost", () =>
|
||||
{
|
||||
Log.Info("|App.OnStartup|Begin PowerToys Run startup ----------------------------------------------------");
|
||||
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
|
||||
RegisterAppDomainExceptions();
|
||||
RegisterDispatcherUnhandledException();
|
||||
|
||||
_themeManager = new ThemeManager(this);
|
||||
ImageLoader.Initialize(_themeManager.GetCurrentTheme());
|
||||
|
||||
_settingsVM = new SettingWindowViewModel();
|
||||
_settings = _settingsVM.Settings;
|
||||
|
||||
_alphabet.Initialize(_settings);
|
||||
_stringMatcher = new StringMatcher(_alphabet);
|
||||
StringMatcher.Instance = _stringMatcher;
|
||||
_stringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision;
|
||||
|
||||
PluginManager.LoadPlugins(_settings.PluginSettings);
|
||||
_mainVM = new MainViewModel(_settings);
|
||||
_mainWindow = new MainWindow(_settings, _mainVM);
|
||||
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet, _themeManager);
|
||||
PluginManager.InitializePlugins(API);
|
||||
|
||||
Current.MainWindow = _mainWindow;
|
||||
Current.MainWindow.Title = Constant.ExeFileName;
|
||||
|
||||
// happlebao todo temp fix for instance code logic
|
||||
// load plugin before change language, because plugin language also needs be changed
|
||||
InternationalizationManager.Instance.Settings = _settings;
|
||||
InternationalizationManager.Instance.ChangeLanguage(_settings.Language);
|
||||
|
||||
// main windows needs initialized before theme change because of blur settings
|
||||
Http.Proxy = _settings.Proxy;
|
||||
|
||||
RegisterExitEvents();
|
||||
|
||||
_settingsWatcher = new SettingsWatcher(_settings);
|
||||
|
||||
_mainVM.MainWindowVisibility = Visibility.Visible;
|
||||
_mainVM.ColdStartFix();
|
||||
_themeManager.ThemeChanged += OnThemeChanged;
|
||||
Log.Info("|App.OnStartup|End PowerToys Run startup ---------------------------------------------------- ");
|
||||
|
||||
bootTime.Stop();
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new LauncherBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });
|
||||
|
||||
// [Conditional("RELEASE")]
|
||||
// check update every 5 hours
|
||||
|
||||
// check updates on startup
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterExitEvents()
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose();
|
||||
Current.Exit += (s, e) => Dispose();
|
||||
Current.SessionEnding += (s, e) => Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback when windows theme is changed.
|
||||
/// </summary>
|
||||
/// <param name="oldTheme">Previous Theme</param>
|
||||
/// <param name="newTheme">Current Theme</param>
|
||||
private void OnThemeChanged(Theme oldTheme, Theme newTheme)
|
||||
{
|
||||
ImageLoader.UpdateIconPath(newTheme);
|
||||
_mainVM.Query();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// let exception throw as normal is better for Debug
|
||||
/// </summary>
|
||||
[Conditional("RELEASE")]
|
||||
private void RegisterDispatcherUnhandledException()
|
||||
{
|
||||
DispatcherUnhandledException += ErrorReporting.DispatcherUnhandledException;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// let exception throw as normal is better for Debug
|
||||
/// </summary>
|
||||
[Conditional("RELEASE")]
|
||||
private static void RegisterAppDomainExceptions()
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
|
||||
}
|
||||
|
||||
public void OnSecondAppStarted()
|
||||
{
|
||||
Current.MainWindow.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Stopwatch.Normal("|App.OnExit|Exit cost", () =>
|
||||
{
|
||||
Log.Info("|App.OnExit| Start PowerToys Run Exit---------------------------------------------------- ");
|
||||
if (disposing)
|
||||
{
|
||||
_themeManager.ThemeChanged -= OnThemeChanged;
|
||||
API.SaveAppAllSettings();
|
||||
PluginManager.Dispose();
|
||||
_mainWindow.Dispose();
|
||||
API.Dispose();
|
||||
_mainVM.Dispose();
|
||||
_themeManager.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
_disposed = true;
|
||||
Log.Info("|App.OnExit| End PowerToys Run Exit ---------------------------------------------------- ");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
// ~App()
|
||||
// {
|
||||
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
// Dispose(disposing: false);
|
||||
// }
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <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 PowerLauncher.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("https://aka.ms/powerToys")]
|
||||
public string GithubRepo {
|
||||
get {
|
||||
return ((string)(this["GithubRepo"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// <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 PowerLauncher.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("https://aka.ms/powerToys")]
|
||||
public string GithubRepo {
|
||||
get {
|
||||
return ((string)(this["GithubRepo"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,277 +1,277 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using PowerLauncher.Helper;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace PowerLauncher.ViewModel
|
||||
{
|
||||
public class ResultsViewModel : BaseModel
|
||||
{
|
||||
private readonly object _collectionLock = new object();
|
||||
|
||||
private readonly Settings _settings;
|
||||
|
||||
public ResultsViewModel()
|
||||
{
|
||||
Results = new ResultCollection();
|
||||
BindingOperations.EnableCollectionSynchronization(Results, _collectionLock);
|
||||
}
|
||||
|
||||
public ResultsViewModel(Settings settings)
|
||||
: this()
|
||||
{
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
_settings.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(_settings.MaxResultsToShow))
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(MaxHeight));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public int MaxHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.MaxResultsToShow * 75;
|
||||
}
|
||||
}
|
||||
|
||||
public int SelectedIndex { get; set; }
|
||||
|
||||
private ResultViewModel _selectedItem;
|
||||
|
||||
public ResultViewModel SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedItem;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
if (_selectedItem != null)
|
||||
{
|
||||
_selectedItem.DeactivateContextButtons(ResultViewModel.ActivationType.Selection);
|
||||
}
|
||||
|
||||
_selectedItem = value;
|
||||
_selectedItem.ActivateContextButtons(ResultViewModel.ActivationType.Selection);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedItem = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Thickness Margin { get; set; }
|
||||
|
||||
public Visibility Visibility { get; set; } = Visibility.Hidden;
|
||||
|
||||
public ResultCollection Results { get; }
|
||||
|
||||
private static int InsertIndexOf(int newScore, IList<ResultViewModel> list)
|
||||
{
|
||||
int index = 0;
|
||||
for (; index < list.Count; index++)
|
||||
{
|
||||
var result = list[index];
|
||||
if (newScore > result.Result.Score)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private int NewIndex(int i)
|
||||
{
|
||||
var n = Results.Count;
|
||||
if (n > 0)
|
||||
{
|
||||
i = (n + i) % n;
|
||||
return i;
|
||||
}
|
||||
else
|
||||
{
|
||||
// SelectedIndex returns -1 if selection is empty.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectNextResult()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex + 1);
|
||||
}
|
||||
|
||||
public void SelectPrevResult()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex - 1);
|
||||
}
|
||||
|
||||
public void SelectNextPage()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex + _settings.MaxResultsToShow);
|
||||
}
|
||||
|
||||
public void SelectPrevPage()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex - _settings.MaxResultsToShow);
|
||||
}
|
||||
|
||||
public void SelectFirstResult()
|
||||
{
|
||||
SelectedIndex = NewIndex(0);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Results.Clear();
|
||||
}
|
||||
|
||||
public void RemoveResultsExcept(PluginMetadata metadata)
|
||||
{
|
||||
Results.RemoveAll(r => r.Result.PluginID != metadata.ID);
|
||||
}
|
||||
|
||||
public void RemoveResultsFor(PluginMetadata metadata)
|
||||
{
|
||||
Results.RemoveAll(r => r.Result.PluginID == metadata.ID);
|
||||
}
|
||||
|
||||
public void SelectNextTabItem()
|
||||
{
|
||||
// Do nothing if there is no selected item or we've selected the next context button
|
||||
if (!SelectedItem?.SelectNextContextButton() ?? true)
|
||||
{
|
||||
SelectNextResult();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectPrevTabItem()
|
||||
{
|
||||
// Do nothing if there is no selected item or we've selected the previous context button
|
||||
if (!SelectedItem?.SelectPrevContextButton() ?? true)
|
||||
{
|
||||
// Tabbing backwards should highlight the last item of the previous row
|
||||
SelectPrevResult();
|
||||
SelectedItem.SelectLastContextButton();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectNextContextMenuItem()
|
||||
{
|
||||
if (SelectedItem != null)
|
||||
{
|
||||
if (!SelectedItem.SelectNextContextButton())
|
||||
{
|
||||
SelectedItem.SelectLastContextButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectPreviousContextMenuItem()
|
||||
{
|
||||
if (SelectedItem != null)
|
||||
{
|
||||
SelectedItem.SelectPrevContextButton();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsContextMenuItemSelected()
|
||||
{
|
||||
if (SelectedItem != null && SelectedItem.ContextMenuSelectedIndex != ResultViewModel.NoSelectionIndex)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add new results to ResultCollection
|
||||
/// </summary>
|
||||
public void AddResults(List<Result> newRawResults, CancellationToken ct)
|
||||
{
|
||||
if (newRawResults == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newRawResults));
|
||||
}
|
||||
|
||||
List<ResultViewModel> newResults = new List<ResultViewModel>(newRawResults.Count);
|
||||
foreach (Result r in newRawResults)
|
||||
{
|
||||
newResults.Add(new ResultViewModel(r));
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
Results.AddRange(newResults);
|
||||
}
|
||||
|
||||
public void Sort()
|
||||
{
|
||||
var sorted = Results.OrderByDescending(x => x.Result.Score).ToList();
|
||||
Clear();
|
||||
Results.AddRange(sorted);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
|
||||
"FormattedText",
|
||||
typeof(Inline),
|
||||
typeof(ResultsViewModel),
|
||||
new PropertyMetadata(null, FormattedTextPropertyChanged));
|
||||
|
||||
public static void SetFormattedText(DependencyObject textBlock, IList<int> value)
|
||||
{
|
||||
if (textBlock != null)
|
||||
{
|
||||
textBlock.SetValue(FormattedTextProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static Inline GetFormattedText(DependencyObject textBlock)
|
||||
{
|
||||
return (Inline)textBlock?.GetValue(FormattedTextProperty);
|
||||
}
|
||||
|
||||
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var textBlock = d as TextBlock;
|
||||
if (textBlock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var inline = (Inline)e.NewValue;
|
||||
|
||||
textBlock.Inlines.Clear();
|
||||
if (inline == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
textBlock.Inlines.Add(inline);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using PowerLauncher.Helper;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace PowerLauncher.ViewModel
|
||||
{
|
||||
public class ResultsViewModel : BaseModel
|
||||
{
|
||||
private readonly object _collectionLock = new object();
|
||||
|
||||
private readonly Settings _settings;
|
||||
|
||||
public ResultsViewModel()
|
||||
{
|
||||
Results = new ResultCollection();
|
||||
BindingOperations.EnableCollectionSynchronization(Results, _collectionLock);
|
||||
}
|
||||
|
||||
public ResultsViewModel(Settings settings)
|
||||
: this()
|
||||
{
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
_settings.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(_settings.MaxResultsToShow))
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(MaxHeight));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public int MaxHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.MaxResultsToShow * 75;
|
||||
}
|
||||
}
|
||||
|
||||
public int SelectedIndex { get; set; }
|
||||
|
||||
private ResultViewModel _selectedItem;
|
||||
|
||||
public ResultViewModel SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedItem;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
if (_selectedItem != null)
|
||||
{
|
||||
_selectedItem.DeactivateContextButtons(ResultViewModel.ActivationType.Selection);
|
||||
}
|
||||
|
||||
_selectedItem = value;
|
||||
_selectedItem.ActivateContextButtons(ResultViewModel.ActivationType.Selection);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedItem = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Thickness Margin { get; set; }
|
||||
|
||||
public Visibility Visibility { get; set; } = Visibility.Hidden;
|
||||
|
||||
public ResultCollection Results { get; }
|
||||
|
||||
private static int InsertIndexOf(int newScore, IList<ResultViewModel> list)
|
||||
{
|
||||
int index = 0;
|
||||
for (; index < list.Count; index++)
|
||||
{
|
||||
var result = list[index];
|
||||
if (newScore > result.Result.Score)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private int NewIndex(int i)
|
||||
{
|
||||
var n = Results.Count;
|
||||
if (n > 0)
|
||||
{
|
||||
i = (n + i) % n;
|
||||
return i;
|
||||
}
|
||||
else
|
||||
{
|
||||
// SelectedIndex returns -1 if selection is empty.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectNextResult()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex + 1);
|
||||
}
|
||||
|
||||
public void SelectPrevResult()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex - 1);
|
||||
}
|
||||
|
||||
public void SelectNextPage()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex + _settings.MaxResultsToShow);
|
||||
}
|
||||
|
||||
public void SelectPrevPage()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex - _settings.MaxResultsToShow);
|
||||
}
|
||||
|
||||
public void SelectFirstResult()
|
||||
{
|
||||
SelectedIndex = NewIndex(0);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Results.Clear();
|
||||
}
|
||||
|
||||
public void RemoveResultsExcept(PluginMetadata metadata)
|
||||
{
|
||||
Results.RemoveAll(r => r.Result.PluginID != metadata.ID);
|
||||
}
|
||||
|
||||
public void RemoveResultsFor(PluginMetadata metadata)
|
||||
{
|
||||
Results.RemoveAll(r => r.Result.PluginID == metadata.ID);
|
||||
}
|
||||
|
||||
public void SelectNextTabItem()
|
||||
{
|
||||
// Do nothing if there is no selected item or we've selected the next context button
|
||||
if (!SelectedItem?.SelectNextContextButton() ?? true)
|
||||
{
|
||||
SelectNextResult();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectPrevTabItem()
|
||||
{
|
||||
// Do nothing if there is no selected item or we've selected the previous context button
|
||||
if (!SelectedItem?.SelectPrevContextButton() ?? true)
|
||||
{
|
||||
// Tabbing backwards should highlight the last item of the previous row
|
||||
SelectPrevResult();
|
||||
SelectedItem.SelectLastContextButton();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectNextContextMenuItem()
|
||||
{
|
||||
if (SelectedItem != null)
|
||||
{
|
||||
if (!SelectedItem.SelectNextContextButton())
|
||||
{
|
||||
SelectedItem.SelectLastContextButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectPreviousContextMenuItem()
|
||||
{
|
||||
if (SelectedItem != null)
|
||||
{
|
||||
SelectedItem.SelectPrevContextButton();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsContextMenuItemSelected()
|
||||
{
|
||||
if (SelectedItem != null && SelectedItem.ContextMenuSelectedIndex != ResultViewModel.NoSelectionIndex)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add new results to ResultCollection
|
||||
/// </summary>
|
||||
public void AddResults(List<Result> newRawResults, CancellationToken ct)
|
||||
{
|
||||
if (newRawResults == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newRawResults));
|
||||
}
|
||||
|
||||
List<ResultViewModel> newResults = new List<ResultViewModel>(newRawResults.Count);
|
||||
foreach (Result r in newRawResults)
|
||||
{
|
||||
newResults.Add(new ResultViewModel(r));
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
Results.AddRange(newResults);
|
||||
}
|
||||
|
||||
public void Sort()
|
||||
{
|
||||
var sorted = Results.OrderByDescending(x => x.Result.Score).ToList();
|
||||
Clear();
|
||||
Results.AddRange(sorted);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
|
||||
"FormattedText",
|
||||
typeof(Inline),
|
||||
typeof(ResultsViewModel),
|
||||
new PropertyMetadata(null, FormattedTextPropertyChanged));
|
||||
|
||||
public static void SetFormattedText(DependencyObject textBlock, IList<int> value)
|
||||
{
|
||||
if (textBlock != null)
|
||||
{
|
||||
textBlock.SetValue(FormattedTextProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static Inline GetFormattedText(DependencyObject textBlock)
|
||||
{
|
||||
return (Inline)textBlock?.GetValue(FormattedTextProperty);
|
||||
}
|
||||
|
||||
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var textBlock = d as TextBlock;
|
||||
if (textBlock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var inline = (Inline)e.NewValue;
|
||||
|
||||
textBlock.Inlines.Clear();
|
||||
if (inline == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
textBlock.Inlines.Add(inline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.Plugin.Program.UnitTests")]
|
||||
|
||||
|
||||
namespace Wox.Infrastructure
|
||||
{
|
||||
public class StringMatcher
|
||||
@@ -61,11 +61,11 @@ namespace Wox.Infrastructure
|
||||
/// </summary>
|
||||
public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query))
|
||||
{
|
||||
return new MatchResult(false, UserSettingSearchPrecision);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query))
|
||||
{
|
||||
return new MatchResult(false, UserSettingSearchPrecision);
|
||||
}
|
||||
|
||||
query = query.Trim();
|
||||
|
||||
if (_alphabet != null)
|
||||
@@ -76,8 +76,8 @@ namespace Wox.Infrastructure
|
||||
|
||||
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
|
||||
|
||||
var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
|
||||
|
||||
var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
|
||||
|
||||
var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
int currentQuerySubstringIndex = 0;
|
||||
var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||
@@ -151,18 +151,18 @@ namespace Wox.Infrastructure
|
||||
currentQuerySubstringIndex++;
|
||||
|
||||
allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
|
||||
if (allQuerySubstringsMatched)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise move to the next query substring
|
||||
if (allQuerySubstringsMatched)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise move to the next query substring
|
||||
currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||
currentQuerySubstringCharacterIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// proceed to calculate score if every char or substring without whitespaces matched
|
||||
}
|
||||
|
||||
// proceed to calculate score if every char or substring without whitespaces matched
|
||||
if (allQuerySubstringsMatched)
|
||||
{
|
||||
var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex);
|
||||
@@ -202,8 +202,8 @@ namespace Wox.Infrastructure
|
||||
}
|
||||
|
||||
return allMatch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List<int> indexList)
|
||||
{
|
||||
var updatedList = new List<int>();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
@@ -15,22 +15,22 @@ namespace Wox.Infrastructure.UserSettings
|
||||
{
|
||||
private string _hotkey = "Alt + Space";
|
||||
private string _previousHotkey = string.Empty;
|
||||
|
||||
public string PreviousHotkey
|
||||
|
||||
public string PreviousHotkey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _previousHotkey;
|
||||
}
|
||||
}
|
||||
|
||||
public string Hotkey
|
||||
|
||||
public string Hotkey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hotkey;
|
||||
}
|
||||
|
||||
|
||||
set
|
||||
{
|
||||
if (_hotkey != value)
|
||||
@@ -41,27 +41,27 @@ namespace Wox.Infrastructure.UserSettings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string Language { get; set; } = "en";
|
||||
|
||||
|
||||
public string Theme { get; set; } = "Dark";
|
||||
|
||||
|
||||
public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name;
|
||||
|
||||
|
||||
public string QueryBoxFontStyle { get; set; }
|
||||
|
||||
|
||||
public string QueryBoxFontWeight { get; set; }
|
||||
|
||||
|
||||
public string QueryBoxFontStretch { get; set; }
|
||||
|
||||
|
||||
public string ResultFont { get; set; } = FontFamily.GenericSansSerif.Name;
|
||||
|
||||
|
||||
public string ResultFontStyle { get; set; }
|
||||
|
||||
|
||||
public string ResultFontWeight { get; set; }
|
||||
|
||||
|
||||
public string ResultFontStretch { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether when false Alphabet static service will always return empty results
|
||||
/// </summary>
|
||||
@@ -72,13 +72,13 @@ namespace Wox.Infrastructure.UserSettings
|
||||
[JsonIgnore]
|
||||
public string QuerySearchPrecisionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return QuerySearchPrecision.ToString();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
get
|
||||
{
|
||||
return QuerySearchPrecision.ToString();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
try
|
||||
{
|
||||
var precisionScore = (StringMatcher.SearchPrecisionScore)Enum
|
||||
@@ -95,41 +95,41 @@ namespace Wox.Infrastructure.UserSettings
|
||||
StringMatcher.Instance.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutoUpdates { get; set; } = false;
|
||||
|
||||
public double WindowLeft { get; set; }
|
||||
|
||||
|
||||
public double WindowTop { get; set; }
|
||||
|
||||
private int _maxResultsToShow = 4;
|
||||
|
||||
public int MaxResultsToShow
|
||||
{
|
||||
get
|
||||
{
|
||||
return _maxResultsToShow;
|
||||
}
|
||||
|
||||
set
|
||||
|
||||
public int MaxResultsToShow
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_maxResultsToShow != value)
|
||||
{
|
||||
_maxResultsToShow = value;
|
||||
OnPropertyChanged();
|
||||
return _maxResultsToShow;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_maxResultsToShow != value)
|
||||
{
|
||||
_maxResultsToShow = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int ActivateTimes { get; set; }
|
||||
|
||||
// Order defaults to 0 or -1, so 1 will let this property appear last
|
||||
[JsonProperty(Order = 1)]
|
||||
public PluginSettings PluginSettings { get; set; } = new PluginSettings();
|
||||
|
||||
|
||||
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
|
||||
|
||||
[Obsolete]
|
||||
@@ -139,37 +139,37 @@ namespace Wox.Infrastructure.UserSettings
|
||||
public OpacityMode OpacityMode { get; set; } = OpacityMode.Normal;
|
||||
|
||||
public bool DontPromptUpdateMsg { get; set; }
|
||||
|
||||
|
||||
public bool EnableUpdateLog { get; set; }
|
||||
|
||||
public bool StartWoxOnSystemStartup { get; set; } = true;
|
||||
|
||||
|
||||
public bool HideOnStartup { get; set; }
|
||||
|
||||
|
||||
private bool _hideNotifyIcon;
|
||||
|
||||
|
||||
public bool HideNotifyIcon
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hideNotifyIcon;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_hideNotifyIcon = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
get
|
||||
{
|
||||
return _hideNotifyIcon;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_hideNotifyIcon = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool LeaveCmdOpen { get; set; }
|
||||
|
||||
|
||||
public bool HideWhenDeactivated { get; set; } = true;
|
||||
|
||||
|
||||
public bool ClearInputOnLaunch { get; set; } = false;
|
||||
|
||||
public bool RememberLastLaunchLocation { get; set; }
|
||||
|
||||
|
||||
public bool IgnoreHotkeysOnFullscreen { get; set; }
|
||||
|
||||
public HttpProxy Proxy { get; set; } = new HttpProxy();
|
||||
@@ -192,4 +192,4 @@ namespace Wox.Infrastructure.UserSettings
|
||||
LayeredWindow = 1,
|
||||
DWM = 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Wox.Plugin
|
||||
{
|
||||
@@ -15,8 +15,8 @@ namespace Wox.Plugin
|
||||
|
||||
public string Glyph { get; set; }
|
||||
|
||||
public string FontFamily { get; set; }
|
||||
|
||||
public string FontFamily { get; set; }
|
||||
|
||||
public Key AcceleratorKey { get; set; }
|
||||
|
||||
public ModifierKeys AcceleratorModifiers { get; set; }
|
||||
@@ -31,4 +31,4 @@ namespace Wox.Plugin
|
||||
return Title;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// 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.Lib;
|
||||
|
||||
namespace Wox.Plugin
|
||||
{
|
||||
public interface ISettingProvider
|
||||
{
|
||||
Control CreateSettingPanel();
|
||||
|
||||
void UpdateSettings(PowerLauncherSettings settings);
|
||||
}
|
||||
}
|
||||
// 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.Lib;
|
||||
|
||||
namespace Wox.Plugin
|
||||
{
|
||||
public interface ISettingProvider
|
||||
{
|
||||
Control CreateSettingPanel();
|
||||
|
||||
void UpdateSettings(PowerLauncherSettings settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,165 +1,165 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Wox.Plugin
|
||||
{
|
||||
public class Result
|
||||
{
|
||||
private string _title;
|
||||
private ToolTipData _toolTipData;
|
||||
private string _pluginDirectory;
|
||||
private string _icoPath;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return _title;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_title = value.Replace("\n", " ");
|
||||
}
|
||||
}
|
||||
|
||||
public string SubTitle { get; set; }
|
||||
|
||||
public string Glyph { get; set; }
|
||||
|
||||
public string FontFamily { get; set; }
|
||||
|
||||
public Visibility ToolTipVisibility { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public ToolTipData ToolTipData
|
||||
{
|
||||
get
|
||||
{
|
||||
return _toolTipData;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_toolTipData = value;
|
||||
ToolTipVisibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text that will get displayed in the Search text box, when this item is selected in the result list.
|
||||
/// </summary>
|
||||
public string QueryTextDisplay { get; set; }
|
||||
|
||||
public string IcoPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return _icoPath;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (!string.IsNullOrEmpty(PluginDirectory) && !Path.IsPathRooted(value))
|
||||
{
|
||||
_icoPath = Path.Combine(value, IcoPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_icoPath = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public delegate ImageSource IconDelegate();
|
||||
|
||||
public IconDelegate Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets return true to hide wox after select result
|
||||
/// </summary>
|
||||
public Func<ActionContext, bool> Action { get; set; }
|
||||
|
||||
public int Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of indexes for the characters to be highlighted in Title
|
||||
/// </summary>
|
||||
public IList<int> TitleHighlightData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of indexes for the characters to be highlighted in SubTitle
|
||||
/// </summary>
|
||||
public IList<int> SubTitleHighlightData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets only results that originQuery match with current query will be displayed in the panel
|
||||
/// </summary>
|
||||
internal Query OriginQuery { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets plugin directory
|
||||
/// </summary>
|
||||
public string PluginDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return _pluginDirectory;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_pluginDirectory = value;
|
||||
if (!string.IsNullOrEmpty(IcoPath) && !Path.IsPathRooted(IcoPath))
|
||||
{
|
||||
IcoPath = Path.Combine(value, IcoPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var r = obj as Result;
|
||||
|
||||
var equality = string.Equals(r?.Title, Title) &&
|
||||
string.Equals(r?.SubTitle, SubTitle) &&
|
||||
string.Equals(r?.IcoPath, IcoPath) &&
|
||||
TitleHighlightData == r.TitleHighlightData &&
|
||||
SubTitleHighlightData == r.SubTitleHighlightData;
|
||||
|
||||
return equality;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashcode = (Title?.GetHashCode() ?? 0) ^
|
||||
(SubTitle?.GetHashCode() ?? 0);
|
||||
return hashcode;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Title + SubTitle;
|
||||
}
|
||||
|
||||
public Result()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional data associate with this result
|
||||
/// </summary>
|
||||
public object ContextData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets plugin ID that generated this result
|
||||
/// </summary>
|
||||
public string PluginID { get; internal set; }
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Wox.Plugin
|
||||
{
|
||||
public class Result
|
||||
{
|
||||
private string _title;
|
||||
private ToolTipData _toolTipData;
|
||||
private string _pluginDirectory;
|
||||
private string _icoPath;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return _title;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_title = value.Replace("\n", " ");
|
||||
}
|
||||
}
|
||||
|
||||
public string SubTitle { get; set; }
|
||||
|
||||
public string Glyph { get; set; }
|
||||
|
||||
public string FontFamily { get; set; }
|
||||
|
||||
public Visibility ToolTipVisibility { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public ToolTipData ToolTipData
|
||||
{
|
||||
get
|
||||
{
|
||||
return _toolTipData;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_toolTipData = value;
|
||||
ToolTipVisibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text that will get displayed in the Search text box, when this item is selected in the result list.
|
||||
/// </summary>
|
||||
public string QueryTextDisplay { get; set; }
|
||||
|
||||
public string IcoPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return _icoPath;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (!string.IsNullOrEmpty(PluginDirectory) && !Path.IsPathRooted(value))
|
||||
{
|
||||
_icoPath = Path.Combine(value, IcoPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_icoPath = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public delegate ImageSource IconDelegate();
|
||||
|
||||
public IconDelegate Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets return true to hide wox after select result
|
||||
/// </summary>
|
||||
public Func<ActionContext, bool> Action { get; set; }
|
||||
|
||||
public int Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of indexes for the characters to be highlighted in Title
|
||||
/// </summary>
|
||||
public IList<int> TitleHighlightData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of indexes for the characters to be highlighted in SubTitle
|
||||
/// </summary>
|
||||
public IList<int> SubTitleHighlightData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets only results that originQuery match with current query will be displayed in the panel
|
||||
/// </summary>
|
||||
internal Query OriginQuery { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets plugin directory
|
||||
/// </summary>
|
||||
public string PluginDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return _pluginDirectory;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_pluginDirectory = value;
|
||||
if (!string.IsNullOrEmpty(IcoPath) && !Path.IsPathRooted(IcoPath))
|
||||
{
|
||||
IcoPath = Path.Combine(value, IcoPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var r = obj as Result;
|
||||
|
||||
var equality = string.Equals(r?.Title, Title) &&
|
||||
string.Equals(r?.SubTitle, SubTitle) &&
|
||||
string.Equals(r?.IcoPath, IcoPath) &&
|
||||
TitleHighlightData == r.TitleHighlightData &&
|
||||
SubTitleHighlightData == r.SubTitleHighlightData;
|
||||
|
||||
return equality;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashcode = (Title?.GetHashCode() ?? 0) ^
|
||||
(SubTitle?.GetHashCode() ?? 0);
|
||||
return hashcode;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Title + SubTitle;
|
||||
}
|
||||
|
||||
public Result()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional data associate with this result
|
||||
/// </summary>
|
||||
public object ContextData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets plugin ID that generated this result
|
||||
/// </summary>
|
||||
public string PluginID { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace MarkdownPreviewHandler.Telemetry.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// A telemetry event that is triggered when a markdown file is viewed in the preview pane.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class MarkdownFileHandlerLoaded : EventBase, IEvent
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace MarkdownPreviewHandler.Telemetry.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// A telemetry event that is triggered when a markdown file is viewed in the preview pane.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class MarkdownFileHandlerLoaded : EventBase, IEvent
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +1,115 @@
|
||||
// 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.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using MarkdownPreviewHandler;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PreviewHandlerCommon;
|
||||
|
||||
namespace PreviewPaneUnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class MarkdownPreviewHandlerTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__AddsBrowserToForm_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 2);
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__AddsInfoBarToFormIfExternalImageLinkPresent_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 2);
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__AddsInfoBarToFormIfHTMLImageTagIsPresent_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithHTMLImageTag.txt");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 2);
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__DoesNotAddInfoBarToFormIfExternalImageLinkNotPresent_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithScript.txt");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 1);
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__UpdatesWebBrowserSettings_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
|
||||
|
||||
// Assert
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt));
|
||||
Assert.IsNotNull(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).DocumentText);
|
||||
Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).Dock, DockStyle.Fill);
|
||||
Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).IsWebBrowserContextMenuEnabled, false);
|
||||
Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).ScriptErrorsSuppressed, true);
|
||||
Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).ScrollBarsEnabled, true);
|
||||
Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).AllowNavigation, false);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__UpdateInfobarSettings_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
|
||||
|
||||
// Assert
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox));
|
||||
Assert.IsNotNull(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Text);
|
||||
Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Dock, DockStyle.Top);
|
||||
Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).BorderStyle, BorderStyle.None);
|
||||
Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).BackColor, Color.LightYellow);
|
||||
Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Multiline, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using MarkdownPreviewHandler;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using PreviewHandlerCommon;
|
||||
|
||||
namespace PreviewPaneUnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class MarkdownPreviewHandlerTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__AddsBrowserToForm_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 2);
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__AddsInfoBarToFormIfExternalImageLinkPresent_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 2);
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__AddsInfoBarToFormIfHTMLImageTagIsPresent_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithHTMLImageTag.txt");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 2);
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__DoesNotAddInfoBarToFormIfExternalImageLinkNotPresent_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithScript.txt");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 1);
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__UpdatesWebBrowserSettings_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
|
||||
|
||||
// Assert
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt));
|
||||
Assert.IsNotNull(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).DocumentText);
|
||||
Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).Dock, DockStyle.Fill);
|
||||
Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).IsWebBrowserContextMenuEnabled, false);
|
||||
Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).ScriptErrorsSuppressed, true);
|
||||
Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).ScrollBarsEnabled, true);
|
||||
Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).AllowNavigation, false);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MarkdownPreviewHandlerControl__UpdateInfobarSettings_WhenDoPreviewIsCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var markdownPreviewHandlerControl = new MarkdownPreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
markdownPreviewHandlerControl.DoPreview<string>("HelperFiles/MarkdownWithExternalImage.txt");
|
||||
|
||||
// Assert
|
||||
Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox));
|
||||
Assert.IsNotNull(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Text);
|
||||
Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Dock, DockStyle.Top);
|
||||
Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).BorderStyle, BorderStyle.None);
|
||||
Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).BackColor, Color.LightYellow);
|
||||
Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Multiline, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <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 SvgPreviewHandler {
|
||||
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", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resource {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resource() {
|
||||
}
|
||||
|
||||
/// <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("SvgPreviewHandler.Resource", typeof(Resource).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 Some elements have been blocked to help prevent the sender from identifying your computer. Open this item to view all elements..
|
||||
/// </summary>
|
||||
internal static string BlockedElementInfoText {
|
||||
get {
|
||||
return ResourceManager.GetString("BlockedElementInfoText", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The Svg could not be preview due to an internal error in Svg Preview Handler..
|
||||
/// </summary>
|
||||
internal static string SvgNotPreviewedError {
|
||||
get {
|
||||
return ResourceManager.GetString("SvgNotPreviewedError", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// <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 SvgPreviewHandler {
|
||||
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", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resource {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resource() {
|
||||
}
|
||||
|
||||
/// <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("SvgPreviewHandler.Resource", typeof(Resource).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 Some elements have been blocked to help prevent the sender from identifying your computer. Open this item to view all elements..
|
||||
/// </summary>
|
||||
internal static string BlockedElementInfoText {
|
||||
get {
|
||||
return ResourceManager.GetString("BlockedElementInfoText", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The Svg could not be preview due to an internal error in Svg Preview Handler..
|
||||
/// </summary>
|
||||
internal static string SvgNotPreviewedError {
|
||||
get {
|
||||
return ResourceManager.GetString("SvgNotPreviewedError", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +1,144 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Common;
|
||||
using Common.Utilities;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using PreviewHandlerCommon;
|
||||
using SvgPreviewHandler.Telemetry.Events;
|
||||
|
||||
namespace SvgPreviewHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of Control for Svg Preview Handler.
|
||||
/// </summary>
|
||||
public class SvgPreviewControl : FormHandlerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Extended Browser Control to display Svg.
|
||||
/// </summary>
|
||||
private WebBrowserExt browser;
|
||||
|
||||
/// <summary>
|
||||
/// Text box to display the information about blocked elements from Svg.
|
||||
/// </summary>
|
||||
private RichTextBox textBox;
|
||||
|
||||
/// <summary>
|
||||
/// Represent if an text box info bar is added for showing message.
|
||||
/// </summary>
|
||||
private bool infoBarAdded = false;
|
||||
|
||||
/// <summary>
|
||||
/// Start the preview on the Control.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Stream reference to access source file.</param>
|
||||
public override void DoPreview<T>(T dataSource)
|
||||
{
|
||||
this.InvokeOnControlThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
this.infoBarAdded = false;
|
||||
string svgData = null;
|
||||
using (var stream = new StreamWrapper(dataSource as IStream))
|
||||
{
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
svgData = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// Add a info bar on top of the Preview if any blocked element is present.
|
||||
if (SvgPreviewHandlerHelper.CheckBlockedElements(svgData))
|
||||
{
|
||||
this.infoBarAdded = true;
|
||||
this.AddTextBoxControl(Resource.BlockedElementInfoText);
|
||||
}
|
||||
|
||||
this.AddBrowserControl(svgData);
|
||||
this.Resize += this.FormResized;
|
||||
base.DoPreview(dataSource);
|
||||
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewed());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = ex.Message });
|
||||
this.Controls.Clear();
|
||||
this.infoBarAdded = true;
|
||||
this.AddTextBoxControl(Resource.SvgNotPreviewedError);
|
||||
base.DoPreview(dataSource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when RichtextBox is resized.
|
||||
/// </summary>
|
||||
/// <param name="sender">Reference to resized control.</param>
|
||||
/// <param name="e">Provides data for the ContentsResized event.</param>
|
||||
private void RTBContentsResized(object sender, ContentsResizedEventArgs e)
|
||||
{
|
||||
var richTextBox = sender as RichTextBox;
|
||||
richTextBox.Height = e.NewRectangle.Height + 5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when form is resized.
|
||||
/// </summary>
|
||||
/// <param name="sender">Reference to resized control.</param>
|
||||
/// <param name="e">Provides data for the resize event.</param>
|
||||
private void FormResized(object sender, EventArgs e)
|
||||
{
|
||||
if (this.infoBarAdded)
|
||||
{
|
||||
this.textBox.Width = this.Width;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Web Browser Control to Control Collection.
|
||||
/// </summary>
|
||||
/// <param name="svgData">Svg to display on Browser Control.</param>
|
||||
private void AddBrowserControl(string svgData)
|
||||
{
|
||||
this.browser = new WebBrowserExt();
|
||||
this.browser.DocumentText = svgData;
|
||||
this.browser.Dock = DockStyle.Fill;
|
||||
this.browser.IsWebBrowserContextMenuEnabled = false;
|
||||
this.browser.ScriptErrorsSuppressed = true;
|
||||
this.browser.ScrollBarsEnabled = true;
|
||||
this.browser.AllowNavigation = false;
|
||||
this.Controls.Add(this.browser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Text Box in Controls for showing information about blocked elements.
|
||||
/// </summary>
|
||||
/// <param name="message">Message to be displayed in textbox.</param>
|
||||
private void AddTextBoxControl(string message)
|
||||
{
|
||||
this.textBox = new RichTextBox();
|
||||
this.textBox.Text = message;
|
||||
this.textBox.BackColor = Color.LightYellow;
|
||||
this.textBox.Multiline = true;
|
||||
this.textBox.Dock = DockStyle.Top;
|
||||
this.textBox.ReadOnly = true;
|
||||
this.textBox.ContentsResized += this.RTBContentsResized;
|
||||
this.textBox.ScrollBars = RichTextBoxScrollBars.None;
|
||||
this.textBox.BorderStyle = BorderStyle.None;
|
||||
this.Controls.Add(this.textBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Common;
|
||||
using Common.Utilities;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using PreviewHandlerCommon;
|
||||
using SvgPreviewHandler.Telemetry.Events;
|
||||
|
||||
namespace SvgPreviewHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of Control for Svg Preview Handler.
|
||||
/// </summary>
|
||||
public class SvgPreviewControl : FormHandlerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Extended Browser Control to display Svg.
|
||||
/// </summary>
|
||||
private WebBrowserExt browser;
|
||||
|
||||
/// <summary>
|
||||
/// Text box to display the information about blocked elements from Svg.
|
||||
/// </summary>
|
||||
private RichTextBox textBox;
|
||||
|
||||
/// <summary>
|
||||
/// Represent if an text box info bar is added for showing message.
|
||||
/// </summary>
|
||||
private bool infoBarAdded = false;
|
||||
|
||||
/// <summary>
|
||||
/// Start the preview on the Control.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Stream reference to access source file.</param>
|
||||
public override void DoPreview<T>(T dataSource)
|
||||
{
|
||||
this.InvokeOnControlThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
this.infoBarAdded = false;
|
||||
string svgData = null;
|
||||
using (var stream = new StreamWrapper(dataSource as IStream))
|
||||
{
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
svgData = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// Add a info bar on top of the Preview if any blocked element is present.
|
||||
if (SvgPreviewHandlerHelper.CheckBlockedElements(svgData))
|
||||
{
|
||||
this.infoBarAdded = true;
|
||||
this.AddTextBoxControl(Resource.BlockedElementInfoText);
|
||||
}
|
||||
|
||||
this.AddBrowserControl(svgData);
|
||||
this.Resize += this.FormResized;
|
||||
base.DoPreview(dataSource);
|
||||
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewed());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = ex.Message });
|
||||
this.Controls.Clear();
|
||||
this.infoBarAdded = true;
|
||||
this.AddTextBoxControl(Resource.SvgNotPreviewedError);
|
||||
base.DoPreview(dataSource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when RichtextBox is resized.
|
||||
/// </summary>
|
||||
/// <param name="sender">Reference to resized control.</param>
|
||||
/// <param name="e">Provides data for the ContentsResized event.</param>
|
||||
private void RTBContentsResized(object sender, ContentsResizedEventArgs e)
|
||||
{
|
||||
var richTextBox = sender as RichTextBox;
|
||||
richTextBox.Height = e.NewRectangle.Height + 5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when form is resized.
|
||||
/// </summary>
|
||||
/// <param name="sender">Reference to resized control.</param>
|
||||
/// <param name="e">Provides data for the resize event.</param>
|
||||
private void FormResized(object sender, EventArgs e)
|
||||
{
|
||||
if (this.infoBarAdded)
|
||||
{
|
||||
this.textBox.Width = this.Width;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Web Browser Control to Control Collection.
|
||||
/// </summary>
|
||||
/// <param name="svgData">Svg to display on Browser Control.</param>
|
||||
private void AddBrowserControl(string svgData)
|
||||
{
|
||||
this.browser = new WebBrowserExt();
|
||||
this.browser.DocumentText = svgData;
|
||||
this.browser.Dock = DockStyle.Fill;
|
||||
this.browser.IsWebBrowserContextMenuEnabled = false;
|
||||
this.browser.ScriptErrorsSuppressed = true;
|
||||
this.browser.ScrollBarsEnabled = true;
|
||||
this.browser.AllowNavigation = false;
|
||||
this.Controls.Add(this.browser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Text Box in Controls for showing information about blocked elements.
|
||||
/// </summary>
|
||||
/// <param name="message">Message to be displayed in textbox.</param>
|
||||
private void AddTextBoxControl(string message)
|
||||
{
|
||||
this.textBox = new RichTextBox();
|
||||
this.textBox.Text = message;
|
||||
this.textBox.BackColor = Color.LightYellow;
|
||||
this.textBox.Multiline = true;
|
||||
this.textBox.Dock = DockStyle.Top;
|
||||
this.textBox.ReadOnly = true;
|
||||
this.textBox.ContentsResized += this.RTBContentsResized;
|
||||
this.textBox.ScrollBars = RichTextBoxScrollBars.None;
|
||||
this.textBox.BorderStyle = BorderStyle.None;
|
||||
this.Controls.Add(this.textBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using Common;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
|
||||
namespace SvgPreviewHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends <see cref="StreamBasedPreviewHandler"/> for Svg Preview Handler.
|
||||
/// </summary>
|
||||
[Guid("ddee2b8a-6807-48a6-bb20-2338174ff779")]
|
||||
[ClassInterface(ClassInterfaceType.None)]
|
||||
[ComVisible(true)]
|
||||
public class SvgPreviewHandler : StreamBasedPreviewHandler
|
||||
{
|
||||
private SvgPreviewControl svgPreviewControl;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void DoPreview()
|
||||
{
|
||||
this.svgPreviewControl.DoPreview(this.Stream);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IPreviewHandlerControl CreatePreviewHandlerControl()
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.Events.SvgFileHandlerLoaded());
|
||||
this.svgPreviewControl = new SvgPreviewControl();
|
||||
return this.svgPreviewControl;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using Common;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
|
||||
namespace SvgPreviewHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends <see cref="StreamBasedPreviewHandler"/> for Svg Preview Handler.
|
||||
/// </summary>
|
||||
[Guid("ddee2b8a-6807-48a6-bb20-2338174ff779")]
|
||||
[ClassInterface(ClassInterfaceType.None)]
|
||||
[ComVisible(true)]
|
||||
public class SvgPreviewHandler : StreamBasedPreviewHandler
|
||||
{
|
||||
private SvgPreviewControl svgPreviewControl;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void DoPreview()
|
||||
{
|
||||
this.svgPreviewControl.DoPreview(this.Stream);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IPreviewHandlerControl CreatePreviewHandlerControl()
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.Events.SvgFileHandlerLoaded());
|
||||
this.svgPreviewControl = new SvgPreviewControl();
|
||||
return this.svgPreviewControl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace SvgPreviewHandler.Telemetry.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// A telemetry event to be raised when a svg file has been viewed in the preview pane.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class SvgFileHandlerLoaded : EventBase, IEvent
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace SvgPreviewHandler.Telemetry.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// A telemetry event to be raised when a svg file has been viewed in the preview pane.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class SvgFileHandlerLoaded : EventBase, IEvent
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace SvgPreviewHandler.Telemetry.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// A telemetry event to be raised when an error has occurred in the preview pane.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class SvgFilePreviewError : EventBase, IEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the error message to log as part of the telemetry event.
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
|
||||
}
|
||||
}
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace SvgPreviewHandler.Telemetry.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// A telemetry event to be raised when an error has occurred in the preview pane.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class SvgFilePreviewError : EventBase, IEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the error message to log as part of the telemetry event.
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace SvgPreviewHandler.Telemetry.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// A telemetry event to be raised when a svg file has been viewed in the preview pane.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class SvgFilePreviewed : EventBase, IEvent
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
// 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.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace SvgPreviewHandler.Telemetry.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// A telemetry event to be raised when a svg file has been viewed in the preview pane.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
public class SvgFilePreviewed : EventBase, IEvent
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
// 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;
|
||||
using Common;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests_PreviewHandlerCommon
|
||||
{
|
||||
[TestClass]
|
||||
public class FileBasedPreviewHandlerTests
|
||||
{
|
||||
public class TestFileBasedPreviewHandler : FileBasedPreviewHandler
|
||||
{
|
||||
public override void DoPreview()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IPreviewHandlerControl CreatePreviewHandlerControl()
|
||||
{
|
||||
return new Mock<IPreviewHandlerControl>().Object;
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(0U)]
|
||||
[DataRow(1U)]
|
||||
public void FileBasedPreviewHandler_ShouldSetFilePath_WhenInitializeCalled(uint grfMode)
|
||||
{
|
||||
// Arrange
|
||||
var fileBasedPreviewHandler = new TestFileBasedPreviewHandler();
|
||||
var filePath = "C:\\valid-path";
|
||||
|
||||
// Act
|
||||
fileBasedPreviewHandler.Initialize(filePath, grfMode);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(fileBasedPreviewHandler.FilePath, filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using Common;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests_PreviewHandlerCommon
|
||||
{
|
||||
[TestClass]
|
||||
public class FileBasedPreviewHandlerTests
|
||||
{
|
||||
public class TestFileBasedPreviewHandler : FileBasedPreviewHandler
|
||||
{
|
||||
public override void DoPreview()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IPreviewHandlerControl CreatePreviewHandlerControl()
|
||||
{
|
||||
return new Mock<IPreviewHandlerControl>().Object;
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(0U)]
|
||||
[DataRow(1U)]
|
||||
public void FileBasedPreviewHandler_ShouldSetFilePath_WhenInitializeCalled(uint grfMode)
|
||||
{
|
||||
// Arrange
|
||||
var fileBasedPreviewHandler = new TestFileBasedPreviewHandler();
|
||||
var filePath = "C:\\valid-path";
|
||||
|
||||
// Act
|
||||
fileBasedPreviewHandler.Initialize(filePath, grfMode);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(fileBasedPreviewHandler.FilePath, filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,200 +1,200 @@
|
||||
// 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;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
using Common;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests_PreviewHandlerCommon
|
||||
{
|
||||
[TestClass]
|
||||
public class FormHandlerControlTests
|
||||
{
|
||||
private class TestFormControl : FormHandlerControl
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldCreateHandle_OnInitialization()
|
||||
{
|
||||
// Arrange and act
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Assert
|
||||
Assert.IsTrue(testFormHandlerControl.IsHandleCreated);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetVisibleFalse_OnInitialization()
|
||||
{
|
||||
// Arrange and act
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Assert
|
||||
Assert.IsFalse(testFormHandlerControl.Visible);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetFormBorderStyle_OnInitialization()
|
||||
{
|
||||
// Arrange and act
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Assert
|
||||
Assert.AreEqual(FormBorderStyle.None, testFormHandlerControl.FormBorderStyle);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldReturnValidHandle_WhenGetHandleCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Act
|
||||
var handle = testFormHandlerControl.GetHandle();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(testFormHandlerControl.Handle, handle);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetBackgroundColor_WhenSetBackgroundColorCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
var color = Color.Navy;
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.SetBackgroundColor(color);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(color, testFormHandlerControl.BackColor);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetFont_WhenSetFontCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
var font = new Font("Arial", 20);
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.SetFont(font);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(font, testFormHandlerControl.Font);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldUpdateBounds_WhenSetRectCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
var bounds = new Rectangle(2, 2, 4, 4);
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.SetRect(bounds);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(bounds, testFormHandlerControl.Bounds);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetTextColor_WhenSetTextColorCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
var color = Color.Navy;
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.SetTextColor(color);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(color, testFormHandlerControl.ForeColor);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldClearAllControls_WhenUnloadCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
testFormHandlerControl.Controls.Add(new TextBox());
|
||||
testFormHandlerControl.Controls.Add(new RichTextBox());
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.Unload();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, testFormHandlerControl.Controls.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetVisibleFalse_WhenUnloadCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Act
|
||||
testFormHandlerControl.Unload();
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(testFormHandlerControl.Visible);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetVisibletrue_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Act
|
||||
testFormHandlerControl.DoPreview("valid-path");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(testFormHandlerControl.Visible);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetParentHandle_WhenSetWindowCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
var parentFormWindow = new UserControl();
|
||||
var parentHwnd = parentFormWindow.Handle;
|
||||
var rect = new Rectangle(2, 2, 4, 4);
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.SetWindow(parentHwnd, rect);
|
||||
var actualParentHwnd = GetAncestor(testFormHandlerControl.Handle, 1); // GA_PARENT 1
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(parentHwnd, actualParentHwnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the ancestor window: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getancestor
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
using Common;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests_PreviewHandlerCommon
|
||||
{
|
||||
[TestClass]
|
||||
public class FormHandlerControlTests
|
||||
{
|
||||
private class TestFormControl : FormHandlerControl
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldCreateHandle_OnInitialization()
|
||||
{
|
||||
// Arrange and act
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Assert
|
||||
Assert.IsTrue(testFormHandlerControl.IsHandleCreated);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetVisibleFalse_OnInitialization()
|
||||
{
|
||||
// Arrange and act
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Assert
|
||||
Assert.IsFalse(testFormHandlerControl.Visible);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetFormBorderStyle_OnInitialization()
|
||||
{
|
||||
// Arrange and act
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Assert
|
||||
Assert.AreEqual(FormBorderStyle.None, testFormHandlerControl.FormBorderStyle);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldReturnValidHandle_WhenGetHandleCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Act
|
||||
var handle = testFormHandlerControl.GetHandle();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(testFormHandlerControl.Handle, handle);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetBackgroundColor_WhenSetBackgroundColorCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
var color = Color.Navy;
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.SetBackgroundColor(color);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(color, testFormHandlerControl.BackColor);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetFont_WhenSetFontCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
var font = new Font("Arial", 20);
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.SetFont(font);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(font, testFormHandlerControl.Font);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldUpdateBounds_WhenSetRectCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
var bounds = new Rectangle(2, 2, 4, 4);
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.SetRect(bounds);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(bounds, testFormHandlerControl.Bounds);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetTextColor_WhenSetTextColorCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
var color = Color.Navy;
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.SetTextColor(color);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(color, testFormHandlerControl.ForeColor);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldClearAllControls_WhenUnloadCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
testFormHandlerControl.Controls.Add(new TextBox());
|
||||
testFormHandlerControl.Controls.Add(new RichTextBox());
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.Unload();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, testFormHandlerControl.Controls.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetVisibleFalse_WhenUnloadCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Act
|
||||
testFormHandlerControl.Unload();
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(testFormHandlerControl.Visible);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetVisibletrue_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
// Act
|
||||
testFormHandlerControl.DoPreview("valid-path");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(testFormHandlerControl.Visible);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FormHandlerControl_ShouldSetParentHandle_WhenSetWindowCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var testFormHandlerControl = new TestFormControl())
|
||||
{
|
||||
var parentFormWindow = new UserControl();
|
||||
var parentHwnd = parentFormWindow.Handle;
|
||||
var rect = new Rectangle(2, 2, 4, 4);
|
||||
|
||||
// Act
|
||||
testFormHandlerControl.SetWindow(parentHwnd, rect);
|
||||
var actualParentHwnd = GetAncestor(testFormHandlerControl.Handle, 1); // GA_PARENT 1
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(parentHwnd, actualParentHwnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the ancestor window: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getancestor
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,394 +1,394 @@
|
||||
// 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;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using Common;
|
||||
using Common.ComInterlop;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests_PreviewHandlerCommon
|
||||
{
|
||||
[TestClass]
|
||||
public class PreviewHandlerBaseTests
|
||||
{
|
||||
private static IPreviewHandlerControl previewHandlerControl;
|
||||
|
||||
public class TestPreviewHandler : PreviewHandlerBase
|
||||
{
|
||||
public override void DoPreview()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IPreviewHandlerControl CreatePreviewHandlerControl()
|
||||
{
|
||||
return GetMockPreviewHandlerControl();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetWindow_WhenSetWindowCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
var handle = new IntPtr(5);
|
||||
var bounds = GetRectangle(2, 2, 4, 4);
|
||||
|
||||
var actualHandle = IntPtr.Zero;
|
||||
var actualBounds = Rectangle.Empty;
|
||||
mockPreviewControl
|
||||
.Setup(_ => _.SetWindow(It.IsAny<IntPtr>(), It.IsAny<Rectangle>()))
|
||||
.Callback<IntPtr, Rectangle>((hwnd, rect) =>
|
||||
{
|
||||
actualHandle = hwnd;
|
||||
actualBounds = rect;
|
||||
});
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetWindow(handle, ref bounds);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualHandle, handle);
|
||||
Assert.AreEqual(actualBounds, bounds.ToRectangle());
|
||||
mockPreviewControl.Verify(_ => _.SetWindow(It.IsAny<IntPtr>(), It.IsAny<Rectangle>()), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetrect_WhenSetRectCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
var bounds = GetRectangle(2, 2, 4, 4);
|
||||
|
||||
var actualBounds = Rectangle.Empty;
|
||||
mockPreviewControl
|
||||
.Setup(_ => _.SetRect(It.IsAny<Rectangle>()))
|
||||
.Callback<Rectangle>((rect) =>
|
||||
{
|
||||
actualBounds = rect;
|
||||
});
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetRect(ref bounds);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualBounds, bounds.ToRectangle());
|
||||
mockPreviewControl.Verify(_ => _.SetRect(It.IsAny<Rectangle>()), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlUnload_WhenUnloadCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.Unload();
|
||||
|
||||
// Assert
|
||||
mockPreviewControl.Verify(_ => _.Unload(), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetBackgroundColor_WhenSetBackgroundColorCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
var color = default(COLORREF);
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetBackgroundColor(color);
|
||||
|
||||
// Assert
|
||||
mockPreviewControl.Verify(_ => _.SetBackgroundColor(It.Is<Color>(c => (c == color.Color))), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetTextColor_WhenSetTextColorCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
var color = default(COLORREF);
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetTextColor(color);
|
||||
|
||||
// Assert
|
||||
mockPreviewControl.Verify(_ => _.SetTextColor(It.Is<Color>(c => (c == color.Color))), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetFont_WhenSetFontCalled()
|
||||
{
|
||||
// Arrange
|
||||
Font actualFont = null;
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
mockPreviewControl
|
||||
.Setup(x => x.SetFont(It.IsAny<Font>()))
|
||||
.Callback<Font>((font) =>
|
||||
{
|
||||
actualFont = font;
|
||||
});
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
var logFont = GetLogFont();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetFont(ref logFont);
|
||||
|
||||
// Assert
|
||||
mockPreviewControl.Verify(_ => _.SetFont(It.IsAny<Font>()), Times.Once);
|
||||
Assert.AreEqual(Font.FromLogFont(logFont), actualFont);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetFocus_WhenSetFocusCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetFocus();
|
||||
|
||||
// Assert
|
||||
mockPreviewControl.Verify(_ => _.SetFocus(), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldSetHandleOnQueryFocus_WhenPreviewControlsReturnValidHandle()
|
||||
{
|
||||
// Arrange
|
||||
var hwnd = new IntPtr(5);
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
mockPreviewControl.Setup(x => x.QueryFocus(out hwnd));
|
||||
var actualHwnd = IntPtr.Zero;
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.QueryFocus(out actualHwnd);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualHwnd, hwnd);
|
||||
mockPreviewControl.Verify(_ => _.QueryFocus(out hwnd), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldThrowOnQueryFocus_WhenPreviewControlsReturnNotValidHandle()
|
||||
{
|
||||
// Arrange
|
||||
var hwnd = IntPtr.Zero;
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
mockPreviewControl.Setup(x => x.QueryFocus(out hwnd));
|
||||
var actualHwnd = IntPtr.Zero;
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
Win32Exception exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
testPreviewHandler.QueryFocus(out actualHwnd);
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
mockPreviewControl.Verify(_ => _.QueryFocus(out hwnd), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldDirectKeyStrokesToIPreviewHandlerFrame_IfIPreviewHandlerFrameIsSet()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
var mockPreviewHandlerFrame = new Mock<IPreviewHandlerFrame>();
|
||||
var msg = default(MSG);
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
testPreviewHandler.SetSite(mockPreviewHandlerFrame.Object);
|
||||
|
||||
// Act
|
||||
testPreviewHandler.TranslateAccelerator(ref msg);
|
||||
|
||||
// Assert
|
||||
mockPreviewHandlerFrame.Verify(_ => _.TranslateAccelerator(ref msg), Times.Once);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(0U)]
|
||||
[DataRow(1U)]
|
||||
public void PreviewHandlerBase_ShouldReturnIPreviewHandlerFrameResponse_IfIPreviewHandlerFrameIsSet(uint resultCode)
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
var mockPreviewHandlerFrame = new Mock<IPreviewHandlerFrame>();
|
||||
var msg = default(MSG);
|
||||
mockPreviewHandlerFrame
|
||||
.Setup(x => x.TranslateAccelerator(ref msg))
|
||||
.Returns(resultCode);
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
testPreviewHandler.SetSite(mockPreviewHandlerFrame.Object);
|
||||
|
||||
// Act
|
||||
var actualResultCode = testPreviewHandler.TranslateAccelerator(ref msg);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(resultCode, actualResultCode);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldReturnS_FALSE_IfIPreviewHandlerFrameIsNotSet()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
var msg = default(MSG);
|
||||
uint sFalse = 1;
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
var result = testPreviewHandler.TranslateAccelerator(ref msg);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(result, sFalse);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldReturnPreviewControlHandle_IfGetWindowCalled()
|
||||
{
|
||||
// Arrange
|
||||
var previewControlHandle = new IntPtr(5);
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
mockPreviewControl.Setup(x => x.GetHandle())
|
||||
.Returns(previewControlHandle);
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
var hwndReceived = IntPtr.Zero;
|
||||
|
||||
// Act
|
||||
testPreviewHandler.GetWindow(out hwndReceived);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(hwndReceived, previewControlHandle);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(true)]
|
||||
[DataRow(false)]
|
||||
public void PreviewHandlerBase_ShouldThrowNotImplementedException_IfContextSensitiveHelpCalled(bool fEnterMode)
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
NotImplementedException exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
testPreviewHandler.ContextSensitiveHelp(fEnterMode);
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldReturnSite_WhenGetSiteCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
var site = new Mock<IPreviewHandlerFrame>().Object;
|
||||
testPreviewHandler.SetSite(site);
|
||||
object actualSite = null;
|
||||
var riid = Guid.Empty;
|
||||
|
||||
// Act
|
||||
testPreviewHandler.GetSite(ref riid, out actualSite);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualSite, site);
|
||||
}
|
||||
|
||||
private LOGFONT GetLogFont()
|
||||
{
|
||||
var logFont = new LOGFONT
|
||||
{
|
||||
LfHeight = 12,
|
||||
LfWidth = 0,
|
||||
LfEscapement = 0,
|
||||
LfWeight = 400, // FW_NORMAL
|
||||
LfItalic = Convert.ToByte(false),
|
||||
LfUnderline = Convert.ToByte(false),
|
||||
LfStrikeOut = Convert.ToByte(0),
|
||||
LfCharSet = Convert.ToByte(0), // ANSI_CHARSET
|
||||
LfOutPrecision = Convert.ToByte(0), // OUT_DEFAULT_PRECIS
|
||||
LfClipPrecision = Convert.ToByte(0),
|
||||
LfQuality = Convert.ToByte(0),
|
||||
LfPitchAndFamily = Convert.ToByte(0),
|
||||
LfFaceName = "valid-font",
|
||||
};
|
||||
|
||||
return logFont;
|
||||
}
|
||||
|
||||
private RECT GetRectangle(int left, int top, int right, int bottom)
|
||||
{
|
||||
var rect = new RECT
|
||||
{
|
||||
Left = left,
|
||||
Top = top,
|
||||
Right = right,
|
||||
Bottom = bottom,
|
||||
};
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static IPreviewHandlerControl GetMockPreviewHandlerControl()
|
||||
{
|
||||
return previewHandlerControl;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using Common;
|
||||
using Common.ComInterlop;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests_PreviewHandlerCommon
|
||||
{
|
||||
[TestClass]
|
||||
public class PreviewHandlerBaseTests
|
||||
{
|
||||
private static IPreviewHandlerControl previewHandlerControl;
|
||||
|
||||
public class TestPreviewHandler : PreviewHandlerBase
|
||||
{
|
||||
public override void DoPreview()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IPreviewHandlerControl CreatePreviewHandlerControl()
|
||||
{
|
||||
return GetMockPreviewHandlerControl();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetWindow_WhenSetWindowCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
var handle = new IntPtr(5);
|
||||
var bounds = GetRectangle(2, 2, 4, 4);
|
||||
|
||||
var actualHandle = IntPtr.Zero;
|
||||
var actualBounds = Rectangle.Empty;
|
||||
mockPreviewControl
|
||||
.Setup(_ => _.SetWindow(It.IsAny<IntPtr>(), It.IsAny<Rectangle>()))
|
||||
.Callback<IntPtr, Rectangle>((hwnd, rect) =>
|
||||
{
|
||||
actualHandle = hwnd;
|
||||
actualBounds = rect;
|
||||
});
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetWindow(handle, ref bounds);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualHandle, handle);
|
||||
Assert.AreEqual(actualBounds, bounds.ToRectangle());
|
||||
mockPreviewControl.Verify(_ => _.SetWindow(It.IsAny<IntPtr>(), It.IsAny<Rectangle>()), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetrect_WhenSetRectCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
var bounds = GetRectangle(2, 2, 4, 4);
|
||||
|
||||
var actualBounds = Rectangle.Empty;
|
||||
mockPreviewControl
|
||||
.Setup(_ => _.SetRect(It.IsAny<Rectangle>()))
|
||||
.Callback<Rectangle>((rect) =>
|
||||
{
|
||||
actualBounds = rect;
|
||||
});
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetRect(ref bounds);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualBounds, bounds.ToRectangle());
|
||||
mockPreviewControl.Verify(_ => _.SetRect(It.IsAny<Rectangle>()), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlUnload_WhenUnloadCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.Unload();
|
||||
|
||||
// Assert
|
||||
mockPreviewControl.Verify(_ => _.Unload(), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetBackgroundColor_WhenSetBackgroundColorCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
var color = default(COLORREF);
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetBackgroundColor(color);
|
||||
|
||||
// Assert
|
||||
mockPreviewControl.Verify(_ => _.SetBackgroundColor(It.Is<Color>(c => (c == color.Color))), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetTextColor_WhenSetTextColorCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
var color = default(COLORREF);
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetTextColor(color);
|
||||
|
||||
// Assert
|
||||
mockPreviewControl.Verify(_ => _.SetTextColor(It.Is<Color>(c => (c == color.Color))), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetFont_WhenSetFontCalled()
|
||||
{
|
||||
// Arrange
|
||||
Font actualFont = null;
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
mockPreviewControl
|
||||
.Setup(x => x.SetFont(It.IsAny<Font>()))
|
||||
.Callback<Font>((font) =>
|
||||
{
|
||||
actualFont = font;
|
||||
});
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
var logFont = GetLogFont();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetFont(ref logFont);
|
||||
|
||||
// Assert
|
||||
mockPreviewControl.Verify(_ => _.SetFont(It.IsAny<Font>()), Times.Once);
|
||||
Assert.AreEqual(Font.FromLogFont(logFont), actualFont);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldCallPreviewControlSetFocus_WhenSetFocusCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.SetFocus();
|
||||
|
||||
// Assert
|
||||
mockPreviewControl.Verify(_ => _.SetFocus(), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldSetHandleOnQueryFocus_WhenPreviewControlsReturnValidHandle()
|
||||
{
|
||||
// Arrange
|
||||
var hwnd = new IntPtr(5);
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
mockPreviewControl.Setup(x => x.QueryFocus(out hwnd));
|
||||
var actualHwnd = IntPtr.Zero;
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
testPreviewHandler.QueryFocus(out actualHwnd);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualHwnd, hwnd);
|
||||
mockPreviewControl.Verify(_ => _.QueryFocus(out hwnd), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldThrowOnQueryFocus_WhenPreviewControlsReturnNotValidHandle()
|
||||
{
|
||||
// Arrange
|
||||
var hwnd = IntPtr.Zero;
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
mockPreviewControl.Setup(x => x.QueryFocus(out hwnd));
|
||||
var actualHwnd = IntPtr.Zero;
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
Win32Exception exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
testPreviewHandler.QueryFocus(out actualHwnd);
|
||||
}
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
mockPreviewControl.Verify(_ => _.QueryFocus(out hwnd), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldDirectKeyStrokesToIPreviewHandlerFrame_IfIPreviewHandlerFrameIsSet()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
var mockPreviewHandlerFrame = new Mock<IPreviewHandlerFrame>();
|
||||
var msg = default(MSG);
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
testPreviewHandler.SetSite(mockPreviewHandlerFrame.Object);
|
||||
|
||||
// Act
|
||||
testPreviewHandler.TranslateAccelerator(ref msg);
|
||||
|
||||
// Assert
|
||||
mockPreviewHandlerFrame.Verify(_ => _.TranslateAccelerator(ref msg), Times.Once);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(0U)]
|
||||
[DataRow(1U)]
|
||||
public void PreviewHandlerBase_ShouldReturnIPreviewHandlerFrameResponse_IfIPreviewHandlerFrameIsSet(uint resultCode)
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
var mockPreviewHandlerFrame = new Mock<IPreviewHandlerFrame>();
|
||||
var msg = default(MSG);
|
||||
mockPreviewHandlerFrame
|
||||
.Setup(x => x.TranslateAccelerator(ref msg))
|
||||
.Returns(resultCode);
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
testPreviewHandler.SetSite(mockPreviewHandlerFrame.Object);
|
||||
|
||||
// Act
|
||||
var actualResultCode = testPreviewHandler.TranslateAccelerator(ref msg);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(resultCode, actualResultCode);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldReturnS_FALSE_IfIPreviewHandlerFrameIsNotSet()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
var msg = default(MSG);
|
||||
uint sFalse = 1;
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
|
||||
// Act
|
||||
var result = testPreviewHandler.TranslateAccelerator(ref msg);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(result, sFalse);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldReturnPreviewControlHandle_IfGetWindowCalled()
|
||||
{
|
||||
// Arrange
|
||||
var previewControlHandle = new IntPtr(5);
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
mockPreviewControl.Setup(x => x.GetHandle())
|
||||
.Returns(previewControlHandle);
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
var hwndReceived = IntPtr.Zero;
|
||||
|
||||
// Act
|
||||
testPreviewHandler.GetWindow(out hwndReceived);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(hwndReceived, previewControlHandle);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(true)]
|
||||
[DataRow(false)]
|
||||
public void PreviewHandlerBase_ShouldThrowNotImplementedException_IfContextSensitiveHelpCalled(bool fEnterMode)
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
NotImplementedException exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
testPreviewHandler.ContextSensitiveHelp(fEnterMode);
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PreviewHandlerBase_ShouldReturnSite_WhenGetSiteCalled()
|
||||
{
|
||||
// Arrange
|
||||
var mockPreviewControl = new Mock<IPreviewHandlerControl>();
|
||||
|
||||
previewHandlerControl = mockPreviewControl.Object;
|
||||
var testPreviewHandler = new TestPreviewHandler();
|
||||
var site = new Mock<IPreviewHandlerFrame>().Object;
|
||||
testPreviewHandler.SetSite(site);
|
||||
object actualSite = null;
|
||||
var riid = Guid.Empty;
|
||||
|
||||
// Act
|
||||
testPreviewHandler.GetSite(ref riid, out actualSite);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualSite, site);
|
||||
}
|
||||
|
||||
private LOGFONT GetLogFont()
|
||||
{
|
||||
var logFont = new LOGFONT
|
||||
{
|
||||
LfHeight = 12,
|
||||
LfWidth = 0,
|
||||
LfEscapement = 0,
|
||||
LfWeight = 400, // FW_NORMAL
|
||||
LfItalic = Convert.ToByte(false),
|
||||
LfUnderline = Convert.ToByte(false),
|
||||
LfStrikeOut = Convert.ToByte(0),
|
||||
LfCharSet = Convert.ToByte(0), // ANSI_CHARSET
|
||||
LfOutPrecision = Convert.ToByte(0), // OUT_DEFAULT_PRECIS
|
||||
LfClipPrecision = Convert.ToByte(0),
|
||||
LfQuality = Convert.ToByte(0),
|
||||
LfPitchAndFamily = Convert.ToByte(0),
|
||||
LfFaceName = "valid-font",
|
||||
};
|
||||
|
||||
return logFont;
|
||||
}
|
||||
|
||||
private RECT GetRectangle(int left, int top, int right, int bottom)
|
||||
{
|
||||
var rect = new RECT
|
||||
{
|
||||
Left = left,
|
||||
Top = top,
|
||||
Right = right,
|
||||
Bottom = bottom,
|
||||
};
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static IPreviewHandlerControl GetMockPreviewHandlerControl()
|
||||
{
|
||||
return previewHandlerControl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Common;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests_PreviewHandlerCommon
|
||||
{
|
||||
[TestClass]
|
||||
public class StreamBasedPreviewHandlerTests
|
||||
{
|
||||
public class TestStreamBasedPreviewHandler : StreamBasedPreviewHandler
|
||||
{
|
||||
public override void DoPreview()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IPreviewHandlerControl CreatePreviewHandlerControl()
|
||||
{
|
||||
return new Mock<IPreviewHandlerControl>().Object;
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(0U)]
|
||||
[DataRow(1U)]
|
||||
public void StreamBasedPreviewHandler_ShouldSetStream_WhenInitializeCalled(uint grfMode)
|
||||
{
|
||||
// Arrange
|
||||
var streamBasedPreviewHandler = new TestStreamBasedPreviewHandler();
|
||||
var stream = new Mock<IStream>().Object;
|
||||
|
||||
// Act
|
||||
streamBasedPreviewHandler.Initialize(stream, grfMode);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(streamBasedPreviewHandler.Stream, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Common;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests_PreviewHandlerCommon
|
||||
{
|
||||
[TestClass]
|
||||
public class StreamBasedPreviewHandlerTests
|
||||
{
|
||||
public class TestStreamBasedPreviewHandler : StreamBasedPreviewHandler
|
||||
{
|
||||
public override void DoPreview()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IPreviewHandlerControl CreatePreviewHandlerControl()
|
||||
{
|
||||
return new Mock<IPreviewHandlerControl>().Object;
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(0U)]
|
||||
[DataRow(1U)]
|
||||
public void StreamBasedPreviewHandler_ShouldSetStream_WhenInitializeCalled(uint grfMode)
|
||||
{
|
||||
// Arrange
|
||||
var streamBasedPreviewHandler = new TestStreamBasedPreviewHandler();
|
||||
var stream = new Mock<IStream>().Object;
|
||||
|
||||
// Act
|
||||
streamBasedPreviewHandler.Initialize(stream, grfMode);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(streamBasedPreviewHandler.Stream, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,331 +1,331 @@
|
||||
// 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;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Common.Utilities;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests_PreviewHandlerCommon
|
||||
{
|
||||
[TestClass]
|
||||
public class StreamWrapperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldThrow_IfInitializeWithNullStream()
|
||||
{
|
||||
// Arrange
|
||||
IStream stream = null;
|
||||
ArgumentNullException exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
var streamWrapper = new StreamWrapper(stream);
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnCanReadTrue()
|
||||
{
|
||||
// Arrange
|
||||
var streamMock = new Mock<IStream>();
|
||||
|
||||
// Act
|
||||
var streamWrapper = new StreamWrapper(streamMock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(streamWrapper.CanRead, true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnCanSeekTrue()
|
||||
{
|
||||
// Arrange
|
||||
var streamMock = new Mock<IStream>();
|
||||
|
||||
// Act
|
||||
var streamWrapper = new StreamWrapper(streamMock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(streamWrapper.CanSeek, true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnCanWriteFalse()
|
||||
{
|
||||
// Arrange
|
||||
var streamMock = new Mock<IStream>();
|
||||
|
||||
// Act
|
||||
var streamWrapper = new StreamWrapper(streamMock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(streamWrapper.CanWrite, false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnValidLength()
|
||||
{
|
||||
// Arrange
|
||||
long streamLength = 5;
|
||||
var stremMock = new Mock<IStream>();
|
||||
var stat = new System.Runtime.InteropServices.ComTypes.STATSTG
|
||||
{
|
||||
cbSize = streamLength,
|
||||
};
|
||||
|
||||
stremMock
|
||||
.Setup(x => x.Stat(out stat, It.IsAny<int>()));
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
var actualLength = streamWrapper.Length;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualLength, streamLength);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnValidPosition()
|
||||
{
|
||||
// Arrange
|
||||
int expectedDwOrigin = 1; // STREAM_SEEK_CUR
|
||||
long expectedOffset = 0;
|
||||
long currPosition = 5;
|
||||
var stremMock = new Mock<IStream>();
|
||||
|
||||
stremMock
|
||||
.Setup(x => x.Seek(It.IsAny<long>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Callback<long, int, IntPtr>((dlibMove, dwOrigin, plibNewPosition) =>
|
||||
{
|
||||
Marshal.WriteInt64(plibNewPosition, currPosition);
|
||||
});
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
var actualPosition = streamWrapper.Position;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualPosition, currPosition);
|
||||
stremMock.Verify(_ => _.Seek(It.Is<long>(offset => offset == expectedOffset), It.Is<int>(dworigin => dworigin == expectedDwOrigin), It.IsAny<IntPtr>()), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldCallIStreamSeek_WhenSetPosition()
|
||||
{
|
||||
// Arrange
|
||||
long positionToSet = 5;
|
||||
int expectedDwOrigin = 0; // STREAM_SEEK_SET
|
||||
var stremMock = new Mock<IStream>();
|
||||
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object)
|
||||
{
|
||||
// Act
|
||||
Position = positionToSet,
|
||||
};
|
||||
|
||||
// Assert
|
||||
stremMock.Verify(_ => _.Seek(It.Is<long>(offset => offset == positionToSet), It.Is<int>(dworigin => dworigin == expectedDwOrigin), It.IsAny<IntPtr>()), Times.Once);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(0L, SeekOrigin.Begin)]
|
||||
[DataRow(5L, SeekOrigin.Begin)]
|
||||
[DataRow(0L, SeekOrigin.Current)]
|
||||
[DataRow(5L, SeekOrigin.Current)]
|
||||
[DataRow(0L, SeekOrigin.End)]
|
||||
[DataRow(5L, SeekOrigin.End)]
|
||||
public void StreamWrapper_ShouldCallIStreamSeekWithValidArguments_WhenSeekCalled(long offset, SeekOrigin origin)
|
||||
{
|
||||
// Arrange
|
||||
int expectedDwOrigin = 0;
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
expectedDwOrigin = 0;
|
||||
break;
|
||||
|
||||
case SeekOrigin.Current:
|
||||
expectedDwOrigin = 1;
|
||||
break;
|
||||
|
||||
case SeekOrigin.End:
|
||||
expectedDwOrigin = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
var stremMock = new Mock<IStream>();
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
streamWrapper.Seek(offset, origin);
|
||||
|
||||
// Assert
|
||||
stremMock.Verify(_ => _.Seek(It.Is<long>(actualOffset => actualOffset == offset), It.Is<int>(actualDwOrigin => actualDwOrigin == expectedDwOrigin), It.IsAny<IntPtr>()), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnValidPosition_WhenSeekCalled()
|
||||
{
|
||||
// Arrange
|
||||
long position = 5;
|
||||
var stremMock = new Mock<IStream>();
|
||||
|
||||
stremMock
|
||||
.Setup(x => x.Seek(It.IsAny<long>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Callback<long, int, IntPtr>((dlibMove, dwOrigin, plibNewPosition) =>
|
||||
{
|
||||
Marshal.WriteInt64(plibNewPosition, position);
|
||||
});
|
||||
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
var actualPosition = streamWrapper.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(position, actualPosition);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(10, -1, 5)]
|
||||
[DataRow(10, 0, -5)]
|
||||
[DataRow(10, 0, 11)]
|
||||
[DataRow(10, 5, 6)]
|
||||
public void StreamWrapper_ShouldThrow_WhenReadCalledWithOutOfRangeArguments(int bufferLength, int offSet, int bytesToRead)
|
||||
{
|
||||
// Arrange
|
||||
var buffer = new byte[bufferLength];
|
||||
var stremMock = new Mock<IStream>();
|
||||
ArgumentOutOfRangeException exception = null;
|
||||
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
streamWrapper.Read(buffer, offSet, bytesToRead);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(5, 0)]
|
||||
[DataRow(5, 5)]
|
||||
[DataRow(0, 5)]
|
||||
public void StreamWrapper_ShouldSetValidBuffer_WhenReadCalled(int count, int offset)
|
||||
{
|
||||
// Arrange
|
||||
var inputBuffer = new byte[1024];
|
||||
var streamBytes = new byte[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
streamBytes[i] = (byte)i;
|
||||
}
|
||||
|
||||
var stremMock = new Mock<IStream>();
|
||||
|
||||
stremMock
|
||||
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Callback<byte[], int, IntPtr>((buffer, countToRead, bytesReadPtr) =>
|
||||
{
|
||||
Array.Copy(streamBytes, 0, buffer, 0, streamBytes.Length);
|
||||
Marshal.WriteInt32(bytesReadPtr, count);
|
||||
});
|
||||
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
var bytesRead = streamWrapper.Read(inputBuffer, offset, count);
|
||||
|
||||
// Assert
|
||||
CollectionAssert.AreEqual(streamBytes, inputBuffer.Skip(offset).Take(count).ToArray());
|
||||
Assert.AreEqual(count, bytesRead);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldThrowNotImplementedException_WhenFlushCalled()
|
||||
{
|
||||
// Arrange
|
||||
var stremMock = new Mock<IStream>();
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
NotImplementedException exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
streamWrapper.Flush();
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldThrowNotImplementedException_WhenSetLengthCalled()
|
||||
{
|
||||
// Arrange
|
||||
var stremMock = new Mock<IStream>();
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
NotImplementedException exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
streamWrapper.SetLength(5);
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldThrowNotImplementedException_WhenWriteCalled()
|
||||
{
|
||||
// Arrange
|
||||
var stremMock = new Mock<IStream>();
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
NotImplementedException exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
streamWrapper.Write(new byte[5], 0, 0);
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Common.Utilities;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests_PreviewHandlerCommon
|
||||
{
|
||||
[TestClass]
|
||||
public class StreamWrapperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldThrow_IfInitializeWithNullStream()
|
||||
{
|
||||
// Arrange
|
||||
IStream stream = null;
|
||||
ArgumentNullException exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
var streamWrapper = new StreamWrapper(stream);
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnCanReadTrue()
|
||||
{
|
||||
// Arrange
|
||||
var streamMock = new Mock<IStream>();
|
||||
|
||||
// Act
|
||||
var streamWrapper = new StreamWrapper(streamMock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(streamWrapper.CanRead, true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnCanSeekTrue()
|
||||
{
|
||||
// Arrange
|
||||
var streamMock = new Mock<IStream>();
|
||||
|
||||
// Act
|
||||
var streamWrapper = new StreamWrapper(streamMock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(streamWrapper.CanSeek, true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnCanWriteFalse()
|
||||
{
|
||||
// Arrange
|
||||
var streamMock = new Mock<IStream>();
|
||||
|
||||
// Act
|
||||
var streamWrapper = new StreamWrapper(streamMock.Object);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(streamWrapper.CanWrite, false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnValidLength()
|
||||
{
|
||||
// Arrange
|
||||
long streamLength = 5;
|
||||
var stremMock = new Mock<IStream>();
|
||||
var stat = new System.Runtime.InteropServices.ComTypes.STATSTG
|
||||
{
|
||||
cbSize = streamLength,
|
||||
};
|
||||
|
||||
stremMock
|
||||
.Setup(x => x.Stat(out stat, It.IsAny<int>()));
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
var actualLength = streamWrapper.Length;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualLength, streamLength);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnValidPosition()
|
||||
{
|
||||
// Arrange
|
||||
int expectedDwOrigin = 1; // STREAM_SEEK_CUR
|
||||
long expectedOffset = 0;
|
||||
long currPosition = 5;
|
||||
var stremMock = new Mock<IStream>();
|
||||
|
||||
stremMock
|
||||
.Setup(x => x.Seek(It.IsAny<long>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Callback<long, int, IntPtr>((dlibMove, dwOrigin, plibNewPosition) =>
|
||||
{
|
||||
Marshal.WriteInt64(plibNewPosition, currPosition);
|
||||
});
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
var actualPosition = streamWrapper.Position;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(actualPosition, currPosition);
|
||||
stremMock.Verify(_ => _.Seek(It.Is<long>(offset => offset == expectedOffset), It.Is<int>(dworigin => dworigin == expectedDwOrigin), It.IsAny<IntPtr>()), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldCallIStreamSeek_WhenSetPosition()
|
||||
{
|
||||
// Arrange
|
||||
long positionToSet = 5;
|
||||
int expectedDwOrigin = 0; // STREAM_SEEK_SET
|
||||
var stremMock = new Mock<IStream>();
|
||||
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object)
|
||||
{
|
||||
// Act
|
||||
Position = positionToSet,
|
||||
};
|
||||
|
||||
// Assert
|
||||
stremMock.Verify(_ => _.Seek(It.Is<long>(offset => offset == positionToSet), It.Is<int>(dworigin => dworigin == expectedDwOrigin), It.IsAny<IntPtr>()), Times.Once);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(0L, SeekOrigin.Begin)]
|
||||
[DataRow(5L, SeekOrigin.Begin)]
|
||||
[DataRow(0L, SeekOrigin.Current)]
|
||||
[DataRow(5L, SeekOrigin.Current)]
|
||||
[DataRow(0L, SeekOrigin.End)]
|
||||
[DataRow(5L, SeekOrigin.End)]
|
||||
public void StreamWrapper_ShouldCallIStreamSeekWithValidArguments_WhenSeekCalled(long offset, SeekOrigin origin)
|
||||
{
|
||||
// Arrange
|
||||
int expectedDwOrigin = 0;
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
expectedDwOrigin = 0;
|
||||
break;
|
||||
|
||||
case SeekOrigin.Current:
|
||||
expectedDwOrigin = 1;
|
||||
break;
|
||||
|
||||
case SeekOrigin.End:
|
||||
expectedDwOrigin = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
var stremMock = new Mock<IStream>();
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
streamWrapper.Seek(offset, origin);
|
||||
|
||||
// Assert
|
||||
stremMock.Verify(_ => _.Seek(It.Is<long>(actualOffset => actualOffset == offset), It.Is<int>(actualDwOrigin => actualDwOrigin == expectedDwOrigin), It.IsAny<IntPtr>()), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldReturnValidPosition_WhenSeekCalled()
|
||||
{
|
||||
// Arrange
|
||||
long position = 5;
|
||||
var stremMock = new Mock<IStream>();
|
||||
|
||||
stremMock
|
||||
.Setup(x => x.Seek(It.IsAny<long>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Callback<long, int, IntPtr>((dlibMove, dwOrigin, plibNewPosition) =>
|
||||
{
|
||||
Marshal.WriteInt64(plibNewPosition, position);
|
||||
});
|
||||
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
var actualPosition = streamWrapper.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(position, actualPosition);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(10, -1, 5)]
|
||||
[DataRow(10, 0, -5)]
|
||||
[DataRow(10, 0, 11)]
|
||||
[DataRow(10, 5, 6)]
|
||||
public void StreamWrapper_ShouldThrow_WhenReadCalledWithOutOfRangeArguments(int bufferLength, int offSet, int bytesToRead)
|
||||
{
|
||||
// Arrange
|
||||
var buffer = new byte[bufferLength];
|
||||
var stremMock = new Mock<IStream>();
|
||||
ArgumentOutOfRangeException exception = null;
|
||||
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
streamWrapper.Read(buffer, offSet, bytesToRead);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(5, 0)]
|
||||
[DataRow(5, 5)]
|
||||
[DataRow(0, 5)]
|
||||
public void StreamWrapper_ShouldSetValidBuffer_WhenReadCalled(int count, int offset)
|
||||
{
|
||||
// Arrange
|
||||
var inputBuffer = new byte[1024];
|
||||
var streamBytes = new byte[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
streamBytes[i] = (byte)i;
|
||||
}
|
||||
|
||||
var stremMock = new Mock<IStream>();
|
||||
|
||||
stremMock
|
||||
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Callback<byte[], int, IntPtr>((buffer, countToRead, bytesReadPtr) =>
|
||||
{
|
||||
Array.Copy(streamBytes, 0, buffer, 0, streamBytes.Length);
|
||||
Marshal.WriteInt32(bytesReadPtr, count);
|
||||
});
|
||||
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
|
||||
// Act
|
||||
var bytesRead = streamWrapper.Read(inputBuffer, offset, count);
|
||||
|
||||
// Assert
|
||||
CollectionAssert.AreEqual(streamBytes, inputBuffer.Skip(offset).Take(count).ToArray());
|
||||
Assert.AreEqual(count, bytesRead);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldThrowNotImplementedException_WhenFlushCalled()
|
||||
{
|
||||
// Arrange
|
||||
var stremMock = new Mock<IStream>();
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
NotImplementedException exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
streamWrapper.Flush();
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldThrowNotImplementedException_WhenSetLengthCalled()
|
||||
{
|
||||
// Arrange
|
||||
var stremMock = new Mock<IStream>();
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
NotImplementedException exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
streamWrapper.SetLength(5);
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StreamWrapper_ShouldThrowNotImplementedException_WhenWriteCalled()
|
||||
{
|
||||
// Arrange
|
||||
var stremMock = new Mock<IStream>();
|
||||
var streamWrapper = new StreamWrapper(stremMock.Object);
|
||||
NotImplementedException exception = null;
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
streamWrapper.Write(new byte[5], 0, 0);
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,268 +1,268 @@
|
||||
// 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;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using PreviewHandlerCommon;
|
||||
using SvgPreviewHandler;
|
||||
|
||||
namespace UnitTests_SvgPreviewHandler
|
||||
{
|
||||
[TestClass]
|
||||
public class SvgPreviewControlTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldAddExtendedBrowserControl_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(svgPreviewControl.Controls.Count, 1);
|
||||
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebBrowserExt));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldSetDocumentStream_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(((WebBrowser)svgPreviewControl.Controls[0]).DocumentStream);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldDisableWebBrowserContextMenu_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).IsWebBrowserContextMenuEnabled, false);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldFillDockForWebBrowser_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).Dock, DockStyle.Fill);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldSetScriptErrorsSuppressedProperty_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).ScriptErrorsSuppressed, true);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldSetScrollBarsEnabledProperty_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).ScrollBarsEnabled, true);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldDisableAllowNavigation_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).AllowNavigation, false);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldAddValidInfoBar_IfSvgPreviewThrows()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
var mockStream = new Mock<IStream>();
|
||||
mockStream
|
||||
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Throws(new Exception());
|
||||
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(mockStream.Object);
|
||||
var textBox = svgPreviewControl.Controls[0] as RichTextBox;
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(textBox.Text));
|
||||
Assert.AreEqual(svgPreviewControl.Controls.Count, 1);
|
||||
Assert.AreEqual(textBox.Dock, DockStyle.Top);
|
||||
Assert.AreEqual(textBox.BackColor, Color.LightYellow);
|
||||
Assert.IsTrue(textBox.Multiline);
|
||||
Assert.IsTrue(textBox.ReadOnly);
|
||||
Assert.AreEqual(textBox.ScrollBars, RichTextBoxScrollBars.None);
|
||||
Assert.AreEqual(textBox.BorderStyle, BorderStyle.None);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_InfoBarWidthShouldAdjustWithParentControlWidthChanges_IfSvgPreviewThrows()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
var mockStream = new Mock<IStream>();
|
||||
mockStream
|
||||
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Throws(new Exception());
|
||||
svgPreviewControl.DoPreview(mockStream.Object);
|
||||
var textBox = svgPreviewControl.Controls[0] as RichTextBox;
|
||||
var incrementParentControlWidth = 5;
|
||||
var initialParentWidth = svgPreviewControl.Width;
|
||||
var initialTextBoxWidth = textBox.Width;
|
||||
var finalParentWidth = initialParentWidth + incrementParentControlWidth;
|
||||
|
||||
// Act
|
||||
svgPreviewControl.Width += incrementParentControlWidth;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(initialParentWidth, initialTextBoxWidth);
|
||||
Assert.AreEqual(finalParentWidth, textBox.Width);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldAddTextBox_IfBlockedElementsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg width =\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
|
||||
svgBuilder.AppendLine("\t<script>alert(\"hello\")</script>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream(svgBuilder.ToString()));
|
||||
|
||||
// Assert
|
||||
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(RichTextBox));
|
||||
Assert.IsInstanceOfType(svgPreviewControl.Controls[1], typeof(WebBrowserExt));
|
||||
Assert.AreEqual(svgPreviewControl.Controls.Count, 2);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldNotAddTextBox_IfNoBlockedElementsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
|
||||
svgBuilder.AppendLine("\t<circle cx=\"50\" cy=\"50\" r=\"50\">");
|
||||
svgBuilder.AppendLine("\t</circle>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream(svgBuilder.ToString()));
|
||||
|
||||
// Assert
|
||||
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebBrowserExt));
|
||||
Assert.AreEqual(svgPreviewControl.Controls.Count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_InfoBarWidthShouldAdjustWithParentControlWidthChanges_IfBlockedElementsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg width =\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
|
||||
svgBuilder.AppendLine("\t<script>alert(\"hello\")</script>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
svgPreviewControl.DoPreview(GetMockStream(svgBuilder.ToString()));
|
||||
var textBox = svgPreviewControl.Controls[0] as RichTextBox;
|
||||
var incrementParentControlWidth = 5;
|
||||
var initialParentWidth = svgPreviewControl.Width;
|
||||
var initialTextBoxWidth = textBox.Width;
|
||||
var finalParentWidth = initialParentWidth + incrementParentControlWidth;
|
||||
|
||||
// Act
|
||||
svgPreviewControl.Width += incrementParentControlWidth;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(initialParentWidth, initialTextBoxWidth);
|
||||
Assert.AreEqual(finalParentWidth, textBox.Width);
|
||||
}
|
||||
}
|
||||
|
||||
private IStream GetMockStream(string streamData)
|
||||
{
|
||||
var mockStream = new Mock<IStream>();
|
||||
var streamBytes = Encoding.UTF8.GetBytes(streamData);
|
||||
|
||||
var streamMock = new Mock<IStream>();
|
||||
var firstCall = true;
|
||||
streamMock
|
||||
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Callback<byte[], int, IntPtr>((buffer, countToRead, bytesReadPtr) =>
|
||||
{
|
||||
if (firstCall)
|
||||
{
|
||||
Array.Copy(streamBytes, 0, buffer, 0, streamBytes.Length);
|
||||
Marshal.WriteInt32(bytesReadPtr, streamBytes.Length);
|
||||
firstCall = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Marshal.WriteInt32(bytesReadPtr, 0);
|
||||
}
|
||||
});
|
||||
|
||||
return streamMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using PreviewHandlerCommon;
|
||||
using SvgPreviewHandler;
|
||||
|
||||
namespace UnitTests_SvgPreviewHandler
|
||||
{
|
||||
[TestClass]
|
||||
public class SvgPreviewControlTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldAddExtendedBrowserControl_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(svgPreviewControl.Controls.Count, 1);
|
||||
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebBrowserExt));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldSetDocumentStream_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(((WebBrowser)svgPreviewControl.Controls[0]).DocumentStream);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldDisableWebBrowserContextMenu_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).IsWebBrowserContextMenuEnabled, false);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldFillDockForWebBrowser_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).Dock, DockStyle.Fill);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldSetScriptErrorsSuppressedProperty_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).ScriptErrorsSuppressed, true);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldSetScrollBarsEnabledProperty_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).ScrollBarsEnabled, true);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldDisableAllowNavigation_WhenDoPreviewCalled()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream("<svg></svg>"));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).AllowNavigation, false);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldAddValidInfoBar_IfSvgPreviewThrows()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
var mockStream = new Mock<IStream>();
|
||||
mockStream
|
||||
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Throws(new Exception());
|
||||
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(mockStream.Object);
|
||||
var textBox = svgPreviewControl.Controls[0] as RichTextBox;
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(textBox.Text));
|
||||
Assert.AreEqual(svgPreviewControl.Controls.Count, 1);
|
||||
Assert.AreEqual(textBox.Dock, DockStyle.Top);
|
||||
Assert.AreEqual(textBox.BackColor, Color.LightYellow);
|
||||
Assert.IsTrue(textBox.Multiline);
|
||||
Assert.IsTrue(textBox.ReadOnly);
|
||||
Assert.AreEqual(textBox.ScrollBars, RichTextBoxScrollBars.None);
|
||||
Assert.AreEqual(textBox.BorderStyle, BorderStyle.None);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_InfoBarWidthShouldAdjustWithParentControlWidthChanges_IfSvgPreviewThrows()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
var mockStream = new Mock<IStream>();
|
||||
mockStream
|
||||
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Throws(new Exception());
|
||||
svgPreviewControl.DoPreview(mockStream.Object);
|
||||
var textBox = svgPreviewControl.Controls[0] as RichTextBox;
|
||||
var incrementParentControlWidth = 5;
|
||||
var initialParentWidth = svgPreviewControl.Width;
|
||||
var initialTextBoxWidth = textBox.Width;
|
||||
var finalParentWidth = initialParentWidth + incrementParentControlWidth;
|
||||
|
||||
// Act
|
||||
svgPreviewControl.Width += incrementParentControlWidth;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(initialParentWidth, initialTextBoxWidth);
|
||||
Assert.AreEqual(finalParentWidth, textBox.Width);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldAddTextBox_IfBlockedElementsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg width =\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
|
||||
svgBuilder.AppendLine("\t<script>alert(\"hello\")</script>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream(svgBuilder.ToString()));
|
||||
|
||||
// Assert
|
||||
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(RichTextBox));
|
||||
Assert.IsInstanceOfType(svgPreviewControl.Controls[1], typeof(WebBrowserExt));
|
||||
Assert.AreEqual(svgPreviewControl.Controls.Count, 2);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_ShouldNotAddTextBox_IfNoBlockedElementsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
|
||||
svgBuilder.AppendLine("\t<circle cx=\"50\" cy=\"50\" r=\"50\">");
|
||||
svgBuilder.AppendLine("\t</circle>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
|
||||
// Act
|
||||
svgPreviewControl.DoPreview(GetMockStream(svgBuilder.ToString()));
|
||||
|
||||
// Assert
|
||||
Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebBrowserExt));
|
||||
Assert.AreEqual(svgPreviewControl.Controls.Count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SvgPreviewControl_InfoBarWidthShouldAdjustWithParentControlWidthChanges_IfBlockedElementsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
using (var svgPreviewControl = new SvgPreviewControl())
|
||||
{
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg width =\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
|
||||
svgBuilder.AppendLine("\t<script>alert(\"hello\")</script>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
svgPreviewControl.DoPreview(GetMockStream(svgBuilder.ToString()));
|
||||
var textBox = svgPreviewControl.Controls[0] as RichTextBox;
|
||||
var incrementParentControlWidth = 5;
|
||||
var initialParentWidth = svgPreviewControl.Width;
|
||||
var initialTextBoxWidth = textBox.Width;
|
||||
var finalParentWidth = initialParentWidth + incrementParentControlWidth;
|
||||
|
||||
// Act
|
||||
svgPreviewControl.Width += incrementParentControlWidth;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(initialParentWidth, initialTextBoxWidth);
|
||||
Assert.AreEqual(finalParentWidth, textBox.Width);
|
||||
}
|
||||
}
|
||||
|
||||
private IStream GetMockStream(string streamData)
|
||||
{
|
||||
var mockStream = new Mock<IStream>();
|
||||
var streamBytes = Encoding.UTF8.GetBytes(streamData);
|
||||
|
||||
var streamMock = new Mock<IStream>();
|
||||
var firstCall = true;
|
||||
streamMock
|
||||
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
|
||||
.Callback<byte[], int, IntPtr>((buffer, countToRead, bytesReadPtr) =>
|
||||
{
|
||||
if (firstCall)
|
||||
{
|
||||
Array.Copy(streamBytes, 0, buffer, 0, streamBytes.Length);
|
||||
Marshal.WriteInt32(bytesReadPtr, streamBytes.Length);
|
||||
firstCall = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Marshal.WriteInt32(bytesReadPtr, 0);
|
||||
}
|
||||
});
|
||||
|
||||
return streamMock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +1,102 @@
|
||||
// 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.Text;
|
||||
using Common.Utilities;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests_SvgPreviewHandler
|
||||
{
|
||||
[TestClass]
|
||||
public class SvgPreviewHandlerHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void CheckBlockedElements_ShouldReturnTrue_IfABlockedElementIsPresent()
|
||||
{
|
||||
// Arrange
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg width =\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
|
||||
svgBuilder.AppendLine("\t<script>alert(\"hello\")</script>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
bool foundFilteredElement;
|
||||
|
||||
// Act
|
||||
foundFilteredElement = SvgPreviewHandlerHelper.CheckBlockedElements(svgBuilder.ToString());
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(foundFilteredElement);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckBlockedElements_ShouldReturnTrue_IfBlockedElementsIsPresentInNestedLevel()
|
||||
{
|
||||
// Arrange
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
|
||||
svgBuilder.AppendLine("\t<circle cx=\"50\" cy=\"50\" r=\"50\">");
|
||||
svgBuilder.AppendLine("\t\t<script>alert(\"valid-message\")</script>");
|
||||
svgBuilder.AppendLine("\t</circle>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
bool foundFilteredElement;
|
||||
|
||||
// Act
|
||||
foundFilteredElement = SvgPreviewHandlerHelper.CheckBlockedElements(svgBuilder.ToString());
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(foundFilteredElement);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckBlockedElements_ShouldReturnTrue_IfMultipleBlockedElementsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg width =\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
|
||||
svgBuilder.AppendLine("\t<script>alert(\"valid-message\")</script>");
|
||||
svgBuilder.AppendLine("\t<image href=\"valid-url\" height=\"200\" width=\"200\"/>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
bool foundFilteredElement;
|
||||
|
||||
// Act
|
||||
foundFilteredElement = SvgPreviewHandlerHelper.CheckBlockedElements(svgBuilder.ToString());
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(foundFilteredElement);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckBlockedElements_ShouldReturnFalse_IfNoBlockedElementsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
|
||||
svgBuilder.AppendLine("\t<circle cx=\"50\" cy=\"50\" r=\"50\">");
|
||||
svgBuilder.AppendLine("\t</circle>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
bool foundFilteredElement;
|
||||
|
||||
// Act
|
||||
foundFilteredElement = SvgPreviewHandlerHelper.CheckBlockedElements(svgBuilder.ToString());
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(foundFilteredElement);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[DataRow(null)]
|
||||
public void CheckBlockedElements_ShouldReturnFalse_IfSvgDataIsNullOrWhiteSpaces(string svgData)
|
||||
{
|
||||
// Arrange
|
||||
bool foundFilteredElement;
|
||||
|
||||
// Act
|
||||
foundFilteredElement = SvgPreviewHandlerHelper.CheckBlockedElements(svgData);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(foundFilteredElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.Text;
|
||||
using Common.Utilities;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests_SvgPreviewHandler
|
||||
{
|
||||
[TestClass]
|
||||
public class SvgPreviewHandlerHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void CheckBlockedElements_ShouldReturnTrue_IfABlockedElementIsPresent()
|
||||
{
|
||||
// Arrange
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg width =\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
|
||||
svgBuilder.AppendLine("\t<script>alert(\"hello\")</script>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
bool foundFilteredElement;
|
||||
|
||||
// Act
|
||||
foundFilteredElement = SvgPreviewHandlerHelper.CheckBlockedElements(svgBuilder.ToString());
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(foundFilteredElement);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckBlockedElements_ShouldReturnTrue_IfBlockedElementsIsPresentInNestedLevel()
|
||||
{
|
||||
// Arrange
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
|
||||
svgBuilder.AppendLine("\t<circle cx=\"50\" cy=\"50\" r=\"50\">");
|
||||
svgBuilder.AppendLine("\t\t<script>alert(\"valid-message\")</script>");
|
||||
svgBuilder.AppendLine("\t</circle>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
bool foundFilteredElement;
|
||||
|
||||
// Act
|
||||
foundFilteredElement = SvgPreviewHandlerHelper.CheckBlockedElements(svgBuilder.ToString());
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(foundFilteredElement);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckBlockedElements_ShouldReturnTrue_IfMultipleBlockedElementsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg width =\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
|
||||
svgBuilder.AppendLine("\t<script>alert(\"valid-message\")</script>");
|
||||
svgBuilder.AppendLine("\t<image href=\"valid-url\" height=\"200\" width=\"200\"/>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
bool foundFilteredElement;
|
||||
|
||||
// Act
|
||||
foundFilteredElement = SvgPreviewHandlerHelper.CheckBlockedElements(svgBuilder.ToString());
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(foundFilteredElement);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckBlockedElements_ShouldReturnFalse_IfNoBlockedElementsArePresent()
|
||||
{
|
||||
// Arrange
|
||||
var svgBuilder = new StringBuilder();
|
||||
svgBuilder.AppendLine("<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">");
|
||||
svgBuilder.AppendLine("\t<circle cx=\"50\" cy=\"50\" r=\"50\">");
|
||||
svgBuilder.AppendLine("\t</circle>");
|
||||
svgBuilder.AppendLine("</svg>");
|
||||
bool foundFilteredElement;
|
||||
|
||||
// Act
|
||||
foundFilteredElement = SvgPreviewHandlerHelper.CheckBlockedElements(svgBuilder.ToString());
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(foundFilteredElement);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[DataRow(null)]
|
||||
public void CheckBlockedElements_ShouldReturnFalse_IfSvgDataIsNullOrWhiteSpaces(string svgData)
|
||||
{
|
||||
// Arrange
|
||||
bool foundFilteredElement;
|
||||
|
||||
// Act
|
||||
foundFilteredElement = SvgPreviewHandlerHelper.CheckBlockedElements(svgData);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(foundFilteredElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,242 +1,242 @@
|
||||
// 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;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
namespace Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps <see cref="IStream"/> interface into <see cref="Stream"/> Class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implements only read from the stream functionality.
|
||||
/// </remarks>
|
||||
public class StreamWrapper : Stream
|
||||
{
|
||||
private IStream stream;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StreamWrapper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="stream">A pointer to an <see cref="IStream" /> interface that represents the stream source.</param>
|
||||
public StreamWrapper(IStream stream)
|
||||
{
|
||||
this.stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current stream supports reading.
|
||||
/// </summary>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current stream supports seeking.
|
||||
/// </summary>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current stream supports writing.
|
||||
/// </summary>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length in bytes of the stream.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
this.CheckDisposed();
|
||||
System.Runtime.InteropServices.ComTypes.STATSTG stat;
|
||||
|
||||
// Stat called with STATFLAG_NONAME. The pwcsName is not required more details https://docs.microsoft.com/en-us/windows/win32/api/wtypes/ne-wtypes-statflag
|
||||
this.stream.Stat(out stat, 1); // STATFLAG_NONAME
|
||||
|
||||
return stat.cbSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the position within the current.
|
||||
/// </summary>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Seek(0, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.Seek(value, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
|
||||
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
|
||||
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero if the end of the stream has been reached.</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.CheckDisposed();
|
||||
|
||||
if (offset < 0 || count < 0 || offset + count > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
byte[] localBuffer = buffer;
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
localBuffer = new byte[count];
|
||||
}
|
||||
|
||||
IntPtr bytesReadPtr = Marshal.AllocCoTaskMem(sizeof(int));
|
||||
|
||||
try
|
||||
{
|
||||
this.stream.Read(localBuffer, count, bytesReadPtr);
|
||||
int bytesRead = Marshal.ReadInt32(bytesReadPtr);
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
Array.Copy(localBuffer, 0, buffer, offset, bytesRead);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeCoTaskMem(bytesReadPtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the position within the current stream.
|
||||
/// </summary>
|
||||
/// <param name="offset">A byte offset relative to the origin parameter.</param>
|
||||
/// <param name="origin">A value of type System.IO.SeekOrigin indicating the reference point used to obtain the new position.</param>
|
||||
/// <returns>The new position within the current stream.</returns>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
this.CheckDisposed();
|
||||
int dwOrigin;
|
||||
|
||||
// Maps the SeekOrigin with dworigin more details: https://docs.microsoft.com/en-us/windows/win32/api/objidl/ne-objidl-stream_seek
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
dwOrigin = 0; // STREAM_SEEK_SET
|
||||
break;
|
||||
|
||||
case SeekOrigin.Current:
|
||||
dwOrigin = 1; // STREAM_SEEK_CUR
|
||||
break;
|
||||
|
||||
case SeekOrigin.End:
|
||||
dwOrigin = 2; // STREAM_SEEK_END
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
IntPtr posPtr = Marshal.AllocCoTaskMem(sizeof(long));
|
||||
|
||||
try
|
||||
{
|
||||
this.stream.Seek(offset, dwOrigin, posPtr);
|
||||
return Marshal.ReadInt64(posPtr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeCoTaskMem(posPtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not implemented current implementation supports only read.
|
||||
/// </remarks>
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the length of the current stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The desired length of the current stream in bytes.</param>
|
||||
/// /// <remarks>
|
||||
/// Not implemented current implementation supports only read.
|
||||
/// </remarks>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
|
||||
/// <param name="count">The number of bytes to be written to the current stream.</param>
|
||||
/// <remarks>
|
||||
/// Not implemented current implementation supports only read.
|
||||
/// </remarks>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (this.stream != null)
|
||||
{
|
||||
if (Marshal.IsComObject(this.stream))
|
||||
{
|
||||
Marshal.ReleaseComObject(this.stream);
|
||||
}
|
||||
|
||||
this.stream = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (this.stream == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(StreamWrapper));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
namespace Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps <see cref="IStream"/> interface into <see cref="Stream"/> Class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implements only read from the stream functionality.
|
||||
/// </remarks>
|
||||
public class StreamWrapper : Stream
|
||||
{
|
||||
private IStream stream;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StreamWrapper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="stream">A pointer to an <see cref="IStream" /> interface that represents the stream source.</param>
|
||||
public StreamWrapper(IStream stream)
|
||||
{
|
||||
this.stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current stream supports reading.
|
||||
/// </summary>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current stream supports seeking.
|
||||
/// </summary>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current stream supports writing.
|
||||
/// </summary>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length in bytes of the stream.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
this.CheckDisposed();
|
||||
System.Runtime.InteropServices.ComTypes.STATSTG stat;
|
||||
|
||||
// Stat called with STATFLAG_NONAME. The pwcsName is not required more details https://docs.microsoft.com/en-us/windows/win32/api/wtypes/ne-wtypes-statflag
|
||||
this.stream.Stat(out stat, 1); // STATFLAG_NONAME
|
||||
|
||||
return stat.cbSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the position within the current.
|
||||
/// </summary>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Seek(0, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.Seek(value, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
|
||||
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
|
||||
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero if the end of the stream has been reached.</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.CheckDisposed();
|
||||
|
||||
if (offset < 0 || count < 0 || offset + count > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
byte[] localBuffer = buffer;
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
localBuffer = new byte[count];
|
||||
}
|
||||
|
||||
IntPtr bytesReadPtr = Marshal.AllocCoTaskMem(sizeof(int));
|
||||
|
||||
try
|
||||
{
|
||||
this.stream.Read(localBuffer, count, bytesReadPtr);
|
||||
int bytesRead = Marshal.ReadInt32(bytesReadPtr);
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
Array.Copy(localBuffer, 0, buffer, offset, bytesRead);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeCoTaskMem(bytesReadPtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the position within the current stream.
|
||||
/// </summary>
|
||||
/// <param name="offset">A byte offset relative to the origin parameter.</param>
|
||||
/// <param name="origin">A value of type System.IO.SeekOrigin indicating the reference point used to obtain the new position.</param>
|
||||
/// <returns>The new position within the current stream.</returns>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
this.CheckDisposed();
|
||||
int dwOrigin;
|
||||
|
||||
// Maps the SeekOrigin with dworigin more details: https://docs.microsoft.com/en-us/windows/win32/api/objidl/ne-objidl-stream_seek
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
dwOrigin = 0; // STREAM_SEEK_SET
|
||||
break;
|
||||
|
||||
case SeekOrigin.Current:
|
||||
dwOrigin = 1; // STREAM_SEEK_CUR
|
||||
break;
|
||||
|
||||
case SeekOrigin.End:
|
||||
dwOrigin = 2; // STREAM_SEEK_END
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
IntPtr posPtr = Marshal.AllocCoTaskMem(sizeof(long));
|
||||
|
||||
try
|
||||
{
|
||||
this.stream.Seek(offset, dwOrigin, posPtr);
|
||||
return Marshal.ReadInt64(posPtr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeCoTaskMem(posPtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not implemented current implementation supports only read.
|
||||
/// </remarks>
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the length of the current stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The desired length of the current stream in bytes.</param>
|
||||
/// /// <remarks>
|
||||
/// Not implemented current implementation supports only read.
|
||||
/// </remarks>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
|
||||
/// <param name="count">The number of bytes to be written to the current stream.</param>
|
||||
/// <remarks>
|
||||
/// Not implemented current implementation supports only read.
|
||||
/// </remarks>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (this.stream != null)
|
||||
{
|
||||
if (Marshal.IsComObject(this.stream))
|
||||
{
|
||||
Marshal.ReleaseComObject(this.stream);
|
||||
}
|
||||
|
||||
this.stream = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (this.stream == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(StreamWrapper));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
// 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.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// The COLORREF value is used to specify an RGB color.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct COLORREF
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores an RGB color value in a 32 bit integer.
|
||||
/// </summary>
|
||||
public uint Dword;
|
||||
|
||||
/// <summary>
|
||||
/// Gets RGB value stored in <see cref="Dword"/> in <see cref="Color"/> structure.
|
||||
/// </summary>
|
||||
public Color Color
|
||||
{
|
||||
get
|
||||
{
|
||||
return Color.FromArgb(
|
||||
(int)(0x000000FFU & this.Dword),
|
||||
(int)(0x0000FF00U & this.Dword) >> 8,
|
||||
(int)(0x00FF0000U & this.Dword) >> 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// The COLORREF value is used to specify an RGB color.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct COLORREF
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores an RGB color value in a 32 bit integer.
|
||||
/// </summary>
|
||||
public uint Dword;
|
||||
|
||||
/// <summary>
|
||||
/// Gets RGB value stored in <see cref="Dword"/> in <see cref="Color"/> structure.
|
||||
/// </summary>
|
||||
public Color Color
|
||||
{
|
||||
get
|
||||
{
|
||||
return Color.FromArgb(
|
||||
(int)(0x000000FFU & this.Dword),
|
||||
(int)(0x0000FF00U & this.Dword) >> 8,
|
||||
(int)(0x00FF0000U & this.Dword) >> 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.Cominterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes a method to initialize a handler, such as a property handler, thumbnail handler, or preview handler, with a file path.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("b7d14566-0509-4cce-a71f-0a554233bd9b")]
|
||||
public interface IInitializeWithFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a handler with a file path.
|
||||
/// </summary>
|
||||
/// <param name="pszFilePath">File Path.</param>
|
||||
/// <param name="grfMode">Indicate the Access Mode either STGM_READ (Read Only Access) or STGM_READWRITE (Read and Write Access).</param>
|
||||
void Initialize([MarshalAs(UnmanagedType.LPWStr)] string pszFilePath, uint grfMode);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.Cominterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes a method to initialize a handler, such as a property handler, thumbnail handler, or preview handler, with a file path.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("b7d14566-0509-4cce-a71f-0a554233bd9b")]
|
||||
public interface IInitializeWithFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a handler with a file path.
|
||||
/// </summary>
|
||||
/// <param name="pszFilePath">File Path.</param>
|
||||
/// <param name="grfMode">Indicate the Access Mode either STGM_READ (Read Only Access) or STGM_READWRITE (Read and Write Access).</param>
|
||||
void Initialize([MarshalAs(UnmanagedType.LPWStr)] string pszFilePath, uint grfMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes a method that initializes a handler, such as a property handler, thumbnail handler, or preview handler, with a stream.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f")]
|
||||
public interface IInitializeWithStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a handler with a stream.
|
||||
/// </summary>
|
||||
/// <param name="pstream">A pointer to an <see cref="IStream" /> interface that represents the stream source.</param>
|
||||
/// <param name="grfMode">One of the <see href="https://docs.microsoft.com/en-us/windows/win32/stg/stgm-constants" >STGM</see> values that indicates the access mode for <paramref name="pstream"/>.</param>
|
||||
void Initialize(IStream pstream, uint grfMode);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes a method that initializes a handler, such as a property handler, thumbnail handler, or preview handler, with a stream.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f")]
|
||||
public interface IInitializeWithStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a handler with a stream.
|
||||
/// </summary>
|
||||
/// <param name="pstream">A pointer to an <see cref="IStream" /> interface that represents the stream source.</param>
|
||||
/// <param name="grfMode">One of the <see href="https://docs.microsoft.com/en-us/windows/win32/stg/stgm-constants" >STGM</see> values that indicates the access mode for <paramref name="pstream"/>.</param>
|
||||
void Initialize(IStream pstream, uint grfMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a simplified way to support communication between an object and its site in the container.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("fc4801a3-2ba9-11cf-a229-00aa003d7352")]
|
||||
public interface IObjectWithSite
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the site's pointer to the site object.
|
||||
/// </summary>
|
||||
/// <param name="pUnkSite">Address of an interface pointer to the site managing this object.</param>
|
||||
void SetSite([In, MarshalAs(UnmanagedType.IUnknown)] object pUnkSite);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the last site set using the <see cref="SetSite(object)" /> method. In cases where there is no known site, the object returns an exception.
|
||||
/// </summary>
|
||||
/// <param name="riid">Provides the IID of the interface pointer returned in the <paramref name="ppvSite"/> parameter.</param>
|
||||
/// <param name="ppvSite">The address of the caller's void variable in which the object stores the interface pointer of the site last seen in the <see cref="SetSite(object)" />.</param>
|
||||
void GetSite(ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvSite);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a simplified way to support communication between an object and its site in the container.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("fc4801a3-2ba9-11cf-a229-00aa003d7352")]
|
||||
public interface IObjectWithSite
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the site's pointer to the site object.
|
||||
/// </summary>
|
||||
/// <param name="pUnkSite">Address of an interface pointer to the site managing this object.</param>
|
||||
void SetSite([In, MarshalAs(UnmanagedType.IUnknown)] object pUnkSite);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the last site set using the <see cref="SetSite(object)" /> method. In cases where there is no known site, the object returns an exception.
|
||||
/// </summary>
|
||||
/// <param name="riid">Provides the IID of the interface pointer returned in the <paramref name="ppvSite"/> parameter.</param>
|
||||
/// <param name="ppvSite">The address of the caller's void variable in which the object stores the interface pointer of the site last seen in the <see cref="SetSite(object)" />.</param>
|
||||
void GetSite(ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvSite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// The IOleWindow interface provides methods that allow an application to obtain the handle to the various windows that participate
|
||||
/// in in-place activation, and also to enter and exit context-sensitive help mode.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[Guid("00000114-0000-0000-C000-000000000046")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IOleWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves a handle to one of the windows participating in in-place activation (frame, document, parent, or in-place object window).
|
||||
/// </summary>
|
||||
/// <param name="phwnd">A pointer to a variable that receives the window handle.</param>
|
||||
void GetWindow(out IntPtr phwnd);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether context-sensitive help mode should be entered during an in-place activation session.
|
||||
/// </summary>
|
||||
/// <param name="fEnterMode">TRUE if help mode should be entered; FALSE if it should be exited.</param>
|
||||
void ContextSensitiveHelp([MarshalAs(UnmanagedType.Bool)] bool fEnterMode);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// The IOleWindow interface provides methods that allow an application to obtain the handle to the various windows that participate
|
||||
/// in in-place activation, and also to enter and exit context-sensitive help mode.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[Guid("00000114-0000-0000-C000-000000000046")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IOleWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves a handle to one of the windows participating in in-place activation (frame, document, parent, or in-place object window).
|
||||
/// </summary>
|
||||
/// <param name="phwnd">A pointer to a variable that receives the window handle.</param>
|
||||
void GetWindow(out IntPtr phwnd);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether context-sensitive help mode should be entered during an in-place activation session.
|
||||
/// </summary>
|
||||
/// <param name="fEnterMode">TRUE if help mode should be entered; FALSE if it should be exited.</param>
|
||||
void ContextSensitiveHelp([MarshalAs(UnmanagedType.Bool)] bool fEnterMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes methods for the display of rich previews.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("8895b1c6-b41f-4c1c-a562-0d564250836f")]
|
||||
public interface IPreviewHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the parent window of the previewer window, as well as the area within the parent to be used for the previewer window.
|
||||
/// </summary>
|
||||
/// <param name="hwnd">A handle to the parent window.</param>
|
||||
/// <param name="rect">A pointer to a <see cref="RECT"/> defining the area for the previewer.</param>
|
||||
void SetWindow(IntPtr hwnd, ref RECT rect);
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to change the area within the parent hwnd that it draws into.
|
||||
/// </summary>
|
||||
/// <param name="rect">A pointer to a <see cref="RECT"/> to be used for the preview.</param>
|
||||
void SetRect(ref RECT rect);
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to load data from the source specified in an earlier Initialize method call, and to begin rendering to the previewer window.
|
||||
/// </summary>
|
||||
void DoPreview();
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to cease rendering a preview and to release all resources that have been allocated based on the item passed in during the initialization.
|
||||
/// </summary>
|
||||
void Unload();
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to set focus to itself.
|
||||
/// </summary>
|
||||
void SetFocus();
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to return the HWND from calling the GetFocus Function.
|
||||
/// </summary>
|
||||
/// <param name="phwnd">When this method returns, contains a pointer to the HWND returned from calling the GetFocus Function from the preview handler's foreground thread.</param>
|
||||
void QueryFocus(out IntPtr phwnd);
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to handle a keystroke passed up from the message pump of the process in which the preview handler is running.
|
||||
/// </summary>
|
||||
/// <param name="pmsg">A pointer to a window message.</param>
|
||||
/// <returns>If the keystroke message can be processed by the preview handler, the handler will process it and return S_OK(0). If the preview handler cannot process the keystroke message, it
|
||||
/// will offer it to the host using <see cref="IPreviewHandlerFrame.TranslateAccelerator(ref MSG)"/>. If the host processes the message, this method will return S_OK(0). If the host does not process the message, this method will return S_FALSE(1).
|
||||
/// </returns>
|
||||
[PreserveSig]
|
||||
uint TranslateAccelerator(ref MSG pmsg);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes methods for the display of rich previews.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("8895b1c6-b41f-4c1c-a562-0d564250836f")]
|
||||
public interface IPreviewHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the parent window of the previewer window, as well as the area within the parent to be used for the previewer window.
|
||||
/// </summary>
|
||||
/// <param name="hwnd">A handle to the parent window.</param>
|
||||
/// <param name="rect">A pointer to a <see cref="RECT"/> defining the area for the previewer.</param>
|
||||
void SetWindow(IntPtr hwnd, ref RECT rect);
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to change the area within the parent hwnd that it draws into.
|
||||
/// </summary>
|
||||
/// <param name="rect">A pointer to a <see cref="RECT"/> to be used for the preview.</param>
|
||||
void SetRect(ref RECT rect);
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to load data from the source specified in an earlier Initialize method call, and to begin rendering to the previewer window.
|
||||
/// </summary>
|
||||
void DoPreview();
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to cease rendering a preview and to release all resources that have been allocated based on the item passed in during the initialization.
|
||||
/// </summary>
|
||||
void Unload();
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to set focus to itself.
|
||||
/// </summary>
|
||||
void SetFocus();
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to return the HWND from calling the GetFocus Function.
|
||||
/// </summary>
|
||||
/// <param name="phwnd">When this method returns, contains a pointer to the HWND returned from calling the GetFocus Function from the preview handler's foreground thread.</param>
|
||||
void QueryFocus(out IntPtr phwnd);
|
||||
|
||||
/// <summary>
|
||||
/// Directs the preview handler to handle a keystroke passed up from the message pump of the process in which the preview handler is running.
|
||||
/// </summary>
|
||||
/// <param name="pmsg">A pointer to a window message.</param>
|
||||
/// <returns>If the keystroke message can be processed by the preview handler, the handler will process it and return S_OK(0). If the preview handler cannot process the keystroke message, it
|
||||
/// will offer it to the host using <see cref="IPreviewHandlerFrame.TranslateAccelerator(ref MSG)"/>. If the host processes the message, this method will return S_OK(0). If the host does not process the message, this method will return S_FALSE(1).
|
||||
/// </returns>
|
||||
[PreserveSig]
|
||||
uint TranslateAccelerator(ref MSG pmsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables preview handlers to pass keyboard shortcuts to the host. This interface retrieves a list of keyboard shortcuts and directs the host to handle a keyboard shortcut.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("fec87aaf-35f9-447a-adb7-20234491401a")]
|
||||
public interface IPreviewHandlerFrame
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of the keyboard shortcuts for the preview host.
|
||||
/// </summary>
|
||||
/// <param name="pinfo">A pointer to a <see href="https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/ns-shobjidl_core-previewhandlerframeinfo">PREVIEWHANDLERFRAMEINFO</see> structure
|
||||
/// that receives accelerator table information.</param>
|
||||
void GetWindowContext(IntPtr pinfo);
|
||||
|
||||
/// <summary>
|
||||
/// Directs the host to handle an keyboard shortcut passed from the preview handler.
|
||||
/// </summary>
|
||||
/// <param name="pmsg">A reference to <see cref="MSG"/> that corresponds to a keyboard shortcut.</param>
|
||||
/// <returns>If the keyboard shortcut is one that the host intends to handle, the host will process it and return S_OK(0); otherwise, it returns S_FALSE(1).</returns>
|
||||
[PreserveSig]
|
||||
uint TranslateAccelerator(ref MSG pmsg);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables preview handlers to pass keyboard shortcuts to the host. This interface retrieves a list of keyboard shortcuts and directs the host to handle a keyboard shortcut.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("fec87aaf-35f9-447a-adb7-20234491401a")]
|
||||
public interface IPreviewHandlerFrame
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of the keyboard shortcuts for the preview host.
|
||||
/// </summary>
|
||||
/// <param name="pinfo">A pointer to a <see href="https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/ns-shobjidl_core-previewhandlerframeinfo">PREVIEWHANDLERFRAMEINFO</see> structure
|
||||
/// that receives accelerator table information.</param>
|
||||
void GetWindowContext(IntPtr pinfo);
|
||||
|
||||
/// <summary>
|
||||
/// Directs the host to handle an keyboard shortcut passed from the preview handler.
|
||||
/// </summary>
|
||||
/// <param name="pmsg">A reference to <see cref="MSG"/> that corresponds to a keyboard shortcut.</param>
|
||||
/// <returns>If the keyboard shortcut is one that the host intends to handle, the host will process it and return S_OK(0); otherwise, it returns S_FALSE(1).</returns>
|
||||
[PreserveSig]
|
||||
uint TranslateAccelerator(ref MSG pmsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes methods for applying color and font information to preview handlers.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("8327b13c-b63f-4b24-9b8a-d010dcc3f599")]
|
||||
public interface IPreviewHandlerVisuals
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the background color of the preview handler.
|
||||
/// </summary>
|
||||
/// <param name="color">A value of type <see cref="COLORREF"/> to use for the preview handler background.</param>
|
||||
void SetBackgroundColor(COLORREF color);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the font attributes to be used for text within the preview handler.
|
||||
/// </summary>
|
||||
/// <param name="plf">A pointer to a <see cref="LOGFONT"/> Structure containing the necessary attributes for the font to use.</param>
|
||||
void SetFont(ref LOGFONT plf);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color of the text within the preview handler.
|
||||
/// </summary>
|
||||
/// <param name="color">A value of type <see cref="COLORREF"/> to use for the preview handler text color.</param>
|
||||
void SetTextColor(COLORREF color);
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes methods for applying color and font information to preview handlers.
|
||||
/// </summary>
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("8327b13c-b63f-4b24-9b8a-d010dcc3f599")]
|
||||
public interface IPreviewHandlerVisuals
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the background color of the preview handler.
|
||||
/// </summary>
|
||||
/// <param name="color">A value of type <see cref="COLORREF"/> to use for the preview handler background.</param>
|
||||
void SetBackgroundColor(COLORREF color);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the font attributes to be used for text within the preview handler.
|
||||
/// </summary>
|
||||
/// <param name="plf">A pointer to a <see cref="LOGFONT"/> Structure containing the necessary attributes for the font to use.</param>
|
||||
void SetFont(ref LOGFONT plf);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color of the text within the preview handler.
|
||||
/// </summary>
|
||||
/// <param name="color">A value of type <see cref="COLORREF"/> to use for the preview handler text color.</param>
|
||||
void SetTextColor(COLORREF color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +1,88 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the attributes of a font.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct LOGFONT
|
||||
{
|
||||
/// <summary>
|
||||
/// Value of type INT that specifies the height, in logical units, of the font's character cell or character.
|
||||
/// </summary>
|
||||
public int LfHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type INT that specifies the width, in logical units, of characters in the font.
|
||||
/// </summary>
|
||||
public int LfWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type INT that contains the angle, in tenths of degrees, between the escapement vector and the x-axis of the device. The escapement
|
||||
/// vector is parallel to the base line of a row of text.
|
||||
/// </summary>
|
||||
public int LfEscapement;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type INT that specifies the angle, in tenths of degrees, between each character's base line and the x-axis of the device.
|
||||
/// </summary>
|
||||
public int LfOrientation;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type INT that specifies the weight of the font in the range from 0 through 1000.
|
||||
/// </summary>
|
||||
public int LfWeight;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies an italic font if set to TRUE.
|
||||
/// </summary>
|
||||
public byte LfItalic;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies an underlined font if set to TRUE.
|
||||
/// </summary>
|
||||
public byte LfUnderline;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies a strikeout font if set to TRUE.
|
||||
/// </summary>
|
||||
public byte LfStrikeOut;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies the character set.
|
||||
/// </summary>
|
||||
public byte LfCharSet;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies the output precision. The output precision defines how closely the output must match the requested
|
||||
/// font's height, width, character orientation, escapement, pitch, and font type.
|
||||
/// </summary>
|
||||
public byte LfOutPrecision;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies the clipping precision. The clipping precision defines how to clip characters that are partially outside the clipping region.
|
||||
/// </summary>
|
||||
public byte LfClipPrecision;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies the output quality. The output quality defines how carefully the GDI must attempt to match the logical-font attributes to those of an actual physical font.
|
||||
/// </summary>
|
||||
public byte LfQuality;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies the pitch and family of the font.
|
||||
/// </summary>
|
||||
public byte LfPitchAndFamily;
|
||||
|
||||
/// <summary>
|
||||
/// Array of wide characters that contains a null-terminated string that specifies the typeface name of the font. The length of the string must not exceed 32 characters, including the NULL terminator.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string LfFaceName;
|
||||
}
|
||||
}
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the attributes of a font.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct LOGFONT
|
||||
{
|
||||
/// <summary>
|
||||
/// Value of type INT that specifies the height, in logical units, of the font's character cell or character.
|
||||
/// </summary>
|
||||
public int LfHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type INT that specifies the width, in logical units, of characters in the font.
|
||||
/// </summary>
|
||||
public int LfWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type INT that contains the angle, in tenths of degrees, between the escapement vector and the x-axis of the device. The escapement
|
||||
/// vector is parallel to the base line of a row of text.
|
||||
/// </summary>
|
||||
public int LfEscapement;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type INT that specifies the angle, in tenths of degrees, between each character's base line and the x-axis of the device.
|
||||
/// </summary>
|
||||
public int LfOrientation;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type INT that specifies the weight of the font in the range from 0 through 1000.
|
||||
/// </summary>
|
||||
public int LfWeight;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies an italic font if set to TRUE.
|
||||
/// </summary>
|
||||
public byte LfItalic;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies an underlined font if set to TRUE.
|
||||
/// </summary>
|
||||
public byte LfUnderline;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies a strikeout font if set to TRUE.
|
||||
/// </summary>
|
||||
public byte LfStrikeOut;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies the character set.
|
||||
/// </summary>
|
||||
public byte LfCharSet;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies the output precision. The output precision defines how closely the output must match the requested
|
||||
/// font's height, width, character orientation, escapement, pitch, and font type.
|
||||
/// </summary>
|
||||
public byte LfOutPrecision;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies the clipping precision. The clipping precision defines how to clip characters that are partially outside the clipping region.
|
||||
/// </summary>
|
||||
public byte LfClipPrecision;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies the output quality. The output quality defines how carefully the GDI must attempt to match the logical-font attributes to those of an actual physical font.
|
||||
/// </summary>
|
||||
public byte LfQuality;
|
||||
|
||||
/// <summary>
|
||||
/// Value of type BYTE that specifies the pitch and family of the font.
|
||||
/// </summary>
|
||||
public byte LfPitchAndFamily;
|
||||
|
||||
/// <summary>
|
||||
/// Array of wide characters that contains a null-terminated string that specifies the typeface name of the font. The length of the string must not exceed 32 characters, including the NULL terminator.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string LfFaceName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains message information from a thread's message queue.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MSG
|
||||
{
|
||||
/// <summary>
|
||||
/// A handle to the window whose window procedure receives the message. This member is NULL when the message is a thread message.
|
||||
/// </summary>
|
||||
public IntPtr Hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// The message identifier. Applications can only use the low word; the high word is reserved by the system.
|
||||
/// </summary>
|
||||
public int Message;
|
||||
|
||||
/// <summary>
|
||||
/// Additional information about the message. The exact meaning depends on the value of the message member.
|
||||
/// </summary>
|
||||
public IntPtr WParam;
|
||||
|
||||
/// <summary>
|
||||
/// Additional information about the message. The exact meaning depends on the value of the message member.
|
||||
/// </summary>
|
||||
public IntPtr LParam;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the message was posted.
|
||||
/// </summary>
|
||||
public int Time;
|
||||
|
||||
/// <summary>
|
||||
/// The x coordinate of cursor position, in screen coordinates, when the message was posted.
|
||||
/// </summary>
|
||||
public int PtX;
|
||||
|
||||
/// <summary>
|
||||
/// The y coordinate of cursor position, in screen coordinates, when the message was posted.
|
||||
/// </summary>
|
||||
public int PtY;
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Common.ComInterlop
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains message information from a thread's message queue.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MSG
|
||||
{
|
||||
/// <summary>
|
||||
/// A handle to the window whose window procedure receives the message. This member is NULL when the message is a thread message.
|
||||
/// </summary>
|
||||
public IntPtr Hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// The message identifier. Applications can only use the low word; the high word is reserved by the system.
|
||||
/// </summary>
|
||||
public int Message;
|
||||
|
||||
/// <summary>
|
||||
/// Additional information about the message. The exact meaning depends on the value of the message member.
|
||||
/// </summary>
|
||||
public IntPtr WParam;
|
||||
|
||||
/// <summary>
|
||||
/// Additional information about the message. The exact meaning depends on the value of the message member.
|
||||
/// </summary>
|
||||
public IntPtr LParam;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the message was posted.
|
||||
/// </summary>
|
||||
public int Time;
|
||||
|
||||
/// <summary>
|
||||
/// The x coordinate of cursor position, in screen coordinates, when the message was posted.
|
||||
/// </summary>
|
||||
public int PtX;
|
||||
|
||||
/// <summary>
|
||||
/// The y coordinate of cursor position, in screen coordinates, when the message was posted.
|
||||
/// </summary>
|
||||
public int PtY;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user