-
-
Notifications
You must be signed in to change notification settings - Fork 49
+semver:minor Added KeysExtensions class with the IsNavigationKey ext… #1437
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -100,7 +100,7 @@ | ||||||||||||||||
context.SetEngine(ibusKeyboard.IBusKeyboardEngine.LongName); | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
private void SetImePreeditWindowLocationAndSize(Control control) | |||||||||||||||||
private void SetImePreEditWindowLocationAndSize(Control control) | |||||||||||||||||
{ | |||||||||||||||||
var eventHandler = GetEventHandlerForControl(control); | |||||||||||||||||
if (eventHandler == null) | |||||||||||||||||
|
@@ -165,8 +165,7 @@ | ||||||||||||||||
e.Control.PreviewKeyDown += HandlePreviewKeyDown; | |||||||||||||||||
e.Control.KeyPress += HandleKeyPress; | |||||||||||||||||
|
|||||||||||||||||
var scrollableControl = e.Control as ScrollableControl; | |||||||||||||||||
if (scrollableControl != null) | |||||||||||||||||
if (e.Control is ScrollableControl scrollableControl) | |||||||||||||||||
scrollableControl.Scroll += HandleScroll; | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
|
@@ -182,8 +181,7 @@ | ||||||||||||||||
e.Control.KeyPress -= HandleKeyPress; | |||||||||||||||||
e.Control.KeyDown -= HandleKeyDownAfterIbusHandledKey; | |||||||||||||||||
|
|||||||||||||||||
var scrollableControl = e.Control as ScrollableControl; | |||||||||||||||||
if (scrollableControl != null) | |||||||||||||||||
if (e.Control is ScrollableControl scrollableControl) | |||||||||||||||||
scrollableControl.Scroll -= HandleScroll; | |||||||||||||||||
|
|||||||||||||||||
var eventHandler = GetEventHandlerForControl(e.Control); | |||||||||||||||||
|
@@ -202,7 +200,7 @@ | ||||||||||||||||
|
|||||||||||||||||
/// <summary> | |||||||||||||||||
/// Passes the key event to ibus. This method deals with the special keys (Cursor up/down, | |||||||||||||||||
/// backspace etc) that usually shouldn't cause a commit. | |||||||||||||||||
/// backspace, etc.) that usually shouldn't cause a commit. | |||||||||||||||||
/// </summary> | |||||||||||||||||
private bool PassSpecialKeyEventToIbus(Control control, Keys keyChar, Keys modifierKeys) | |||||||||||||||||
{ | |||||||||||||||||
|
@@ -212,8 +210,11 @@ | ||||||||||||||||
|
|||||||||||||||||
private bool PassKeyEventToIbus(Control control, char keyChar, Keys modifierKeys) | |||||||||||||||||
{ | |||||||||||||||||
if (keyChar == 0x7f) // we get this for Ctrl-Backspace | |||||||||||||||||
keyChar = '\b'; | |||||||||||||||||
const char asciiDel = (char)0x7F; | |||||||||||||||||
const char backspace = '\b'; | |||||||||||||||||
|
|||||||||||||||||
if (keyChar == asciiDel) // Ctrl+Backspace arrives as DEL; normalize to Backspace | |||||||||||||||||
keyChar = backspace; | |||||||||||||||||
|
|||||||||||||||||
return PassKeyEventToIbus(control, keyChar, modifierKeys, true); | |||||||||||||||||
} | |||||||||||||||||
|
@@ -235,7 +236,7 @@ | ||||||||||||||||
|
|||||||||||||||||
if (resetIfUnhandled) | |||||||||||||||||
{ | |||||||||||||||||
// If ProcessKeyEvent doesn't consume the key, we need to kill any preedits and | |||||||||||||||||
// If ProcessKeyEvent doesn't consume the key, we need to kill any pre-edits and | |||||||||||||||||
// sync before continuing processing the keypress. We return false so that the | |||||||||||||||||
// control can process the character. | |||||||||||||||||
ResetAndWaitForCommit(control); | |||||||||||||||||
|
@@ -254,8 +255,7 @@ | ||||||||||||||||
// and the other time because we have to call the original window proc. However, only | |||||||||||||||||
// the second time will the control report as being focused (or when we not intercept | |||||||||||||||||
// the message then the first time) (see SimpleRootSite.OriginalWndProc). | |||||||||||||||||
var control = sender as Control; | |||||||||||||||||
if (control == null || !control.Focused) | |||||||||||||||||
if (!(sender is Control control) || !control.Focused) | |||||||||||||||||
return; | |||||||||||||||||
|
|||||||||||||||||
_ibusComm.FocusIn(); | |||||||||||||||||
|
@@ -276,7 +276,7 @@ | ||||||||||||||||
|
|||||||||||||||||
/// <summary> | |||||||||||||||||
/// Inform input bus of Keydown events | |||||||||||||||||
/// This is useful to get warning of key that should stop the preedit | |||||||||||||||||
/// This is useful to get warning of key that should stop the pre-edit | |||||||||||||||||
/// </summary> | |||||||||||||||||
private void HandlePreviewKeyDown(object sender, PreviewKeyDownEventArgs e) | |||||||||||||||||
{ | |||||||||||||||||
|
@@ -290,49 +290,62 @@ | ||||||||||||||||
|
|||||||||||||||||
if (_needIMELocation) | |||||||||||||||||
{ | |||||||||||||||||
SetImePreeditWindowLocationAndSize(control); | |||||||||||||||||
SetImePreEditWindowLocationAndSize(control); | |||||||||||||||||
_needIMELocation = false; | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
var key = e.KeyCode; | |||||||||||||||||
switch (key) | |||||||||||||||||
if (key == Keys.Escape) | |||||||||||||||||
{ | |||||||||||||||||
case Keys.Escape: | |||||||||||||||||
// These should end a preedit, so wait until that has happened | |||||||||||||||||
// before allowing the key to be processed. | |||||||||||||||||
ResetAndWaitForCommit(control); | |||||||||||||||||
return; | |||||||||||||||||
case Keys.Up: | |||||||||||||||||
case Keys.Down: | |||||||||||||||||
case Keys.Left: | |||||||||||||||||
case Keys.Right: | |||||||||||||||||
case Keys.Delete: | |||||||||||||||||
case Keys.PageUp: | |||||||||||||||||
case Keys.PageDown: | |||||||||||||||||
case Keys.Home: | |||||||||||||||||
case Keys.End: | |||||||||||||||||
case Keys.Back: | |||||||||||||||||
if (PassSpecialKeyEventToIbus(control, key, e.Modifiers)) | |||||||||||||||||
{ | |||||||||||||||||
// If IBus handled the key we don't want the control to get it. However, | |||||||||||||||||
// we can't do this in PreviewKeyDown, so we temporarily subscribe to | |||||||||||||||||
// KeyDown and suppress the key event there. | |||||||||||||||||
control.KeyDown += HandleKeyDownAfterIbusHandledKey; | |||||||||||||||||
} | |||||||||||||||||
return; | |||||||||||||||||
// These should end a pre-edit, so wait until that has happened | |||||||||||||||||
// before allowing the key to be processed. | |||||||||||||||||
ResetAndWaitForCommit(control); | |||||||||||||||||
return; | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
if (IsKeyHandledByIbus(key)) | |||||||||||||||||
{ | |||||||||||||||||
if (PassSpecialKeyEventToIbus(control, key, e.Modifiers)) | |||||||||||||||||
{ | |||||||||||||||||
// If IBus handled the key we don't want the control to get it. However, | |||||||||||||||||
// we can't do this in PreviewKeyDown, so we temporarily subscribe to | |||||||||||||||||
// KeyDown and suppress the key event there. | |||||||||||||||||
control.KeyDown += HandleKeyDownAfterIbusHandledKey; | |||||||||||||||||
Check warningCode scanning / CodeQL Dereferenced variable may be null Warning
Variable
control Error loading related location Loading this Error loading related location Loading
Copilot AutofixAI 6 days ago To fix the issue, we need to ensure that This fix ensures that the method behaves safely even if
Suggested changeset
1
SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs
Copilot is powered by AI and may make mistakes. Always verify output.
Positive FeedbackNegative Feedback
Refresh and try again.
|
|||||||||||||||||
} | |||||||||||||||||
return; | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
// pass function keys onto ibus since they don't appear (on mono at least) as WM_SYSCHAR | |||||||||||||||||
if (key >= Keys.F1 && key <= Keys.F24) | |||||||||||||||||
PassSpecialKeyEventToIbus(control, key, e.Modifiers); | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
/// <summary> | |||||||||||||||||
/// Handles a key down. While a preedit is active we don't want the control to handle | |||||||||||||||||
/// Handles a key down. While a pre-edit is active we don't want the control to handle | |||||||||||||||||
/// any of the keys that IBus deals with. | |||||||||||||||||
/// </summary> | |||||||||||||||||
private void HandleKeyDownAfterIbusHandledKey(object sender, KeyEventArgs e) | |||||||||||||||||
{ | |||||||||||||||||
switch (e.KeyCode) | |||||||||||||||||
if (IsKeyHandledByIbus(e.KeyCode) && sender is Control control) | |||||||||||||||||
{ | |||||||||||||||||
var eventHandler = GetEventHandlerForControl(control); | |||||||||||||||||
if (eventHandler != null) | |||||||||||||||||
e.SuppressKeyPress = eventHandler.IsPreeditActive; | |||||||||||||||||
control.KeyDown -= HandleKeyDownAfterIbusHandledKey; | |||||||||||||||||
} | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
/// <summary> | |||||||||||||||||
/// Gets whether the given key is expected to be handled by IBus when a pre-edit is active. | |||||||||||||||||
/// </summary> | |||||||||||||||||
/// <remarks> | |||||||||||||||||
/// REVIEW: During pre-edit, I assume that IBus handles both basic and modified navigation | |||||||||||||||||
/// keys like Ctrl+Left. ChatGPT says this is true, but I have not been able to verify this | |||||||||||||||||
/// since I don't know how to set up a Linux/IBus environment where I could test this. | |||||||||||||||||
Comment on lines
+342
to
+344
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To test this on Linux:
|
|||||||||||||||||
/// </remarks> | |||||||||||||||||
private bool IsKeyHandledByIbus(Keys key) | |||||||||||||||||
{ | |||||||||||||||||
switch (key & Keys.KeyCode) | |||||||||||||||||
{ | |||||||||||||||||
case Keys.Up: | |||||||||||||||||
case Keys.Down: | |||||||||||||||||
|
@@ -344,13 +357,10 @@ | ||||||||||||||||
case Keys.Home: | |||||||||||||||||
case Keys.End: | |||||||||||||||||
case Keys.Back: | |||||||||||||||||
var control = sender as Control; | |||||||||||||||||
var eventHandler = GetEventHandlerForControl(control); | |||||||||||||||||
if (eventHandler != null) | |||||||||||||||||
e.SuppressKeyPress = eventHandler.IsPreeditActive; | |||||||||||||||||
control.KeyDown -= HandleKeyDownAfterIbusHandledKey; | |||||||||||||||||
break; | |||||||||||||||||
return true; | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
return false; | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
/// <summary> | |||||||||||||||||
|
@@ -360,17 +370,20 @@ | ||||||||||||||||
/// this method gets called. We forward the key press to IBus. If IBus swallowed the key | |||||||||||||||||
/// it will return true, so no further handling is done by the control, otherwise the | |||||||||||||||||
/// control will process the key and update the selection. | |||||||||||||||||
/// If IBus swallows the key event, it will either raise the UpdatePreeditText event, | |||||||||||||||||
/// allowing the event handler to insert the composition as preedit (and update the | |||||||||||||||||
/// selection), or it will raise the CommitText event so that the event handler can | |||||||||||||||||
/// remove the preedit, replace it with the final composition string and update the | |||||||||||||||||
/// selection. Some IBus keyboards might raise a ForwardKeyEvent (handled by | |||||||||||||||||
/// <see cref="IIbusEventHandler.OnIbusKeyPress"/>) prior to calling CommitText to | |||||||||||||||||
/// simulate a key press (e.g. backspace) so that the event handler can modify the | |||||||||||||||||
/// existing text of the control. | |||||||||||||||||
/// If IBus swallows the key event, it will either raise the | |||||||||||||||||
/// <see cref="IIbusCommunicator.UpdatePreeditText"/> event, allowing the event handler to | |||||||||||||||||
/// insert the composition as pre-edit (and update the selection), or it will raise the | |||||||||||||||||
/// <see cref="IIbusCommunicator.CommitText"/> event so that the event handler can | |||||||||||||||||
/// remove the pre-edit, replace it with the final composition string and update the | |||||||||||||||||
/// selection. Some IBus keyboards might raise a | |||||||||||||||||
/// <see cref="IBusDotNet.IInputContext.ForwardKeyEvent"/> (handled by | |||||||||||||||||
/// <see cref="IIbusEventHandler.OnIbusKeyPress"/>) prior to raising | |||||||||||||||||
/// <see cref="IIbusCommunicator.CommitText"/> to simulate a key press (e.g. backspace) | |||||||||||||||||
/// so that the event handler can modify the existing text of the control. | |||||||||||||||||
/// IBus might also open a pop-up window at the location we told it | |||||||||||||||||
/// (<see cref="IIbusEventHandler.SelectionLocationAndHeight"/>) to display possible | |||||||||||||||||
/// compositions. However, it will still call UpdatePreeditText.</remarks> | |||||||||||||||||
/// compositions. However, it will still raise | |||||||||||||||||
/// <see cref="IIbusCommunicator.UpdatePreeditText"/>.</remarks> | |||||||||||||||||
private void HandleKeyPress(object sender, KeyPressEventArgs e) | |||||||||||||||||
{ | |||||||||||||||||
e.Handled = PassKeyEventToIbus(sender as Control, e.KeyChar, Control.ModifierKeys); | |||||||||||||||||
|
@@ -390,7 +403,7 @@ | ||||||||||||||||
if (!_ibusComm.Connected) | |||||||||||||||||
return; | |||||||||||||||||
|
|||||||||||||||||
SetImePreeditWindowLocationAndSize(sender as Control); | |||||||||||||||||
SetImePreEditWindowLocationAndSize(sender as Control); | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
#endregion | |||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using System.Windows.Forms; | ||
|
||
namespace SIL.Windows.Forms.Extensions | ||
{ | ||
public static class KeysExtensions | ||
{ | ||
/// <summary> | ||
/// Determines whether the specified key is considered a navigation key. | ||
/// </summary> | ||
/// <param name="key">The key (or key-combination) to examine.</param> | ||
/// <param name="ctrlPressed">If <c>true</c>, the letter <c>Keys.A</c> will be treated as a | ||
/// navigation key (even if the <paramref name="key"/> value does not explicitly include | ||
/// <c>Keys.Control</c>). In contexts where Ctrl-A is not a shortcut for Select All or that | ||
/// behavior is not relevant, pass false or omit this optional parameter.</param> | ||
/// <remarks>If <paramref name="key"/> represents the combination Ctrl-A (i.e., | ||
/// <c>Keys.Control | Keys.A</c>), but <paramref name="ctrlPressed"/> is <c>false</c>, | ||
/// this method will return <c>false</c>.</remarks> | ||
public static bool IsNavigationKey(this Keys key, bool ctrlPressed = false) | ||
{ | ||
switch (key & Keys.KeyCode) | ||
{ | ||
case Keys.Left: | ||
case Keys.Up: | ||
case Keys.Down: | ||
case Keys.Right: | ||
case Keys.Home: | ||
case Keys.End: | ||
case Keys.PageDown: | ||
case Keys.PageUp: | ||
return true; | ||
case Keys.A: | ||
return ctrlPressed; | ||
default: | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.