Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Automatic repositioning of the tray context menu #86

Merged
merged 2 commits into from
Apr 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
119 changes: 119 additions & 0 deletions KeeTrayTOTP.Tests/TrayContextMenuPositionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System.Collections.Generic;
using System.Drawing;
using KeeTrayTOTP.Libraries;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace KeeTrayTOTP.Tests
{
[TestClass]
public class TrayContextMenuPositionTests
{
[DataTestMethod]
[DynamicData(nameof(DropDownLocationTestData))]
public void CalculatedLocationForDropDown_Should_ReturnTheCorrectLocation(Size controlSize, Point mousePosition,
Rectangle screenRect, Point expectedPoint)
{
var dropDownLocationCalculator = new DropDownLocationCalculator(controlSize);
var sut = dropDownLocationCalculator.CalculateLocationForDropDown(mousePosition, screenRect);

Assert.AreEqual(expectedPoint, sut);
}

private static IEnumerable<object[]> DropDownLocationTestData
{
get
{
var controlSize = new Size(300, 300);
var screenRect = new Rectangle(0, 0, 1920, 1080);

// top-left
yield return new object[]
{
controlSize, new Point(400, 500), screenRect,
// expected
new Point(400 - controlSize.Width, 500 - controlSize.Height)
};

// top-right
yield return new object[]
{
controlSize, new Point(200, 500), screenRect,
// expected
new Point(200, 500 - controlSize.Height)
};

// bottom-right
yield return new object[]
{
controlSize, new Point(200, 200), screenRect,
// expected
new Point(200, 200)
};

// bottom-left
yield return new object[]
{
controlSize, new Point(400, 200), screenRect,
// expected
new Point(400 - controlSize.Width, 200)
};

// top-left
yield return new object[]
{
controlSize, new Point(300, 300), screenRect,
// expected
new Point(0, 0)
};

// top-left
yield return new object[]
{
controlSize, new Point(screenRect.Width, screenRect.Height), screenRect,
// expected
new Point(screenRect.Width - controlSize.Width, screenRect.Height - controlSize.Height)
};

// edge case control size bigger than screen size
yield return new object[]
{
controlSize, new Point(0, 0), new Rectangle(0, 0, 200, 200),
// expected
new Point(0, 200 - controlSize.Height)
};

// edge case control height bigger than screen height
yield return new object[]
{
controlSize, new Point(50, 0), new Rectangle(0, 0, 1920, 200),
// expected
new Point(50, 200 - controlSize.Height)
};

// edge case control width bigger than screen width
yield return new object[]
{
controlSize, new Point(0, 0), new Rectangle(0, 0, 200, 1080),
// expected
new Point(0, 0)
};

// multi monitor test
yield return new object[]
{
controlSize, new Point(2500, 500), screenRect,
// expected
new Point(2500 - controlSize.Width, 500 - controlSize.Height)
};

// multi monitor test control bigger than screen
yield return new object[]
{
controlSize, new Point(1200, 150), new Rectangle(1080, 0, 200, 200),
// expected
new Point(1080, 200 - controlSize.Height)
};
}
}
}
}
1 change: 1 addition & 0 deletions KeeTrayTOTP/KeeTrayTOTP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Libraries\DropDownLocationCalculator.cs" />
<Compile Include="FormAbout.cs">
<SubType>Form</SubType>
</Compile>
Expand Down
120 changes: 120 additions & 0 deletions KeeTrayTOTP/Libraries/DropDownLocationCalculator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System.Drawing;
using System.Windows.Forms;

namespace KeeTrayTOTP.Libraries
{
/// <summary>
/// Calculator for the location of a dropdown control based on the location of the mouse and screen space available
/// </summary>
public class DropDownLocationCalculator
{
private Size _controlSize;

/// <summary>
/// Initializes a calculator which can calculate the location of a dropdown control based on the location of the mouse
/// and the screen space available.
/// </summary>
/// <param name="controlSize">The size of the control which will be dropdown-ed</param>
public DropDownLocationCalculator(Size controlSize)
{
_controlSize = controlSize;
}

/// <summary>
/// Calculate the position for a dropdown control based on the given mouse position.
/// The default behavior is to calculate the location for opening the dropdown to the top-left.
/// </summary>
/// <param name="mousePosition">The position in which the control will be anchored</param>
/// <returns>The new location for the control</returns>
internal Point CalculateLocationForDropDown(Point mousePosition)
{
var screen = Screen.FromPoint(mousePosition);
return CalculateLocationForDropDown(mousePosition, screen.Bounds);
}

/// <summary>
/// Calculate the position for a dropdown control based on the given mouse position and screen rectangle
/// The default behavior is to calculate the location for opening the dropdown to the top-left.
/// </summary>
/// <param name="mousePosition">The position in which the control will be anchored</param>
/// <param name="screenRect">
/// The screen rectangle which is used for the calculation. This should be the screen on which the
/// control will be opened
/// </param>
/// <returns></returns>
internal Point CalculateLocationForDropDown(Point mousePosition, Rectangle screenRect)
{
var screenHeight = screenRect.Height;
var screenWidth = screenRect.Width;

// after calculating the offset, the mouse position is relative to the screen containing the cursor
// this allows for correct calculations even on multi-monitor systems
mousePosition.Offset(-screenRect.X, -screenRect.Y);

int calculatedX = CalculateX(mousePosition, screenWidth);
int calculatedY = CalculateY(mousePosition, screenHeight);

return new Point(calculatedX + screenRect.X, calculatedY + screenRect.Y);
}

private int CalculateY(Point mousePosition, int screenHeight)
{
int calculatedY;
if (IsEnoughSpaceToTheTop(mousePosition))
{
calculatedY = mousePosition.Y - _controlSize.Height;
}
else if (IsEnoughSpaceToTheBottom(mousePosition, screenHeight))
{
calculatedY = mousePosition.Y;
}
else
{
// the control height is higher than the screen height
calculatedY = screenHeight - _controlSize.Height;
}

return calculatedY;
}

private int CalculateX(Point mousePosition, int screenWidth)
{
int calculatedX;
if (IsEnoughSpaceToTheLeft(mousePosition))
{
calculatedX = mousePosition.X - _controlSize.Width;
}
else if (IsEnoughSpaceToTheRight(mousePosition, screenWidth))
{
calculatedX = mousePosition.X;
}
else
{
// the control width is bigger than the screen width
calculatedX = 0;
}

return calculatedX;
}

private bool IsEnoughSpaceToTheLeft(Point mousePosition)
{
return (mousePosition.X - _controlSize.Width) >= 0;
}

private bool IsEnoughSpaceToTheTop(Point mousePosition)
{
return (mousePosition.Y - _controlSize.Height) >= 0;
}

private bool IsEnoughSpaceToTheRight(Point mousePosition, int screenWidth)
{
return (mousePosition.X + _controlSize.Width) <= screenWidth;
}

private bool IsEnoughSpaceToTheBottom(Point mousePosition, int screenHeight)
{
return (mousePosition.Y + _controlSize.Height) <= screenHeight;
}
}
}
12 changes: 11 additions & 1 deletion KeeTrayTOTP/TrayTOTP_Plugin.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
Expand Down Expand Up @@ -267,6 +267,8 @@ public override bool Initialize(IPluginHost host)
_niMenuSeperator = new ToolStripSeparator();
PluginHost.MainWindow.TrayContextMenu.Items.Insert(1, _niMenuSeperator);

PluginHost.MainWindow.TrayContextMenu.Opened += OnTrayContextMenuOpened;

// Register auto-type function.
if (PluginHost.CustomConfig.GetBool(setname_bool_AutoType_Enable, true))
{
Expand All @@ -291,6 +293,13 @@ public override bool Initialize(IPluginHost host)
return true;
}

private void OnTrayContextMenuOpened(object sender, EventArgs e)
{
var contextMenuStrip = (ContextMenuStrip)sender;
var dropDownLocationCalculator = new DropDownLocationCalculator(contextMenuStrip.Size);
contextMenuStrip.Location = dropDownLocationCalculator.CalculateLocationForDropDown(Cursor.Position);
}

/// <summary>
/// Occurs when the main window is shown.
/// </summary>
Expand Down Expand Up @@ -883,6 +892,7 @@ public override void Terminate()

// Remove Notify Icon menus.
PluginHost.MainWindow.TrayContextMenu.Opening -= OnNotifyMenuOpening;
PluginHost.MainWindow.TrayContextMenu.Opened -= OnTrayContextMenuOpened;
PluginHost.MainWindow.TrayContextMenu.Items.Remove(_niMenuTitle);
_niMenuTitle.Dispose();
foreach (var menu in _niMenuList)
Expand Down