Build 0.20.10.0
This commit is contained in:
@@ -174,9 +174,9 @@ namespace Barotrauma
|
||||
|
||||
Character.DisableControls = true;
|
||||
|
||||
if (PlayerInput.KeyHit(Keys.Tab))
|
||||
if (PlayerInput.KeyHit(Keys.Tab) && !textBox.IsIMEActive)
|
||||
{
|
||||
textBox.Text = AutoComplete(textBox.Text, increment: string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : 1 );
|
||||
textBox.Text = AutoComplete(textBox.Text, increment: string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : 1 );
|
||||
}
|
||||
|
||||
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
|
||||
|
||||
@@ -1,72 +1,18 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace EventInput
|
||||
{
|
||||
public class CharacterEventArgs : EventArgs
|
||||
public readonly record struct CharacterEventArgs(char Character, long Param)
|
||||
{
|
||||
private readonly char character;
|
||||
private readonly long lParam;
|
||||
|
||||
public CharacterEventArgs(char character, long lParam)
|
||||
{
|
||||
this.character = character;
|
||||
this.lParam = lParam;
|
||||
}
|
||||
|
||||
public char Character
|
||||
{
|
||||
get { return character; }
|
||||
}
|
||||
|
||||
public long Param
|
||||
{
|
||||
get { return lParam; }
|
||||
}
|
||||
|
||||
public long RepeatCount
|
||||
{
|
||||
get { return lParam & 0xffff; }
|
||||
}
|
||||
|
||||
public bool ExtendedKey
|
||||
{
|
||||
get { return (lParam & (1 << 24)) > 0; }
|
||||
}
|
||||
|
||||
public bool AltPressed
|
||||
{
|
||||
get { return (lParam & (1 << 29)) > 0; }
|
||||
}
|
||||
|
||||
public bool PreviousState
|
||||
{
|
||||
get { return (lParam & (1 << 30)) > 0; }
|
||||
}
|
||||
|
||||
public bool TransitionState
|
||||
{
|
||||
get { return (lParam & (1 << 31)) > 0; }
|
||||
}
|
||||
public long RepeatCount => Param & 0xffff;
|
||||
public bool ExtendedKey => (Param & (1 << 24)) > 0;
|
||||
public bool AltPressed => (Param & (1 << 29)) > 0;
|
||||
public bool PreviousState => (Param & (1 << 30)) > 0;
|
||||
public bool TransitionState => (Param & (1 << 31)) > 0;
|
||||
}
|
||||
|
||||
public class KeyEventArgs : EventArgs
|
||||
{
|
||||
private Keys keyCode;
|
||||
|
||||
public KeyEventArgs(Keys keyCode)
|
||||
{
|
||||
this.keyCode = keyCode;
|
||||
}
|
||||
|
||||
public Keys KeyCode
|
||||
{
|
||||
get { return keyCode; }
|
||||
}
|
||||
}
|
||||
public readonly record struct KeyEventArgs(Keys KeyCode, char Character);
|
||||
|
||||
public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
|
||||
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
|
||||
@@ -89,14 +35,11 @@ namespace EventInput
|
||||
/// </summary>
|
||||
public static event KeyEventHandler KeyUp;
|
||||
|
||||
|
||||
#if !WINDOWS
|
||||
/// <summary>
|
||||
/// Raised when the user is editing text and IME is in progress.
|
||||
/// Windows build uses ImeSharp instead because SDL2's IME implementation is broken on Windows (https://github.com/libsdl-org/SDL/issues/2243)
|
||||
/// </summary>
|
||||
public static event EditingTextHandler EditingText;
|
||||
#endif
|
||||
|
||||
|
||||
static bool initialized;
|
||||
|
||||
@@ -110,11 +53,10 @@ namespace EventInput
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
window.TextInput += ReceiveInput;
|
||||
#if !WINDOWS
|
||||
window.KeyDown += ReceiveKeyDown;
|
||||
window.TextEditing += ReceiveTextEditing;
|
||||
#endif
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
@@ -122,15 +64,17 @@ namespace EventInput
|
||||
private static void ReceiveInput(object sender, TextInputEventArgs e)
|
||||
{
|
||||
OnCharEntered(e.Character);
|
||||
KeyDown?.Invoke(sender, new KeyEventArgs(e.Key));
|
||||
}
|
||||
|
||||
#if !WINDOWS
|
||||
private static void ReceiveKeyDown(object sender, TextInputEventArgs e)
|
||||
{
|
||||
KeyDown?.Invoke(sender, new KeyEventArgs(e.Key, e.Character));
|
||||
}
|
||||
|
||||
private static void ReceiveTextEditing(object sender, TextEditingEventArgs e)
|
||||
{
|
||||
EditingText?.Invoke(sender, e);
|
||||
}
|
||||
#endif
|
||||
|
||||
public static void OnCharEntered(char character)
|
||||
{
|
||||
|
||||
@@ -10,11 +10,7 @@ namespace EventInput
|
||||
void ReceiveTextInput(string text);
|
||||
void ReceiveCommandInput(char command);
|
||||
void ReceiveSpecialInput(Keys key);
|
||||
|
||||
#if !WINDOWS
|
||||
/// Windows build uses ImeSharp instead because SDL2's IME implementation is broken on Windows (https://github.com/libsdl-org/SDL/issues/2243)
|
||||
void ReceiveEditingInput(string text, int start);
|
||||
#endif
|
||||
void ReceiveEditingInput(string text, int start, int length);
|
||||
|
||||
bool Selected { get; set; } //or Focused
|
||||
}
|
||||
@@ -26,59 +22,27 @@ namespace EventInput
|
||||
EventInput.Initialize(window);
|
||||
EventInput.CharEntered += EventInput_CharEntered;
|
||||
EventInput.KeyDown += EventInput_KeyDown;
|
||||
#if !WINDOWS
|
||||
EventInput.EditingText += EventInput_TextEditing;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* SDL by default starts in a state where it accepts IME inputs
|
||||
* this is bad because this blocks keybinds since the IME thinks
|
||||
* it's typing in a text box and not forwarding keybinds to the game.
|
||||
*/
|
||||
TextInput.StopTextInput();
|
||||
GameMain.ResetIMEWorkaround();
|
||||
}
|
||||
#if !WINDOWS
|
||||
|
||||
public void EventInput_TextEditing(object sender, TextEditingEventArgs e)
|
||||
{
|
||||
_subscriber?.ReceiveEditingInput(e.Text, e.Start);
|
||||
_subscriber?.ReceiveEditingInput(e.Text, e.Start, e.Length);
|
||||
}
|
||||
#endif
|
||||
|
||||
public void EventInput_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
_subscriber?.ReceiveSpecialInput(e.KeyCode);
|
||||
if (char.IsControl(e.Character))
|
||||
{
|
||||
_subscriber?.ReceiveCommandInput(e.Character);
|
||||
}
|
||||
}
|
||||
|
||||
void EventInput_CharEntered(object sender, CharacterEventArgs e)
|
||||
{
|
||||
if (_subscriber == null)
|
||||
return;
|
||||
if (char.IsControl(e.Character))
|
||||
{
|
||||
_subscriber.ReceiveCommandInput(e.Character);
|
||||
// Doesn't work as expected. Not sure why this should be run in a separate thread.
|
||||
//#if WINDOWS
|
||||
// //ctrl-v
|
||||
// if (e.Character == 0x16) // 22
|
||||
// {
|
||||
// //XNA runs in Multiple Thread Apartment state, which cannot recieve clipboard
|
||||
// Thread thread = new Thread(PasteThread);
|
||||
// thread.SetApartmentState(ApartmentState.STA);
|
||||
// thread.Start();
|
||||
// thread.Join();
|
||||
// _subscriber.ReceiveTextInput(_pasteResult);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _subscriber.ReceiveCommandInput(e.Character);
|
||||
// }
|
||||
//#else
|
||||
// _subscriber.ReceiveCommandInput(e.Character);
|
||||
//#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
_subscriber.ReceiveTextInput(e.Character);
|
||||
}
|
||||
_subscriber?.ReceiveTextInput(e.Character);
|
||||
}
|
||||
|
||||
IKeyboardSubscriber _subscriber;
|
||||
@@ -97,8 +61,9 @@ namespace EventInput
|
||||
|
||||
if (value is GUITextBox box)
|
||||
{
|
||||
TextInput.SetTextInputRect(box.Rect);
|
||||
TextInput.SetTextInputRect(box.MouseRect);
|
||||
TextInput.StartTextInput();
|
||||
TextInput.SetTextInputRect(box.MouseRect);
|
||||
}
|
||||
|
||||
_subscriber = value;
|
||||
|
||||
@@ -1261,10 +1261,6 @@ namespace Barotrauma
|
||||
UpdateMessages(deltaTime);
|
||||
UpdateSavingIndicator(deltaTime);
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
GUITextBox.UpdateIME();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void UpdateGUIMessageBoxesOnly(float deltaTime)
|
||||
|
||||
@@ -111,10 +111,7 @@ namespace Barotrauma
|
||||
}
|
||||
public void ReceiveTextInput(string text) { }
|
||||
public void ReceiveCommandInput(char command) { }
|
||||
|
||||
#if !WINDOWS
|
||||
public void ReceiveEditingInput(string text, int start) { }
|
||||
#endif
|
||||
public void ReceiveEditingInput(string text, int start, int length) { }
|
||||
|
||||
public void ReceiveSpecialInput(Keys key)
|
||||
{
|
||||
@@ -125,9 +122,7 @@ namespace Barotrauma
|
||||
listBox.ReceiveSpecialInput(key);
|
||||
GUI.KeyboardDispatcher.Subscriber = this;
|
||||
break;
|
||||
case Keys.Enter:
|
||||
case Keys.Space:
|
||||
case Keys.Escape:
|
||||
default:
|
||||
GUI.KeyboardDispatcher.Subscriber = null;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1347,10 +1347,7 @@ namespace Barotrauma
|
||||
}
|
||||
public void ReceiveTextInput(string text) { }
|
||||
public void ReceiveCommandInput(char command) { }
|
||||
|
||||
#if !WINDOWS
|
||||
public void ReceiveEditingInput(string text, int start) { }
|
||||
#endif
|
||||
public void ReceiveEditingInput(string text, int start, int length) { }
|
||||
|
||||
public void ReceiveSpecialInput(Keys key)
|
||||
{
|
||||
@@ -1368,9 +1365,7 @@ namespace Barotrauma
|
||||
case Keys.Right:
|
||||
if (isHorizontal && AllowArrowKeyScroll) { SelectNext(playSelectSound: PlaySelectSound.Yes); }
|
||||
break;
|
||||
case Keys.Enter:
|
||||
case Keys.Space:
|
||||
case Keys.Escape:
|
||||
default:
|
||||
GUI.KeyboardDispatcher.Subscriber = null;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -223,24 +223,24 @@ namespace Barotrauma
|
||||
public ScalableFont GetFontForStr(string str) =>
|
||||
Prefabs.ActivePrefab.GetFontForCategory(TextManager.GetSpeciallyHandledCategories(str));
|
||||
|
||||
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
|
||||
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth)
|
||||
{
|
||||
DrawString(sb, text.Value, position, color, rotation, origin, scale, se, layerDepth);
|
||||
DrawString(sb, text.Value, position, color, rotation, origin, scale, spriteEffects, layerDepth);
|
||||
}
|
||||
|
||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
|
||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth)
|
||||
{
|
||||
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, se, layerDepth);
|
||||
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth);
|
||||
}
|
||||
|
||||
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, Alignment alignment = Alignment.TopLeft)
|
||||
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, Alignment alignment = Alignment.TopLeft)
|
||||
{
|
||||
DrawString(sb, text.Value, position, color, rotation, origin, scale, se, layerDepth, alignment);
|
||||
DrawString(sb, text.Value, position, color, rotation, origin, scale, spriteEffects, layerDepth, alignment);
|
||||
}
|
||||
|
||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
|
||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
|
||||
{
|
||||
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, se, layerDepth, alignment, forceUpperCase);
|
||||
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, alignment, forceUpperCase);
|
||||
}
|
||||
|
||||
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit, bool italics = false)
|
||||
@@ -253,9 +253,9 @@ namespace Barotrauma
|
||||
GetFontForStr(text).DrawString(sb, text, position, color, forceUpperCase, italics);
|
||||
}
|
||||
|
||||
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, in ImmutableArray<RichTextData>? richTextData, int rtdOffset = 0, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
|
||||
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, in ImmutableArray<RichTextData>? richTextData, int rtdOffset = 0, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
|
||||
{
|
||||
GetFontForStr(text).DrawStringWithColors(sb, text, position, color, rotation, origin, scale, se, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase);
|
||||
GetFontForStr(text).DrawStringWithColors(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase);
|
||||
}
|
||||
|
||||
public Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing = false)
|
||||
|
||||
@@ -256,6 +256,8 @@ namespace Barotrauma
|
||||
|
||||
private readonly IMEPreviewTextHandler imePreviewTextHandler;
|
||||
|
||||
public bool IsIMEActive => imePreviewTextHandler is { HasText: true };
|
||||
|
||||
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null,
|
||||
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true)
|
||||
: base(style, rectT)
|
||||
@@ -574,6 +576,7 @@ namespace Barotrauma
|
||||
{
|
||||
CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length);
|
||||
OnTextChanged?.Invoke(this, Text);
|
||||
imePreviewTextHandler?.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,10 +605,12 @@ namespace Barotrauma
|
||||
|
||||
public void ReceiveCommandInput(char command)
|
||||
{
|
||||
if (Text == null) Text = "";
|
||||
if (IsIMEActive) { return; }
|
||||
|
||||
if (Text == null) { Text = ""; }
|
||||
|
||||
// Prevent alt gr from triggering any of these as that combination is often needed for special characters
|
||||
if (PlayerInput.IsAltDown()) return;
|
||||
if (PlayerInput.IsAltDown()) { return; }
|
||||
|
||||
switch (command)
|
||||
{
|
||||
@@ -678,21 +683,22 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if !WINDOWS
|
||||
public void ReceiveEditingInput(string text, int start)
|
||||
|
||||
public void ReceiveEditingInput(string text, int start, int length)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
if (start is 0) { imePreviewTextHandler.Reset(); }
|
||||
imePreviewTextHandler.Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
imePreviewTextHandler.UpdateText(text, start);
|
||||
imePreviewTextHandler.UpdateText(text, start, length);
|
||||
}
|
||||
#endif
|
||||
|
||||
public void ReceiveSpecialInput(Keys key)
|
||||
{
|
||||
if (IsIMEActive) { return; }
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Left:
|
||||
@@ -883,20 +889,7 @@ namespace Barotrauma
|
||||
|
||||
public void DrawIMEPreview(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!imePreviewTextHandler.HasText) { return; }
|
||||
|
||||
Vector2 imePosition = CaretScreenPos;
|
||||
int inflate = GUI.IntScale(3);
|
||||
|
||||
RectangleF rect = new RectangleF(imePosition, imePreviewTextHandler.TextSize);
|
||||
rect.Inflate(inflate, inflate);
|
||||
|
||||
RectangleF borderRect = rect;
|
||||
borderRect.Inflate(1, 1);
|
||||
|
||||
GUI.DrawFilledRectangle(spriteBatch, borderRect, Color.White, depth: 0.02f);
|
||||
GUI.DrawFilledRectangle(spriteBatch, rect, Color.Black, depth: 0.01f);
|
||||
Font.DrawString(spriteBatch, imePreviewTextHandler.PreviewText, imePosition, GUIStyle.Orange, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, 0, alignment: textBlock.TextAlignment, forceUpperCase: textBlock.ForceUpperCase);
|
||||
imePreviewTextHandler.DrawIMEPreview(spriteBatch, CaretScreenPos, textBlock);
|
||||
}
|
||||
|
||||
private void CalculateSelection()
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
using ImeSharp;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
/// <summary>
|
||||
/// A class for handling Input Method Editor (used for inputting e.g. Chinese and Japanese text)
|
||||
/// </summary>
|
||||
public partial class GUITextBox : GUIComponent
|
||||
{
|
||||
private static bool initialized;
|
||||
|
||||
public static GUIFrame IMEWindow { get; set; }
|
||||
public static GUITextBlock IMETextBlock { get; set; }
|
||||
|
||||
public static void UpdateIME()
|
||||
{
|
||||
if (!initialized) { InitializeIME(); }
|
||||
if (GUI.KeyboardDispatcher.Subscriber is GUITextBox { Selected: true })
|
||||
{
|
||||
IMEWindow?.AddToGUIUpdateList(order: 10);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitializeIME()
|
||||
{
|
||||
InputMethod.Initialize(GameMain.Instance.Window.Hwnd, false);
|
||||
InputMethod.TextCompositionCallback = OnTextComposition;
|
||||
InputMethod.CommitTextCompositionCallback = OnCommitTextComposition;
|
||||
InputMethod.Enabled = true;
|
||||
IMEWindow = new GUIFrame(new RectTransform(new Point(GUI.IntScale(300), GUI.IntScale(300)), GUI.Canvas), "InnerFrame") { CanBeFocused = false, Visible = false };
|
||||
IMETextBlock = new GUITextBlock(new RectTransform(Vector2.One, IMEWindow.RectTransform), "") { CanBeFocused = false };
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private static void OnTextComposition(IMEString compositionText, int cursorPosition, IMEString[] candidateList, int candidatePageStart, int candidatePageSize, int candidateSelection)
|
||||
{
|
||||
if (GUI.KeyboardDispatcher.Subscriber is not GUITextBox { Selected: true } textBox) { return; }
|
||||
IMEWindow.Visible = true;
|
||||
string text = compositionText.ToString().Insert(cursorPosition, "|");
|
||||
if (candidateList != null)
|
||||
{
|
||||
text += "\n";
|
||||
for (int i = 0; i < candidatePageSize; i++)
|
||||
{
|
||||
string candidateStr = $"\t{candidatePageStart + i + 1} {candidateList[i]}";
|
||||
if (candidateSelection == i)
|
||||
{
|
||||
candidateStr = $" ‖color:{XMLExtensions.ToStringHex(Color.White)}‖{candidateStr}‖end‖";
|
||||
}
|
||||
candidateStr += "\n";
|
||||
text += candidateStr;
|
||||
}
|
||||
}
|
||||
IMETextBlock.Text = RichString.Rich(text);
|
||||
|
||||
IMEWindow.RectTransform.NonScaledSize = new Point(
|
||||
Math.Max(IMEWindow.Rect.Width, (int)IMETextBlock.TextSize.X + GUI.IntScale(32)),
|
||||
(int)IMETextBlock.TextSize.Y);
|
||||
|
||||
Point windowPos = new Point(textBox.Rect.X, textBox.Rect.Bottom);
|
||||
if (windowPos.Y + IMEWindow.Rect.Height > GameMain.GraphicsHeight)
|
||||
{
|
||||
windowPos.Y = textBox.Rect.Y - IMEWindow.Rect.Height;
|
||||
}
|
||||
IMEWindow.RectTransform.ScreenSpaceOffset = windowPos;
|
||||
}
|
||||
|
||||
private static void OnCommitTextComposition(string text)
|
||||
{
|
||||
if (IMEWindow.Visible)
|
||||
{
|
||||
foreach (char c in text)
|
||||
{
|
||||
if (!char.IsControl(c))
|
||||
{
|
||||
GUI.KeyboardDispatcher.Subscriber?.ReceiveTextInput(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
IMEWindow.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
public sealed class IMEPreviewTextHandler
|
||||
{
|
||||
public string PreviewText { get; private set; } = string.Empty;
|
||||
public Vector2 TextSize { get; private set; }
|
||||
public bool HasText => !string.IsNullOrEmpty(PreviewText);
|
||||
public bool HasText => !string.IsNullOrEmpty(previewText);
|
||||
|
||||
// This has to be settable because for some reason we update the font of GUITextBox in some places
|
||||
public GUIFont Font { get; set; }
|
||||
|
||||
private string previewText = string.Empty;
|
||||
private Vector2 textSize;
|
||||
|
||||
private bool isSectioned;
|
||||
private ImmutableArray<RichTextData>? richTextData;
|
||||
|
||||
public IMEPreviewTextHandler(GUIFont font)
|
||||
{
|
||||
Font = font;
|
||||
@@ -20,35 +27,64 @@ namespace Barotrauma
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
TextSize = Vector2.Zero;
|
||||
PreviewText = string.Empty;
|
||||
textSize = Vector2.Zero;
|
||||
previewText = string.Empty;
|
||||
richTextData = null;
|
||||
isSectioned = false;
|
||||
}
|
||||
|
||||
public void UpdateText(string text, int start)
|
||||
public void UpdateText(string text, int start, int length)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text) && start is 0)
|
||||
isSectioned = start >= 0 && length > 0;
|
||||
richTextData = null;
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
int totalLength = start + text.Length;
|
||||
string newText = PreviewText;
|
||||
if (newText.Length > totalLength)
|
||||
{
|
||||
newText = newText[..totalLength];
|
||||
}
|
||||
previewText = text;
|
||||
|
||||
if (totalLength > newText.Length)
|
||||
{
|
||||
// this is required for some reason on Windows
|
||||
// my guess is that the order which TextEditing events come thru is not guaranteed
|
||||
newText = newText.PadRight(totalLength);
|
||||
}
|
||||
textSize = Font.MeasureString(text);
|
||||
|
||||
newText = newText.Remove(start, text.Length).Insert(start, text);
|
||||
PreviewText = newText;
|
||||
TextSize = Font.MeasureString(PreviewText);
|
||||
if (!isSectioned) { return; }
|
||||
|
||||
string coloredText = ToolBox.ColorSectionOfString(text, start, length, GUIStyle.Orange);
|
||||
|
||||
RichString richString = RichString.Rich(coloredText);
|
||||
|
||||
previewText = richString.SanitizedValue;
|
||||
richTextData = richString.RichTextData;
|
||||
}
|
||||
|
||||
public void DrawIMEPreview(SpriteBatch spriteBatch, Vector2 position, GUITextBlock textBlock)
|
||||
{
|
||||
if (!HasText) { return; }
|
||||
|
||||
int inflate = GUI.IntScale(3);
|
||||
|
||||
RectangleF rect = new RectangleF(position, textSize);
|
||||
rect.Inflate(inflate, inflate);
|
||||
|
||||
RectangleF borderRect = rect;
|
||||
borderRect.Inflate(1, 1);
|
||||
|
||||
GUI.DrawFilledRectangle(spriteBatch, borderRect, Color.White, depth: 0.02f);
|
||||
GUI.DrawFilledRectangle(spriteBatch, rect, Color.Black, depth: 0.01f);
|
||||
|
||||
Font.DrawStringWithColors(spriteBatch,
|
||||
text: previewText,
|
||||
position: position,
|
||||
color: isSectioned ? GUIStyle.TextColorNormal : GUIStyle.Orange,
|
||||
rotation: 0.0f,
|
||||
origin: Vector2.Zero,
|
||||
scale: 1f,
|
||||
spriteEffects: SpriteEffects.None,
|
||||
layerDepth: 0,
|
||||
richTextData: richTextData,
|
||||
alignment: textBlock.TextAlignment,
|
||||
forceUpperCase: textBlock.ForceUpperCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,6 +377,7 @@ namespace Barotrauma
|
||||
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull));
|
||||
|
||||
performanceCounterTimer = Stopwatch.StartNew();
|
||||
ResetIMEWorkaround();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1239,5 +1240,20 @@ namespace Barotrauma
|
||||
};
|
||||
msgBox.Buttons[1].OnClicked = msgBox.Close;
|
||||
}
|
||||
|
||||
/*
|
||||
* On some systems, IME input is enabled by default, and being able to set the game to a state
|
||||
* where it doesn't accept IME input on game launch seems very inconsistent.
|
||||
* This function quickly cycles through IME input states and is called from couple different places
|
||||
* to ensure that IME input is disabled properly when it's not needed.
|
||||
*/
|
||||
public static void ResetIMEWorkaround()
|
||||
{
|
||||
Rectangle rect = new Rectangle(0, 0, GraphicsWidth, GraphicsHeight);
|
||||
TextInput.SetTextInputRect(rect);
|
||||
TextInput.StartTextInput();
|
||||
TextInput.SetTextInputRect(rect);
|
||||
TextInput.StopTextInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -999,6 +999,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (MapEntityCategory category in Enum.GetValues(typeof(MapEntityCategory)))
|
||||
{
|
||||
if (category == MapEntityCategory.None) { continue; }
|
||||
entityCategoryButtons.Add(new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), entityMenuTop.RectTransform, scaleBasis: ScaleBasis.BothHeight),
|
||||
"", style: "CategoryButton." + category.ToString())
|
||||
{
|
||||
@@ -1085,6 +1086,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (MapEntityCategory category in Enum.GetValues(typeof(MapEntityCategory)))
|
||||
{
|
||||
if (category == MapEntityCategory.None) { continue; }
|
||||
LocalizedString categoryName = TextManager.Get("MapEntityCategory." + category);
|
||||
maxTextWidth = (int)Math.Max(maxTextWidth, GUIStyle.SubHeadingFont.MeasureString(categoryName.Replace(" ", "\n")).X + GUI.IntScale(50));
|
||||
foreach (MapEntityPrefab ep in MapEntityPrefab.List)
|
||||
|
||||
@@ -863,22 +863,21 @@ namespace Barotrauma
|
||||
PlayDamageSound(damageType, damage, bodyPosition, 800.0f);
|
||||
}
|
||||
|
||||
private static readonly List<DamageSound> tempList = new List<DamageSound>();
|
||||
public static void PlayDamageSound(string damageType, float damage, Vector2 position, float range = 2000.0f, IEnumerable<Identifier> tags = null)
|
||||
{
|
||||
damage = MathHelper.Clamp(damage + Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f);
|
||||
tempList.Clear();
|
||||
foreach (var s in damageSounds)
|
||||
{
|
||||
if ((s.DamageRange == Vector2.Zero ||
|
||||
(damage >= s.DamageRange.X && damage <= s.DamageRange.Y)) &&
|
||||
s.DamageType == damageType &&
|
||||
(s.RequiredTag.IsEmpty || (tags == null ? s.RequiredTag.IsEmpty : tags.Contains(s.RequiredTag))))
|
||||
{
|
||||
tempList.Add(s);
|
||||
}
|
||||
}
|
||||
var damageSound = tempList.GetRandomUnsynced();
|
||||
//if the damage is too low for any sound, don't play anything
|
||||
if (damageSounds.All(d => damage < d.DamageRange.X)) { return; }
|
||||
|
||||
//allow the damage to differ by 10 from the configured damage range,
|
||||
//so the same amount of damage doesn't always play the same sound
|
||||
float randomizedDamage = MathHelper.Clamp(damage + Rand.Range(-10.0f, 10.0f), 0.0f, 100.0f);
|
||||
|
||||
var suitableSounds = damageSounds.Where(s =>
|
||||
s.DamageType == damageType &&
|
||||
(s.DamageRange == Vector2.Zero || (randomizedDamage >= s.DamageRange.X && randomizedDamage <= s.DamageRange.Y)) &&
|
||||
(s.RequiredTag.IsEmpty || (tags == null ? s.RequiredTag.IsEmpty : tags.Contains(s.RequiredTag))));
|
||||
|
||||
var damageSound = suitableSounds.GetRandomUnsynced();
|
||||
damageSound?.Sound?.Play(1.0f, range, position, muffle: !damageSound.IgnoreMuffling && ShouldMuffleSound(Character.Controlled, position, range, null));
|
||||
}
|
||||
|
||||
|
||||
@@ -512,5 +512,34 @@ namespace Barotrauma
|
||||
|
||||
return new Vector2(paddingX, paddingY);
|
||||
}
|
||||
|
||||
public static string ColorSectionOfString(string text, int start, int length, Color color)
|
||||
{
|
||||
int end = start + length;
|
||||
|
||||
if (start < 0 || length < 0 || end > text.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Invalid start ({start}) or length ({length}) for text \"{text}\".");
|
||||
}
|
||||
|
||||
string stichedString = string.Empty;
|
||||
|
||||
if (start > 0)
|
||||
{
|
||||
stichedString += text[..start];
|
||||
}
|
||||
|
||||
// this is the highlighted part
|
||||
stichedString += ColorString(text[start..end], color);
|
||||
|
||||
if (end < text.Length)
|
||||
{
|
||||
stichedString += text[end..];
|
||||
}
|
||||
|
||||
return stichedString;
|
||||
|
||||
static string ColorString(string text, Color color) => $"‖color:{color.ToStringHex()}‖{text}‖end‖";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.20.9.0</Version>
|
||||
<Version>0.20.10.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.20.9.0</Version>
|
||||
<Version>0.20.10.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.20.9.0</Version>
|
||||
<Version>0.20.10.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
@@ -117,7 +117,6 @@
|
||||
<ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||
<ProjectReference Include="..\..\Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.Windows.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||
<ProjectReference Include="..\..\Libraries\SharpFont\Source\SharpFont\SharpFont.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||
<ProjectReference Include="..\..\Libraries\ImeSharp\ImeSharp.csproj" AdditionalProperties="Configuration=Release" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ProjectReference Include="..\..\Libraries\Concentus\CSharp\Concentus\Concentus.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||
@@ -127,7 +126,6 @@
|
||||
<ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||
<ProjectReference Include="..\..\Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.Windows.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||
<ProjectReference Include="..\..\Libraries\SharpFont\Source\SharpFont\SharpFont.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||
<ProjectReference Include="..\..\Libraries\ImeSharp\ImeSharp.csproj" AdditionalProperties="Configuration=Debug" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.20.9.0</Version>
|
||||
<Version>0.20.10.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.20.9.0</Version>
|
||||
<Version>0.20.10.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.20.9.0</Version>
|
||||
<Version>0.20.10.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -30,8 +30,7 @@ namespace Barotrauma.Abilities
|
||||
NotSelf = 3,
|
||||
Alive = 4,
|
||||
Monster = 5,
|
||||
InFriendlySubmarine = 6,
|
||||
Large = 7,
|
||||
InFriendlySubmarine = 6
|
||||
};
|
||||
|
||||
protected List<TargetType> ParseTargetTypes(string[] targetTypeStrings)
|
||||
@@ -80,9 +79,6 @@ namespace Barotrauma.Abilities
|
||||
return !targetCharacter.IsHuman;
|
||||
case TargetType.InFriendlySubmarine:
|
||||
return targetCharacter.Submarine != null && targetCharacter.Submarine.TeamID == character.TeamID;
|
||||
case TargetType.Large:
|
||||
// mass of mudraptor is ~48
|
||||
return targetCharacter.AnimController is { Mass: > 50.0f };
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionItem : AbilityConditionData
|
||||
{
|
||||
private readonly string[] identifiers;
|
||||
private readonly string[] tags;
|
||||
private readonly ImmutableArray<Identifier> identifiers;
|
||||
private readonly ImmutableArray<Identifier> tags;
|
||||
private readonly MapEntityCategory category = MapEntityCategory.None;
|
||||
|
||||
public AbilityConditionItem(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
identifiers = conditionElement.GetAttributeStringArray("identifiers", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
identifiers = conditionElement.GetAttributeIdentifierArray("identifiers", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
tags = conditionElement.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
category = conditionElement.GetAttributeEnum("category", MapEntityCategory.None);
|
||||
|
||||
if (!identifiers.Any() && !tags.Any())
|
||||
if (identifiers.None() && tags.None() && category == MapEntityCategory.None)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No identifiers or tags defined.");
|
||||
DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No identifiers, tags or category defined.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +37,11 @@ namespace Barotrauma.Abilities
|
||||
|
||||
if (itemPrefab != null)
|
||||
{
|
||||
if (category != MapEntityCategory.None)
|
||||
{
|
||||
if (!itemPrefab.Category.HasFlag(category)) { return false; }
|
||||
}
|
||||
|
||||
if (identifiers.Any())
|
||||
{
|
||||
if (!identifiers.Any(t => itemPrefab.Identifier == t))
|
||||
@@ -40,7 +49,6 @@ namespace Barotrauma.Abilities
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p));
|
||||
}
|
||||
else
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
foreach (Character c in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||
{
|
||||
if (c.IsUnconscious)
|
||||
if (!c.IsDead && c.IsUnconscious)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
public bool IsViable()
|
||||
{
|
||||
if (!AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
if (!AllowClientSimulation && GameMain.NetworkMember is { IsClient: true }) { return false; }
|
||||
if (RequiresAlive && Character.IsDead) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
internal sealed class CharacterAbilityUnlockApprenticeshipTalentTree : CharacterAbility
|
||||
{
|
||||
public override bool AllowClientSimulation => false;
|
||||
|
||||
public CharacterAbilityUnlockApprenticeshipTalentTree(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { }
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
|
||||
@@ -417,14 +417,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
item.body.FarseerBody.OnCollision += OnProjectileCollision;
|
||||
item.body.FarseerBody.IsBullet = true;
|
||||
|
||||
item.body.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
|
||||
if (item.Prefab.DamagedByProjectiles && !IgnoreProjectilesWhileActive)
|
||||
{
|
||||
item.body.CollidesWith |= Physics.CollisionProjectile;
|
||||
}
|
||||
|
||||
EnableProjectileCollisions();
|
||||
IsActive = true;
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
@@ -549,11 +542,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (fixture?.Body == null || fixture.IsSensor)
|
||||
{
|
||||
//ignore sensors and items
|
||||
//ignore sensors
|
||||
return true;
|
||||
}
|
||||
if (fixture.Body.UserData is VineTile) { return true; }
|
||||
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; }
|
||||
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body.UserData is Hull || fixture.UserData is Hull) { return true; }
|
||||
|
||||
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
|
||||
@@ -563,13 +555,28 @@ namespace Barotrauma.Items.Components
|
||||
if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return true; }
|
||||
}
|
||||
|
||||
//ignore everything else than characters, sub walls and level walls
|
||||
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
|
||||
!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
|
||||
!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return true; }
|
||||
|
||||
if (fixture.Body.UserData is VoronoiCell && (this.item.Submarine != null || submarine != null)) { return true; }
|
||||
|
||||
if (fixture.Body.UserData is Item item)
|
||||
{
|
||||
if (item == Item) { return true; }
|
||||
if (item.Condition <= 0) { return true; }
|
||||
if (!item.Prefab.DamagedByProjectiles && item.GetComponent<Door>() == null) { return true; }
|
||||
}
|
||||
else if (fixture.Body.UserData is Holdable { CanPush: false })
|
||||
{
|
||||
// Ignore holdables that can't push -> shouldn't block
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: This might make us ignore something we don't want to ignore?
|
||||
// Not item -> ignore everything else than characters, sub walls and level walls
|
||||
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
|
||||
!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
|
||||
!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return true; }
|
||||
}
|
||||
|
||||
fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform);
|
||||
if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
|
||||
|
||||
@@ -586,20 +593,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (fixture?.Body == null || fixture.IsSensor)
|
||||
{
|
||||
//ignore sensors and items
|
||||
//ignore sensors
|
||||
return -1;
|
||||
}
|
||||
if (fixture.Body.UserData is VineTile) { return -1; }
|
||||
|
||||
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; }
|
||||
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; }
|
||||
if (!(fixture.Body.UserData is Holdable holdable && holdable.CanPush))
|
||||
if (fixture.Body.UserData is Item item)
|
||||
{
|
||||
//ignore everything else than characters, sub walls and level walls
|
||||
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
|
||||
!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
|
||||
!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return -1; }
|
||||
if (item.Condition <= 0) { return -1; }
|
||||
if (!item.Prefab.DamagedByProjectiles && item.GetComponent<Door>() == null) { return -1; }
|
||||
}
|
||||
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; }
|
||||
|
||||
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
|
||||
if (submarine != null)
|
||||
@@ -609,6 +612,12 @@ namespace Barotrauma.Items.Components
|
||||
if (fixture.Body.UserData is Limb limb && limb.character?.Submarine != submarine) { return -1; }
|
||||
}
|
||||
|
||||
// Ignore holdables that can't push -> shouldn't block
|
||||
if (fixture.Body.UserData is Holdable { CanPush: false })
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
//ignore level cells if the item and the point of impact are inside a sub
|
||||
if (fixture.Body.UserData is VoronoiCell)
|
||||
{
|
||||
@@ -639,7 +648,7 @@ namespace Barotrauma.Items.Components
|
||||
hits.Add(new HitscanResult(fixture, point, normal, fraction));
|
||||
|
||||
return 1;
|
||||
}, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking);
|
||||
}, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking | Physics.CollisionProjectile);
|
||||
|
||||
return hits;
|
||||
}
|
||||
@@ -759,6 +768,12 @@ namespace Barotrauma.Items.Components
|
||||
else if (target.Body.UserData is Item item)
|
||||
{
|
||||
if (item.Condition <= 0.0f) { return false; }
|
||||
if (!item.Prefab.DamagedByProjectiles) { return false; }
|
||||
}
|
||||
else if (target.Body.UserData is Holdable { CanPush: false })
|
||||
{
|
||||
// Ignore holdables that can't push -> shouldn't block
|
||||
return false;
|
||||
}
|
||||
|
||||
//ignore character colliders (the projectile only hits limbs)
|
||||
@@ -774,7 +789,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.body.FarseerBody.ResetDynamics();
|
||||
}
|
||||
if (hits.Count() >= MaxTargetsToHit || target.Body.UserData is VoronoiCell)
|
||||
if (hits.Count >= MaxTargetsToHit || target.Body.UserData is VoronoiCell)
|
||||
{
|
||||
DisableProjectileCollisions();
|
||||
return true;
|
||||
@@ -1047,6 +1062,16 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private void EnableProjectileCollisions()
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
|
||||
if (!IgnoreProjectilesWhileActive)
|
||||
{
|
||||
item.body.CollidesWith |= Physics.CollisionProjectile;
|
||||
}
|
||||
}
|
||||
|
||||
private void DisableProjectileCollisions()
|
||||
{
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Barotrauma
|
||||
[Flags]
|
||||
enum MapEntityCategory
|
||||
{
|
||||
None = 0,
|
||||
Structure = 1,
|
||||
Decorative = 2,
|
||||
Machine = 4,
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
{
|
||||
GUI.DisableSavingIndicatorDelayed();
|
||||
}
|
||||
GameMain.ResetIMEWorkaround();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.20.10.0
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Input Method Editor (Chinese/Japanese input) fixes:
|
||||
- Fixed inputs sometimes not working. Happened because listboxes stole keyboard focus, for example when rearranging bot orders or interacting with a chatbox.
|
||||
- Fixed moving the input position with arrow keys in the candidate box moving the caret in the textbox too.
|
||||
- Fixed current composition text disappearing when you browse through the options with arrow keys.
|
||||
- Fixed candidate box not being hidden when there's no input (= when you've just selected the textbox or committed the input).
|
||||
|
||||
Fixes/improvements to depth charges:
|
||||
- Fixed projectile-on-projectile collisions not working properly.
|
||||
- Reduced the health of the depth charges (100 -> 10) to make them easier to detonate with turrets.
|
||||
- Made decoys self-destruct instead of just becoming disabled after 45 seconds, turning them into time bombs.
|
||||
- Fixed depth charges disappearing with a delay (after the explosion particles had gone away, making it easy to see them vanish).
|
||||
- Fixed regular depth charges being destructible before launching.
|
||||
|
||||
Misc fixes:
|
||||
- Fixed "Graduation Ceremony" talent unlocking 2 talent trees instead of one.
|
||||
- Fixed Scrap Cannon's firing sound being barely audibly.
|
||||
- Fixed "Better Than New" allowing you to repair any device past the maximum condition, not just electrical devices.
|
||||
- Adjustments to Harpoon Coil-rifle sounds.
|
||||
- Fixed some sprite bleed issues in the new talent icons.
|
||||
- Fixed Kastrull's reactor having 1000 condition instead of 100.
|
||||
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.20.9.0
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
public unsafe struct IMEString : IEnumerable<char>
|
||||
{
|
||||
internal const int IMECharBufferSize = 64;
|
||||
|
||||
public static readonly IMEString Empty = new IMEString((List<char>)null);
|
||||
|
||||
internal struct Enumerator : IEnumerator<char>
|
||||
{
|
||||
private IMEString _imeString;
|
||||
private char _currentCharacter;
|
||||
private int _currentIndex;
|
||||
|
||||
public Enumerator(IMEString imeString)
|
||||
{
|
||||
_imeString = imeString;
|
||||
_currentCharacter = '\0';
|
||||
_currentIndex = -1;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
int size = _imeString.Count;
|
||||
|
||||
_currentIndex++;
|
||||
|
||||
if (_currentIndex == size)
|
||||
return false;
|
||||
|
||||
fixed (char* ptr = _imeString.buffer)
|
||||
{
|
||||
_currentCharacter = *(ptr + _currentIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_currentIndex = -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public char Current { get { return _currentCharacter; } }
|
||||
object IEnumerator.Current { get { return Current; } }
|
||||
}
|
||||
|
||||
public int Count { get { return _size; } }
|
||||
|
||||
public char this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index >= Count || index < 0)
|
||||
throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
fixed (char* ptr = buffer)
|
||||
{
|
||||
return *(ptr + index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _size;
|
||||
|
||||
fixed char buffer[IMECharBufferSize];
|
||||
|
||||
public IMEString(string characters)
|
||||
{
|
||||
if (string.IsNullOrEmpty(characters))
|
||||
{
|
||||
_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_size = characters.Length;
|
||||
if (_size > IMECharBufferSize)
|
||||
_size = IMECharBufferSize - 1;
|
||||
|
||||
fixed (char* _ptr = buffer)
|
||||
{
|
||||
char* ptr = _ptr;
|
||||
for (var i = 0; i < _size; i++)
|
||||
{
|
||||
*ptr = characters[i];
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMEString(List<char> characters)
|
||||
{
|
||||
if (characters == null || characters.Count == 0)
|
||||
{
|
||||
_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_size = characters.Count;
|
||||
if (_size > IMECharBufferSize)
|
||||
_size = IMECharBufferSize - 1;
|
||||
|
||||
fixed (char* _ptr = buffer)
|
||||
{
|
||||
char* ptr = _ptr;
|
||||
for (var i = 0; i < _size; i++)
|
||||
{
|
||||
*ptr = characters[i];
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMEString(char[] characters, int count)
|
||||
{
|
||||
if (characters == null || count <= 0)
|
||||
{
|
||||
_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_size = count;
|
||||
if (_size > IMECharBufferSize)
|
||||
_size = IMECharBufferSize - 1;
|
||||
|
||||
if (_size > characters.Length)
|
||||
_size = characters.Length;
|
||||
|
||||
fixed (char* _ptr = buffer)
|
||||
{
|
||||
char* ptr = _ptr;
|
||||
for (var i = 0; i < _size; i++)
|
||||
{
|
||||
*ptr = characters[i];
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMEString(IntPtr bStrPtr)
|
||||
{
|
||||
if (bStrPtr == IntPtr.Zero)
|
||||
{
|
||||
_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var ptrSrc = (char*)bStrPtr;
|
||||
|
||||
int i = 0;
|
||||
|
||||
fixed (char* _ptr = buffer)
|
||||
{
|
||||
char* ptr = _ptr;
|
||||
|
||||
while (ptrSrc[i] != '\0')
|
||||
{
|
||||
*ptr = ptrSrc[i];
|
||||
i++;
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
_size = i;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
fixed (char* ptr = buffer)
|
||||
{
|
||||
return new string(ptr, 0, _size);
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr ToIntPtr()
|
||||
{
|
||||
fixed (char* ptr = buffer)
|
||||
{
|
||||
return (IntPtr)ptr;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<char> GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Arguments for the <see cref="IImmService.TextComposition" /> event.
|
||||
/// </summary>
|
||||
public struct IMETextCompositionEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
// Construct a TextCompositionEventArgs with composition infos.
|
||||
/// </summary>
|
||||
public IMETextCompositionEventArgs(IMEString compositionText,
|
||||
int cursorPosition,
|
||||
IMEString[] candidateList = null,
|
||||
int candidatePageStart = 0,
|
||||
int candidatePageSize = 0,
|
||||
int candidateSelection = 0)
|
||||
{
|
||||
CompositionText = compositionText;
|
||||
CursorPosition = cursorPosition;
|
||||
|
||||
CandidateList = candidateList;
|
||||
CandidatePageStart = candidatePageStart;
|
||||
CandidatePageSize = candidatePageSize;
|
||||
CandidateSelection = candidateSelection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The full string as it's composed by the IMM.
|
||||
/// </summary>
|
||||
public readonly IMEString CompositionText;
|
||||
|
||||
/// <summary>
|
||||
/// The position of the cursor inside the composed string.
|
||||
/// </summary>
|
||||
public readonly int CursorPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The candidate text list for the composition.
|
||||
/// This property is only supported on WindowsDX and WindowsUniversal.
|
||||
/// If the composition string does not generate candidates this array is empty.
|
||||
/// </summary>
|
||||
public readonly IMEString[] CandidateList;
|
||||
|
||||
/// <summary>
|
||||
/// First candidate index of current page.
|
||||
/// </summary>
|
||||
public readonly int CandidatePageStart;
|
||||
|
||||
/// <summary>
|
||||
/// How many candidates should display per page.
|
||||
/// </summary>
|
||||
public readonly int CandidatePageSize;
|
||||
|
||||
/// <summary>
|
||||
/// The selected candidate index.
|
||||
/// </summary>
|
||||
public readonly int CandidateSelection;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace ImeSharp
|
||||
{
|
||||
public struct IMETextInputEventArgs
|
||||
{
|
||||
public IMETextInputEventArgs(char character)
|
||||
{
|
||||
Character = character;
|
||||
}
|
||||
|
||||
public readonly char Character;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>C# wrapper for Windows IME APIs. Its goal is to support both IMM32 and TSF.</Description>
|
||||
<PackageTags>IME;netcoreapp3.1;net5.0;net6.0;winforms;windows;tsf;imm32</PackageTags>
|
||||
<PackageId>ImeSharp</PackageId>
|
||||
<PackageProjectUrl>https://github.com/ryancheung/ImeSharp</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/ryancheung/ImeSharp</RepositoryUrl>
|
||||
<Authors>ryancheung</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<BaseOutputPath>..\Artifacts</BaseOutputPath>
|
||||
<AssemblyName>ImeSharp</AssemblyName>
|
||||
<RootNamespace>ImeSharp</RootNamespace>
|
||||
<LangVersion>5</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TsfSharp" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,349 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using ImeSharp.Native;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
internal class Imm32Manager
|
||||
{
|
||||
|
||||
// If the system is IMM enabled, this is true.
|
||||
private static bool _immEnabled = SafeSystemMetrics.IsImmEnabled;
|
||||
|
||||
public static bool ImmEnabled { get { return _immEnabled; } }
|
||||
|
||||
public const int LANG_CHINESE = 0x04;
|
||||
public const int LANG_KOREAN = 0x12;
|
||||
public const int LANG_JAPANESE = 0x11;
|
||||
|
||||
public static int PRIMARYLANGID(int lgid)
|
||||
{
|
||||
return ((ushort)(lgid) & 0x3ff);
|
||||
}
|
||||
|
||||
static Imm32Manager()
|
||||
{
|
||||
SetCurrentCulture();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return true if the current keyboard layout is a real IMM32-IME.
|
||||
/// </summary>
|
||||
public static bool IsImm32ImeCurrent()
|
||||
{
|
||||
if (!_immEnabled)
|
||||
return false;
|
||||
|
||||
IntPtr hkl = NativeMethods.GetKeyboardLayout(0);
|
||||
|
||||
return IsImm32Ime(hkl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return true if the keyboard layout is a real IMM32-IME.
|
||||
/// </summary>
|
||||
public static bool IsImm32Ime(IntPtr hkl)
|
||||
{
|
||||
if (hkl == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
return ((NativeMethods.IntPtrToInt32(hkl) & 0xf0000000) == 0xe0000000);
|
||||
}
|
||||
|
||||
private static int _inputLanguageId;
|
||||
|
||||
internal static void SetCurrentCulture()
|
||||
{
|
||||
var hkl = NativeMethods.GetKeyboardLayout(0);
|
||||
_inputLanguageId = NativeMethods.IntPtrToInt32(hkl) & 0xFFFF;
|
||||
}
|
||||
|
||||
private IntPtr _windowHandle;
|
||||
|
||||
private IntPtr _defaultImc;
|
||||
private IntPtr DefaultImc
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_defaultImc == IntPtr.Zero)
|
||||
{
|
||||
IntPtr himc = NativeMethods.ImmCreateContext();
|
||||
|
||||
// Store the default imc to _defaultImc.
|
||||
_defaultImc = himc;
|
||||
}
|
||||
return _defaultImc;
|
||||
}
|
||||
}
|
||||
|
||||
private static ImmCompositionStringHandler _compositionStringHandler;
|
||||
private static ImmCompositionIntHandler _compositionCursorHandler;
|
||||
|
||||
private bool _lastImmOpenStatus;
|
||||
|
||||
public Imm32Manager(IntPtr windowHandle)
|
||||
{
|
||||
_windowHandle = windowHandle;
|
||||
|
||||
_compositionStringHandler = new ImmCompositionStringHandler(DefaultImc, NativeMethods.GCS_COMPSTR);
|
||||
_compositionCursorHandler = new ImmCompositionIntHandler(DefaultImc, NativeMethods.GCS_CURSORPOS);
|
||||
}
|
||||
|
||||
public static Imm32Manager Current
|
||||
{
|
||||
get
|
||||
{
|
||||
var defaultImm32Manager = InputMethod.DefaultImm32Manager;
|
||||
|
||||
if (defaultImm32Manager == null)
|
||||
{
|
||||
defaultImm32Manager = new Imm32Manager(InputMethod.WindowHandle);
|
||||
InputMethod.DefaultImm32Manager = defaultImm32Manager;
|
||||
}
|
||||
|
||||
return defaultImm32Manager;
|
||||
}
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if (DefaultImc != IntPtr.Zero)
|
||||
{
|
||||
// Create a temporary system caret
|
||||
NativeMethods.CreateCaret(_windowHandle, IntPtr.Zero, 2, 10);
|
||||
NativeMethods.ImmAssociateContext(_windowHandle, _defaultImc);
|
||||
}
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
NativeMethods.ImmAssociateContext(_windowHandle, IntPtr.Zero);
|
||||
NativeMethods.DestroyCaret();
|
||||
}
|
||||
|
||||
const int kCaretMargin = 1;
|
||||
|
||||
// Set candidate window position.
|
||||
// Borrowed from https://github.com/chromium/chromium/blob/master/ui/base/ime/win/imm32_manager.cc
|
||||
public void SetCandidateWindow(TsfSharp.Rect caretRect)
|
||||
{
|
||||
int x = caretRect.Left;
|
||||
int y = caretRect.Top;
|
||||
|
||||
if (PRIMARYLANGID(_inputLanguageId) == LANG_CHINESE)
|
||||
{
|
||||
// Chinese IMEs ignore function calls to ::ImmSetCandidateWindow()
|
||||
// when a user disables TSF (Text Service Framework) and CUAS (Cicero
|
||||
// Unaware Application Support).
|
||||
// On the other hand, when a user enables TSF and CUAS, Chinese IMEs
|
||||
// ignore the position of the current system caret and uses the
|
||||
// parameters given to ::ImmSetCandidateWindow() with its 'dwStyle'
|
||||
// parameter CFS_CANDIDATEPOS.
|
||||
// Therefore, we do not only call ::ImmSetCandidateWindow() but also
|
||||
// set the positions of the temporary system caret.
|
||||
var candidateForm = new NativeMethods.CANDIDATEFORM();
|
||||
candidateForm.dwStyle = NativeMethods.CFS_CANDIDATEPOS;
|
||||
candidateForm.ptCurrentPos.X = x;
|
||||
candidateForm.ptCurrentPos.Y = y;
|
||||
NativeMethods.ImmSetCandidateWindow(_defaultImc, ref candidateForm);
|
||||
}
|
||||
|
||||
if (PRIMARYLANGID(_inputLanguageId) == LANG_JAPANESE)
|
||||
NativeMethods.SetCaretPos(x, caretRect.Bottom);
|
||||
else
|
||||
NativeMethods.SetCaretPos(x, y);
|
||||
|
||||
// Set composition window position also to ensure move the candidate window position.
|
||||
var compositionForm = new NativeMethods.COMPOSITIONFORM();
|
||||
compositionForm.dwStyle = NativeMethods.CFS_POINT;
|
||||
compositionForm.ptCurrentPos.X = x;
|
||||
compositionForm.ptCurrentPos.Y = y;
|
||||
NativeMethods.ImmSetCompositionWindow(_defaultImc, ref compositionForm);
|
||||
|
||||
if (PRIMARYLANGID(_inputLanguageId) == LANG_KOREAN)
|
||||
{
|
||||
// Chinese IMEs and Japanese IMEs require the upper-left corner of
|
||||
// the caret to move the position of their candidate windows.
|
||||
// On the other hand, Korean IMEs require the lower-left corner of the
|
||||
// caret to move their candidate windows.
|
||||
y += kCaretMargin;
|
||||
}
|
||||
|
||||
// Need to return here since some Chinese IMEs would stuck if set
|
||||
// candidate window position with CFS_EXCLUDE style.
|
||||
if (PRIMARYLANGID(_inputLanguageId) == LANG_CHINESE) return;
|
||||
|
||||
// Japanese IMEs and Korean IMEs also use the rectangle given to
|
||||
// ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE
|
||||
// to move their candidate windows when a user disables TSF and CUAS.
|
||||
// Therefore, we also set this parameter here.
|
||||
var excludeRectangle = new NativeMethods.CANDIDATEFORM();
|
||||
compositionForm.dwStyle = NativeMethods.CFS_EXCLUDE;
|
||||
compositionForm.ptCurrentPos.X = x;
|
||||
compositionForm.ptCurrentPos.Y = y;
|
||||
compositionForm.rcArea.Left = x;
|
||||
compositionForm.rcArea.Top = y;
|
||||
compositionForm.rcArea.Right = caretRect.Right;
|
||||
compositionForm.rcArea.Bottom = caretRect.Bottom;
|
||||
NativeMethods.ImmSetCandidateWindow(_defaultImc, ref excludeRectangle);
|
||||
}
|
||||
|
||||
internal bool ProcessMessage(IntPtr hWnd, uint msg, ref IntPtr wParam, ref IntPtr lParam)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case NativeMethods.WM_INPUTLANGCHANGE:
|
||||
SetCurrentCulture();
|
||||
break;
|
||||
case NativeMethods.WM_IME_SETCONTEXT:
|
||||
if (wParam.ToInt32() == 1 && InputMethod.Enabled)
|
||||
{
|
||||
// Must re-associate ime context or things won't work.
|
||||
NativeMethods.ImmAssociateContext(_windowHandle, DefaultImc);
|
||||
|
||||
if (_lastImmOpenStatus)
|
||||
NativeMethods.ImmSetOpenStatus(DefaultImc, true);
|
||||
|
||||
var lParam64 = lParam.ToInt64();
|
||||
if (!InputMethod.ShowOSImeWindow)
|
||||
lParam64 &= ~NativeMethods.ISC_SHOWUICANDIDATEWINDOW;
|
||||
else
|
||||
lParam64 &= ~NativeMethods.ISC_SHOWUICOMPOSITIONWINDOW;
|
||||
lParam = (IntPtr)(int)lParam64;
|
||||
}
|
||||
break;
|
||||
case NativeMethods.WM_KILLFOCUS:
|
||||
_lastImmOpenStatus = NativeMethods.ImmGetOpenStatus(DefaultImc);
|
||||
break;
|
||||
case NativeMethods.WM_IME_NOTIFY:
|
||||
IMENotify(wParam.ToInt32());
|
||||
if (!InputMethod.ShowOSImeWindow)
|
||||
return true;
|
||||
break;
|
||||
case NativeMethods.WM_IME_STARTCOMPOSITION:
|
||||
//Debug.WriteLine("NativeMethods.WM_IME_STARTCOMPOSITION");
|
||||
IMEStartComposion(lParam.ToInt32());
|
||||
// Force to not show composition window, `lParam64 &= ~ISC_SHOWUICOMPOSITIONWINDOW` don't work sometime.
|
||||
return true;
|
||||
case NativeMethods.WM_IME_COMPOSITION:
|
||||
//Debug.WriteLine("NativeMethods.WM_IME_COMPOSITION");
|
||||
IMEComposition(lParam.ToInt32());
|
||||
break;
|
||||
case NativeMethods.WM_IME_ENDCOMPOSITION:
|
||||
//Debug.WriteLine("NativeMethods.WM_IME_ENDCOMPOSITION");
|
||||
IMEEndComposition(lParam.ToInt32());
|
||||
if (!InputMethod.ShowOSImeWindow)
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void IMENotify(int WParam)
|
||||
{
|
||||
switch (WParam)
|
||||
{
|
||||
case NativeMethods.IMN_OPENCANDIDATE:
|
||||
case NativeMethods.IMN_CHANGECANDIDATE:
|
||||
IMEChangeCandidate();
|
||||
break;
|
||||
case NativeMethods.IMN_CLOSECANDIDATE:
|
||||
InputMethod.ClearCandidates();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void IMEChangeCandidate()
|
||||
{
|
||||
if (TextServicesLoader.ServicesInstalled) // TSF is enabled
|
||||
{
|
||||
if (!TextStore.Current.SupportUIElement) // But active IME not support UIElement
|
||||
UpdateCandidates(); // We have to fetch candidate list here.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal candidate list fetch in IMM32
|
||||
UpdateCandidates();
|
||||
// Send event on candidate updates
|
||||
InputMethod.OnTextComposition(this, new IMEString(_compositionStringHandler.Values, _compositionStringHandler.Count), _compositionCursorHandler.Value);
|
||||
|
||||
if (InputMethod.CandidateList != null)
|
||||
ArrayPool<IMEString>.Shared.Return(InputMethod.CandidateList);
|
||||
}
|
||||
|
||||
private unsafe void UpdateCandidates()
|
||||
{
|
||||
uint length = NativeMethods.ImmGetCandidateList(DefaultImc, 0, IntPtr.Zero, 0);
|
||||
if (length > 0)
|
||||
{
|
||||
IntPtr pointer = Marshal.AllocHGlobal((int)length);
|
||||
length = NativeMethods.ImmGetCandidateList(DefaultImc, 0, pointer, length);
|
||||
NativeMethods.CANDIDATELIST* cList = (NativeMethods.CANDIDATELIST*)pointer;
|
||||
|
||||
var selection = (int)cList->dwSelection;
|
||||
var pageStart = (int)cList->dwPageStart;
|
||||
var pageSize = (int)cList->dwPageSize;
|
||||
|
||||
selection -= pageStart;
|
||||
|
||||
IMEString[] candidates = ArrayPool<IMEString>.Shared.Rent(pageSize);
|
||||
|
||||
int i, j;
|
||||
for (i = pageStart, j = 0; i < cList->dwCount && j < pageSize; i++, j++)
|
||||
{
|
||||
int sOffset = Marshal.ReadInt32(pointer, 24 + 4 * i);
|
||||
candidates[j] = new IMEString(pointer + sOffset);
|
||||
}
|
||||
|
||||
//Debug.WriteLine("IMM========IMM");
|
||||
//Debug.WriteLine("pageStart: {0}, pageSize: {1}, selection: {2}, candidates:", pageStart, pageSize, selection);
|
||||
//for (int k = 0; k < candidates.Length; k++)
|
||||
// Debug.WriteLine(" {2}{0}.{1}", k + 1, candidates[k], k == selection ? "*" : "");
|
||||
//Debug.WriteLine("IMM++++++++IMM");
|
||||
|
||||
InputMethod.CandidatePageStart = pageStart;
|
||||
InputMethod.CandidatePageSize = pageSize;
|
||||
InputMethod.CandidateSelection = selection;
|
||||
InputMethod.CandidateList = candidates;
|
||||
|
||||
Marshal.FreeHGlobal(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearComposition()
|
||||
{
|
||||
_compositionStringHandler.Clear();
|
||||
}
|
||||
|
||||
private void IMEStartComposion(int lParam)
|
||||
{
|
||||
InputMethod.OnTextCompositionStarted(this);
|
||||
ClearComposition();
|
||||
}
|
||||
|
||||
private void IMEComposition(int lParam)
|
||||
{
|
||||
if (_compositionStringHandler.Update(lParam))
|
||||
{
|
||||
_compositionCursorHandler.Update();
|
||||
|
||||
InputMethod.OnTextComposition(this, new IMEString(_compositionStringHandler.Values, _compositionStringHandler.Count), _compositionCursorHandler.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void IMEEndComposition(int lParam)
|
||||
{
|
||||
InputMethod.ClearCandidates();
|
||||
ClearComposition();
|
||||
|
||||
InputMethod.OnTextCompositionEnded(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using ImeSharp.Native;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
internal abstract class ImmCompositionResultHandler
|
||||
{
|
||||
protected IntPtr _imeContext;
|
||||
|
||||
public int Flag { get; private set; }
|
||||
|
||||
internal ImmCompositionResultHandler(IntPtr imeContext, int flag)
|
||||
{
|
||||
this.Flag = flag;
|
||||
_imeContext = imeContext;
|
||||
}
|
||||
|
||||
internal virtual void Update() { }
|
||||
|
||||
internal bool Update(int lParam)
|
||||
{
|
||||
if ((lParam & Flag) == Flag)
|
||||
{
|
||||
Update();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ImmCompositionStringHandler : ImmCompositionResultHandler
|
||||
{
|
||||
internal const int BufferSize = 1024;
|
||||
private byte[] _byteBuffer = new byte[BufferSize];
|
||||
private int _byteCount;
|
||||
|
||||
private char[] _charBuffer = new char[BufferSize / 2];
|
||||
private int _charCount;
|
||||
|
||||
public char[] Values { get { return _charBuffer; } }
|
||||
public int Count { get { return _charCount; } }
|
||||
|
||||
public char this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index >= _charCount || index < 0)
|
||||
throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
return _charBuffer[index];
|
||||
}
|
||||
}
|
||||
|
||||
internal ImmCompositionStringHandler(IntPtr imeContext, int flag) : base(imeContext, flag)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (_charCount <= 0)
|
||||
return string.Empty;
|
||||
|
||||
return new string(_charBuffer, 0, _charCount);
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
Array.Clear(_byteBuffer, 0, _byteCount);
|
||||
_byteCount = 0;
|
||||
|
||||
Array.Clear(_charBuffer, 0, _charCount);
|
||||
_charCount = 0;
|
||||
}
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
_byteCount = NativeMethods.ImmGetCompositionString(_imeContext, Flag, IntPtr.Zero, 0);
|
||||
IntPtr pointer = Marshal.AllocHGlobal(_byteCount);
|
||||
|
||||
try
|
||||
{
|
||||
Array.Clear(_byteBuffer, 0, _byteCount);
|
||||
|
||||
if (_byteCount > 0)
|
||||
{
|
||||
NativeMethods.ImmGetCompositionString(_imeContext, Flag, pointer, _byteCount);
|
||||
|
||||
Marshal.Copy(pointer, _byteBuffer, 0, _byteCount);
|
||||
|
||||
Array.Clear(_charBuffer, 0, _charCount);
|
||||
_charCount = Encoding.Unicode.GetChars(_byteBuffer, 0, _byteCount, _charBuffer, 0);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ImmCompositionIntHandler : ImmCompositionResultHandler
|
||||
{
|
||||
public int Value { get; private set; }
|
||||
|
||||
internal ImmCompositionIntHandler(IntPtr imeContext, int flag) : base(imeContext, flag) { }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
Value = NativeMethods.ImmGetCompositionString(_imeContext, Flag, IntPtr.Zero, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using ImeSharp.Native;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
public static class InputMethod
|
||||
{
|
||||
private static IntPtr _windowHandle;
|
||||
public static IntPtr WindowHandle { get { return _windowHandle; } }
|
||||
|
||||
private static IntPtr _prevWndProc;
|
||||
private static NativeMethods.WndProcDelegate _wndProcDelegate;
|
||||
|
||||
private static TextServicesContext _textServicesContext;
|
||||
internal static TextServicesContext TextServicesContext
|
||||
{
|
||||
get { return _textServicesContext; }
|
||||
set { _textServicesContext = value; }
|
||||
}
|
||||
|
||||
private static TextStore _defaultTextStore;
|
||||
internal static TextStore DefaultTextStore
|
||||
{
|
||||
get { return _defaultTextStore; }
|
||||
set { _defaultTextStore = value; }
|
||||
}
|
||||
|
||||
private static Imm32Manager _defaultImm32Manager;
|
||||
internal static Imm32Manager DefaultImm32Manager
|
||||
{
|
||||
get { return _defaultImm32Manager; }
|
||||
set { _defaultImm32Manager = value; }
|
||||
}
|
||||
|
||||
private static bool _enabled;
|
||||
public static bool Enabled
|
||||
{
|
||||
get { return _enabled; }
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
|
||||
EnableOrDisableInputMethod(_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
internal static TsfSharp.Rect TextInputRect;
|
||||
|
||||
/// <summary>
|
||||
/// Set the position of the candidate window rendered by the OS.
|
||||
/// Let the OS render the candidate window by set param "showOSImeWindow" to <c>true</c> on <see cref="Initialize"/>.
|
||||
/// </summary>
|
||||
public static void SetTextInputRect(int x, int y, int width, int height)
|
||||
{
|
||||
if (!_showOSImeWindow) return;
|
||||
|
||||
TextInputRect.Left = x;
|
||||
TextInputRect.Top = y;
|
||||
TextInputRect.Right = x + width;
|
||||
TextInputRect.Bottom = y + height;
|
||||
|
||||
if (Imm32Manager.ImmEnabled)
|
||||
Imm32Manager.Current.SetCandidateWindow(TextInputRect);
|
||||
}
|
||||
|
||||
private static bool _showOSImeWindow = false;
|
||||
|
||||
/// <summary>
|
||||
/// Return if let OS render IME Candidate window or not.
|
||||
/// </summary>
|
||||
public static bool ShowOSImeWindow { get { return _showOSImeWindow; } }
|
||||
|
||||
internal static int CandidatePageStart;
|
||||
internal static int CandidatePageSize;
|
||||
internal static int CandidateSelection;
|
||||
internal static IMEString[] CandidateList;
|
||||
|
||||
internal static void ClearCandidates()
|
||||
{
|
||||
CandidateList = null;
|
||||
CandidatePageStart = 0;
|
||||
CandidatePageSize = 0;
|
||||
CandidateSelection = 0;
|
||||
}
|
||||
|
||||
public static event EventHandler<IMETextCompositionEventArgs> TextComposition;
|
||||
public static event EventHandler<IMETextInputEventArgs> TextInput;
|
||||
public static event EventHandler<string> CommitTextComposition;
|
||||
|
||||
public static TextInputCallback TextInputCallback { get; set; }
|
||||
public static TextCompositionCallback TextCompositionCallback { get; set; }
|
||||
public static CommitTextCompositionCallback CommitTextCompositionCallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize InputMethod with a Window Handle.
|
||||
/// Let the OS render the candidate window by set <see paramref="showOSImeWindow"/> to <c>true</c>.
|
||||
/// </summary>
|
||||
public static void Initialize(IntPtr windowHandle, bool showOSImeWindow = true)
|
||||
{
|
||||
if (_windowHandle != IntPtr.Zero)
|
||||
throw new InvalidOperationException("InputMethod can only be initialized once!");
|
||||
|
||||
_windowHandle = windowHandle;
|
||||
_showOSImeWindow = showOSImeWindow;
|
||||
|
||||
_wndProcDelegate = new NativeMethods.WndProcDelegate(WndProc);
|
||||
_prevWndProc = (IntPtr)NativeMethods.SetWindowLongPtr(_windowHandle, NativeMethods.GWL_WNDPROC,
|
||||
Marshal.GetFunctionPointerForDelegate(_wndProcDelegate));
|
||||
}
|
||||
|
||||
internal static void OnTextInput(object sender, char character)
|
||||
{
|
||||
if (TextInput != null)
|
||||
TextInput.Invoke(sender, new IMETextInputEventArgs(character));
|
||||
|
||||
if (TextInputCallback != null)
|
||||
TextInputCallback(character);
|
||||
}
|
||||
|
||||
// Some Chinese IME only send composition start event but no composition update event.
|
||||
// We need this to ensure candidate window position can be set in time.
|
||||
internal static void OnTextCompositionStarted(object sender)
|
||||
{
|
||||
if (TextComposition != null)
|
||||
TextComposition.Invoke(sender, new IMETextCompositionEventArgs(IMEString.Empty, 0));
|
||||
|
||||
if (TextCompositionCallback != null)
|
||||
TextCompositionCallback(IMEString.Empty, 0, null, 0, 0, 0);
|
||||
}
|
||||
|
||||
// On text composition update.
|
||||
internal static void OnTextComposition(object sender, IMEString compositionText, int cursorPos)
|
||||
{
|
||||
if (compositionText.Count == 0) // Crash guard
|
||||
cursorPos = 0;
|
||||
|
||||
if (cursorPos > compositionText.Count) // Another crash guard
|
||||
cursorPos = compositionText.Count;
|
||||
|
||||
if (TextComposition != null)
|
||||
{
|
||||
TextComposition.Invoke(sender,
|
||||
new IMETextCompositionEventArgs(compositionText, cursorPos, CandidateList, CandidatePageStart, CandidatePageSize, CandidateSelection));
|
||||
}
|
||||
|
||||
if (TextCompositionCallback != null)
|
||||
TextCompositionCallback(compositionText, cursorPos, CandidateList, CandidatePageStart, CandidatePageSize, CandidateSelection);
|
||||
}
|
||||
|
||||
internal static void OnTextCompositionResult(object sender, string compositionResult)
|
||||
{
|
||||
if (CommitTextComposition != null)
|
||||
CommitTextComposition.Invoke(sender, compositionResult);
|
||||
|
||||
if (CommitTextCompositionCallback != null)
|
||||
CommitTextCompositionCallback(compositionResult);
|
||||
}
|
||||
|
||||
internal static void OnTextCompositionEnded(object sender)
|
||||
{
|
||||
if (TextComposition != null)
|
||||
TextComposition.Invoke(sender, new IMETextCompositionEventArgs(IMEString.Empty, 0));
|
||||
|
||||
if (TextCompositionCallback != null)
|
||||
TextCompositionCallback(IMEString.Empty, 0, null, 0, 0, 0);
|
||||
}
|
||||
|
||||
private static void EnableOrDisableInputMethod(bool bEnabled)
|
||||
{
|
||||
// InputMethod enable/disabled status was changed on the current focus Element.
|
||||
if (TextServicesLoader.ServicesInstalled)
|
||||
{
|
||||
if (bEnabled)
|
||||
TextServicesContext.Current.SetFocusOnDefaultTextStore();
|
||||
else
|
||||
TextServicesContext.Current.SetFocusOnEmptyDim();
|
||||
}
|
||||
|
||||
// Under IMM32 enabled system, we associate default hIMC or null hIMC.
|
||||
if (Imm32Manager.ImmEnabled)
|
||||
{
|
||||
if (bEnabled)
|
||||
Imm32Manager.Current.Enable();
|
||||
else
|
||||
Imm32Manager.Current.Disable();
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
if (Imm32Manager.ImmEnabled)
|
||||
{
|
||||
if (Imm32Manager.Current.ProcessMessage(hWnd, msg, ref wParam, ref lParam))
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case NativeMethods.WM_DESTROY:
|
||||
TextServicesContext.Current.Uninitialize(true);
|
||||
break;
|
||||
case NativeMethods.WM_CHAR:
|
||||
{
|
||||
if (InputMethod.Enabled)
|
||||
InputMethod.OnTextInput(null, (char)wParam.ToInt32());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NativeMethods.CallWindowProc(_prevWndProc, hWnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom windows message pumping to fix frame stuck issue.
|
||||
/// Normally, you need call this method in <see cref="Application.Idle" /> handler.
|
||||
/// </summary>
|
||||
public static void PumpMessage()
|
||||
{
|
||||
if (!Enabled) return;
|
||||
if (!TextServicesLoader.ServicesInstalled) return;
|
||||
|
||||
bool result;
|
||||
var msg = new NativeMethods.NativeMessage();
|
||||
|
||||
do
|
||||
{
|
||||
result = NativeMethods.PeekMessage(out msg, _windowHandle, 0, 0, NativeMethods.PM_REMOVE);
|
||||
|
||||
if (result)
|
||||
{
|
||||
NativeMethods.TranslateMessage(ref msg);
|
||||
NativeMethods.DispatchMessage(ref msg);
|
||||
}
|
||||
} while (result);
|
||||
|
||||
NativeMethods.PostMessage(_windowHandle, NativeMethods.WM_NULL, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ImeSharp.Native
|
||||
{
|
||||
public partial class NativeMethods
|
||||
{
|
||||
#region Constants
|
||||
|
||||
public const int S_OK = 0x00000000;
|
||||
public const int S_FALSE = 0x00000001;
|
||||
public const int E_FAIL = unchecked((int)0x80004005);
|
||||
public const int E_INVALIDARG = unchecked((int)0x80070057);
|
||||
public const int E_NOTIMPL = unchecked((int)0x80004001);
|
||||
|
||||
public const int WM_KEYFIRST = 0x0100;
|
||||
public const int WM_KEYDOWN = 0x0100;
|
||||
public const int WM_KEYUP = 0x0101;
|
||||
public const int WM_CHAR = 0x0102;
|
||||
public const int WM_DEADCHAR = 0x0103;
|
||||
public const int WM_SYSKEYDOWN = 0x0104;
|
||||
public const int WM_SYSKEYUP = 0x0105;
|
||||
public const int WM_SYSCHAR = 0x0106;
|
||||
public const int WM_SYSDEADCHAR = 0x0107;
|
||||
public const int WM_UNICHAR = 0x0109;
|
||||
public const int WM_KEYLAST = 0x0109;
|
||||
public const int UNICODE_NOCHAR = 0xFFFF;
|
||||
|
||||
public const int WM_NOTIFY = 0x004E;
|
||||
public const int WM_INPUTLANGCHANGEREQUEST = 0x0050;
|
||||
public const int WM_INPUTLANGCHANGE = 0x0051;
|
||||
public const int WM_TCARD = 0x0052;
|
||||
public const int WM_HELP = 0x0053;
|
||||
public const int WM_USERCHANGED = 0x0054;
|
||||
public const int WM_NOTIFYFORMAT = 0x0055;
|
||||
|
||||
public const int GWL_WNDPROC = -4;
|
||||
|
||||
public const int WM_ACTIVATE = 0x0006;
|
||||
// WM_ACTIVATE state values
|
||||
public const int WA_INACTIVE = 0;
|
||||
public const int WA_ACTIVE = 1;
|
||||
public const int WA_CLICKACTIVE = 2;
|
||||
|
||||
public const int WM_SETFOCUS = 0x0007;
|
||||
public const int WM_KILLFOCUS = 0x0008;
|
||||
|
||||
public const int WM_DESTROY = 0x0002;
|
||||
public const int WM_NULL = 0x0000;
|
||||
public const int WM_QUIT = 0x0012;
|
||||
|
||||
public const int CLSCTX_INPROC_SERVER = 0x1;
|
||||
|
||||
public const int PM_NOREMOVE = 0x0000;
|
||||
public const int PM_REMOVE = 0x0001;
|
||||
public const int PM_NOYIELD = 0x0002;
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Structs
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct NativeMessage
|
||||
{
|
||||
public IntPtr handle;
|
||||
public uint msg;
|
||||
public IntPtr wParam;
|
||||
public IntPtr lParam;
|
||||
public uint time;
|
||||
public int ptX;
|
||||
public int ptY;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int GetSystemMetrics(SM nIndex);
|
||||
|
||||
// We have this wrapper because casting IntPtr to int may
|
||||
// generate OverflowException when one of high 32 bits is set.
|
||||
public static int IntPtrToInt32(IntPtr intPtr)
|
||||
{
|
||||
return unchecked((int)intPtr.ToInt64());
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr GetKeyboardLayout(int dwLayout);
|
||||
|
||||
public delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
// This static method is required because legacy OSes do not support
|
||||
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
|
||||
else
|
||||
return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Unicode)]
|
||||
private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool GetWindowRect(IntPtr hwnd, out TsfSharp.Rect lpRect);
|
||||
|
||||
[DllImport("user32", ExactSpelling = true, SetLastError = true)]
|
||||
public static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, ref TsfSharp.Rect rect, [MarshalAs(UnmanagedType.U4)] int cPoints);
|
||||
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, StringBuilder lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool TranslateMessage(ref NativeMessage lpMsg);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr DispatchMessage(ref NativeMessage lpmsg);
|
||||
|
||||
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern bool PeekMessage(out NativeMessage msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
|
||||
|
||||
|
||||
[DllImport("ole32.dll", ExactSpelling = true, EntryPoint = "CoCreateInstance", PreserveSig = true)]
|
||||
public static extern int CoCreateInstance([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, IntPtr pUnkOuter, int dwClsContext, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ImeSharp.Native
|
||||
{
|
||||
public partial class NativeMethods
|
||||
{
|
||||
#region Constants
|
||||
|
||||
public const int WM_IME_SETCONTEXT = 0x0281;
|
||||
public const int WM_IME_NOTIFY = 0x0282;
|
||||
public const int WM_IME_CONTROL = 0x0283;
|
||||
public const int WM_IME_COMPOSITIONFULL = 0x0284;
|
||||
public const int WM_IME_SELECT = 0x0285;
|
||||
public const int WM_IME_CHAR = 0x0286;
|
||||
public const int WM_IME_REQUEST = 0x0288;
|
||||
public const int WM_IME_KEYDOWN = 0x0290;
|
||||
public const int WM_IME_KEYUP = 0x0291;
|
||||
public const int WM_IME_STARTCOMPOSITION = 0x010D;
|
||||
public const int WM_IME_ENDCOMPOSITION = 0x010E;
|
||||
public const int WM_IME_COMPOSITION = 0x010F;
|
||||
public const int WM_IME_KEYLAST = 0x010F;
|
||||
|
||||
// wParam of report message WM_IME_NOTIFY
|
||||
public const int IMN_CLOSESTATUSWINDOW = 0x0001;
|
||||
public const int IMN_OPENSTATUSWINDOW = 0x0002;
|
||||
public const int IMN_CHANGECANDIDATE = 0x0003;
|
||||
public const int IMN_CLOSECANDIDATE = 0x0004;
|
||||
public const int IMN_OPENCANDIDATE = 0x0005;
|
||||
public const int IMN_SETCONVERSIONMODE = 0x0006;
|
||||
public const int IMN_SETSENTENCEMODE = 0x0007;
|
||||
public const int IMN_SETOPENSTATUS = 0x0008;
|
||||
public const int IMN_SETCANDIDATEPOS = 0x0009;
|
||||
public const int IMN_SETCOMPOSITIONFONT = 0x000A;
|
||||
public const int IMN_SETCOMPOSITIONWINDOW = 0x000B;
|
||||
public const int IMN_SETSTATUSWINDOWPOS = 0x000C;
|
||||
public const int IMN_GUIDELINE = 0x000D;
|
||||
public const int IMN_PRIVATE = 0x000E;
|
||||
|
||||
// wParam of report message WM_IME_REQUEST
|
||||
public const int IMR_COMPOSITIONWINDOW = 0x0001;
|
||||
public const int IMR_CANDIDATEWINDOW = 0x0002;
|
||||
public const int IMR_COMPOSITIONFONT = 0x0003;
|
||||
public const int IMR_RECONVERTSTRING = 0x0004;
|
||||
public const int IMR_CONFIRMRECONVERTSTRING = 0x0005;
|
||||
public const int IMR_QUERYCHARPOSITION = 0x0006;
|
||||
public const int IMR_DOCUMENTFEED = 0x0007;
|
||||
|
||||
// parameter of ImmGetCompositionString
|
||||
public const int GCS_COMPREADSTR = 0x0001;
|
||||
public const int GCS_COMPREADATTR = 0x0002;
|
||||
public const int GCS_COMPREADCLAUSE = 0x0004;
|
||||
public const int GCS_COMPSTR = 0x0008;
|
||||
public const int GCS_COMPATTR = 0x0010;
|
||||
public const int GCS_COMPCLAUSE = 0x0020;
|
||||
public const int GCS_CURSORPOS = 0x0080;
|
||||
public const int GCS_DELTASTART = 0x0100;
|
||||
public const int GCS_RESULTREADSTR = 0x0200;
|
||||
public const int GCS_RESULTREADCLAUSE = 0x0400;
|
||||
public const int GCS_RESULTSTR = 0x0800;
|
||||
public const int GCS_RESULTCLAUSE = 0x1000;
|
||||
|
||||
public const int GCS_COMP = (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE);
|
||||
public const int GCS_COMPREAD = (GCS_COMPREADSTR | GCS_COMPREADATTR | GCS_COMPREADCLAUSE);
|
||||
public const int GCS_RESULT = (GCS_RESULTSTR | GCS_RESULTCLAUSE);
|
||||
public const int GCS_RESULTREAD = (GCS_RESULTREADSTR | GCS_RESULTREADCLAUSE);
|
||||
|
||||
public const int CFS_CANDIDATEPOS = 0x0040;
|
||||
public const int CFS_POINT = 0x0002;
|
||||
public const int CFS_EXCLUDE = 0x0080;
|
||||
|
||||
// lParam for WM_IME_SETCONTEXT
|
||||
public const long ISC_SHOWUICANDIDATEWINDOW = 0x00000001;
|
||||
public const long ISC_SHOWUICOMPOSITIONWINDOW = 0x80000000;
|
||||
public const long ISC_SHOWUIGUIDELINE = 0x40000000;
|
||||
public const long ISC_SHOWUIALLCANDIDATEWINDOW = 0x0000000F;
|
||||
public const long ISC_SHOWUIALL = 0xC000000F;
|
||||
|
||||
#endregion Constants
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct CANDIDATELIST
|
||||
{
|
||||
public uint dwSize;
|
||||
public uint dwStyle;
|
||||
public uint dwCount;
|
||||
public uint dwSelection;
|
||||
public uint dwPageStart;
|
||||
public uint dwPageSize;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.U4)]
|
||||
public fixed uint dwOffset[1];
|
||||
}
|
||||
|
||||
// CANDIDATEFORM structures
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CANDIDATEFORM
|
||||
{
|
||||
public int dwIndex;
|
||||
public int dwStyle;
|
||||
public TsfSharp.Point ptCurrentPos;
|
||||
public TsfSharp.Rect rcArea;
|
||||
}
|
||||
|
||||
// COMPOSITIONFORM structures
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct COMPOSITIONFORM
|
||||
{
|
||||
public int dwStyle;
|
||||
public TsfSharp.Point ptCurrentPos;
|
||||
public TsfSharp.Rect rcArea;
|
||||
}
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern IntPtr ImmCreateContext();
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern bool ImmDestroyContext(IntPtr hIMC);
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern IntPtr ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
|
||||
|
||||
[DllImport("imm32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern uint ImmGetCandidateList(IntPtr hIMC, uint deIndex, IntPtr candidateList, uint dwBufLen);
|
||||
|
||||
[DllImport("imm32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int ImmGetCompositionString(IntPtr hIMC, int CompositionStringFlag, IntPtr buffer, int bufferLength);
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern IntPtr ImmGetContext(IntPtr hWnd);
|
||||
|
||||
[DllImport("Imm32.dll", SetLastError = true)]
|
||||
public static extern bool ImmGetOpenStatus(IntPtr hIMC);
|
||||
|
||||
[DllImport("Imm32.dll", SetLastError = true)]
|
||||
public static extern bool ImmSetOpenStatus(IntPtr hIMC, bool open);
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern bool ImmSetCandidateWindow(IntPtr hIMC, ref CANDIDATEFORM candidateForm);
|
||||
|
||||
[DllImport("imm32.dll", SetLastError = true)]
|
||||
public static extern int ImmSetCompositionWindow(IntPtr hIMC, ref COMPOSITIONFORM compForm);
|
||||
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool DestroyCaret();
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool SetCaretPos(int x, int y);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ImeSharp.Native
|
||||
{
|
||||
/// <summary>
|
||||
/// SystemMetrics. SM_*
|
||||
/// </summary>
|
||||
public enum SM
|
||||
{
|
||||
CXSCREEN = 0,
|
||||
CYSCREEN = 1,
|
||||
CXVSCROLL = 2,
|
||||
CYHSCROLL = 3,
|
||||
CYCAPTION = 4,
|
||||
CXBORDER = 5,
|
||||
CYBORDER = 6,
|
||||
CXFIXEDFRAME = 7,
|
||||
CYFIXEDFRAME = 8,
|
||||
CYVTHUMB = 9,
|
||||
CXHTHUMB = 10,
|
||||
CXICON = 11,
|
||||
CYICON = 12,
|
||||
CXCURSOR = 13,
|
||||
CYCURSOR = 14,
|
||||
CYMENU = 15,
|
||||
CXFULLSCREEN = 16,
|
||||
CYFULLSCREEN = 17,
|
||||
CYKANJIWINDOW = 18,
|
||||
MOUSEPRESENT = 19,
|
||||
CYVSCROLL = 20,
|
||||
CXHSCROLL = 21,
|
||||
DEBUG = 22,
|
||||
SWAPBUTTON = 23,
|
||||
CXMIN = 28,
|
||||
CYMIN = 29,
|
||||
CXSIZE = 30,
|
||||
CYSIZE = 31,
|
||||
CXFRAME = 32,
|
||||
CXSIZEFRAME = CXFRAME,
|
||||
CYFRAME = 33,
|
||||
CYSIZEFRAME = CYFRAME,
|
||||
CXMINTRACK = 34,
|
||||
CYMINTRACK = 35,
|
||||
CXDOUBLECLK = 36,
|
||||
CYDOUBLECLK = 37,
|
||||
CXICONSPACING = 38,
|
||||
CYICONSPACING = 39,
|
||||
MENUDROPALIGNMENT = 40,
|
||||
PENWINDOWS = 41,
|
||||
DBCSENABLED = 42,
|
||||
CMOUSEBUTTONS = 43,
|
||||
SECURE = 44,
|
||||
CXEDGE = 45,
|
||||
CYEDGE = 46,
|
||||
CXMINSPACING = 47,
|
||||
CYMINSPACING = 48,
|
||||
CXSMICON = 49,
|
||||
CYSMICON = 50,
|
||||
CYSMCAPTION = 51,
|
||||
CXSMSIZE = 52,
|
||||
CYSMSIZE = 53,
|
||||
CXMENUSIZE = 54,
|
||||
CYMENUSIZE = 55,
|
||||
ARRANGE = 56,
|
||||
CXMINIMIZED = 57,
|
||||
CYMINIMIZED = 58,
|
||||
CXMAXTRACK = 59,
|
||||
CYMAXTRACK = 60,
|
||||
CXMAXIMIZED = 61,
|
||||
CYMAXIMIZED = 62,
|
||||
NETWORK = 63,
|
||||
CLEANBOOT = 67,
|
||||
CXDRAG = 68,
|
||||
CYDRAG = 69,
|
||||
SHOWSOUNDS = 70,
|
||||
CXMENUCHECK = 71,
|
||||
CYMENUCHECK = 72,
|
||||
SLOWMACHINE = 73,
|
||||
MIDEASTENABLED = 74,
|
||||
MOUSEWHEELPRESENT = 75,
|
||||
XVIRTUALSCREEN = 76,
|
||||
YVIRTUALSCREEN = 77,
|
||||
CXVIRTUALSCREEN = 78,
|
||||
CYVIRTUALSCREEN = 79,
|
||||
CMONITORS = 80,
|
||||
SAMEDISPLAYFORMAT = 81,
|
||||
IMMENABLED = 82,
|
||||
CXFOCUSBORDER = 83,
|
||||
CYFOCUSBORDER = 84,
|
||||
TABLETPC = 86,
|
||||
MEDIACENTER = 87,
|
||||
REMOTESESSION = 0x1000,
|
||||
REMOTECONTROL = 0x2001,
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using ImeSharp.Native;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains properties that are queries into the system's various settings.
|
||||
/// </summary>
|
||||
internal sealed class SafeSystemMetrics
|
||||
{
|
||||
|
||||
private SafeSystemMetrics()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps to SM_CXDOUBLECLK
|
||||
/// </summary>
|
||||
public static int DoubleClickDeltaX
|
||||
{
|
||||
get { return NativeMethods.GetSystemMetrics(SM.CXDOUBLECLK); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps to SM_CYDOUBLECLK
|
||||
/// </summary>
|
||||
public static int DoubleClickDeltaY
|
||||
{
|
||||
get { return NativeMethods.GetSystemMetrics(SM.CYDOUBLECLK); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Maps to SM_CXDRAG
|
||||
/// </summary>
|
||||
public static int DragDeltaX
|
||||
{
|
||||
get { return NativeMethods.GetSystemMetrics(SM.CXDRAG); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps to SM_CYDRAG
|
||||
/// </summary>
|
||||
public static int DragDeltaY
|
||||
{
|
||||
get { return NativeMethods.GetSystemMetrics(SM.CYDRAG); }
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Is an IMM enabled ? Maps to SM_IMMENABLED
|
||||
///</summary>
|
||||
public static bool IsImmEnabled
|
||||
{
|
||||
get { return (NativeMethods.GetSystemMetrics(SM.IMMENABLED) != 0); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace ImeSharp
|
||||
{
|
||||
public delegate void TextInputCallback(char character);
|
||||
public delegate void TextCompositionCallback(IMEString compositionText, int cursorPosition, IMEString[] candidateList, int candidatePageStart, int candidatePageSize, int candidateSelection);
|
||||
public delegate void CommitTextCompositionCallback(string text);
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using ImeSharp.Native;
|
||||
using TsfSharp;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// TextServicesContext class
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// This class manages the ITfThreadMgr, EmptyDim and the reference to
|
||||
/// the default TextStore.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// </remarks>
|
||||
internal class TextServicesContext
|
||||
{
|
||||
public const int TF_POPF_ALL = 0x0001;
|
||||
public const int TF_INVALID_COOKIE = -1;
|
||||
public static readonly Guid IID_ITfUIElementSink = new Guid(0xea1ea136, 0x19df, 0x11d7, 0xa6, 0xd2, 0x00, 0x06, 0x5b, 0x84, 0x43, 0x5c);
|
||||
public static readonly Guid IID_ITfTextEditSink = new Guid(0x8127d409, 0xccd3, 0x4683, 0x96, 0x7a, 0xb4, 0x3d, 0x5b, 0x48, 0x2b, 0xf7);
|
||||
|
||||
|
||||
public static TextServicesContext Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (InputMethod.TextServicesContext == null)
|
||||
InputMethod.TextServicesContext = new TextServicesContext();
|
||||
|
||||
return InputMethod.TextServicesContext;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Constructors
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a TextServicesContext.
|
||||
/// </summary>
|
||||
private TextServicesContext()
|
||||
{
|
||||
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
|
||||
Debug.WriteLine("CRASH: ImeSharp won't work on MTA thread!!!");
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// public Methods
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Releases all unmanaged resources allocated by the
|
||||
/// TextServicesContext.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// if appDomainShutdown == false, this method must be called on the
|
||||
/// Dispatcher thread. Otherwise, the caller is an AppDomain.Shutdown
|
||||
/// listener, and is calling from a worker thread.
|
||||
/// </remarks>
|
||||
public void Uninitialize(bool appDomainShutdown)
|
||||
{
|
||||
// Unregister DefaultTextStore.
|
||||
if (_defaultTextStore != null)
|
||||
{
|
||||
UnadviseSinks();
|
||||
if (_defaultTextStore.DocumentManager != null)
|
||||
{
|
||||
_defaultTextStore.DocumentManager.Pop(TF_POPF_ALL);
|
||||
_defaultTextStore.DocumentManager.Dispose();
|
||||
_defaultTextStore.DocumentManager = null;
|
||||
}
|
||||
|
||||
_defaultTextStore = null;
|
||||
}
|
||||
|
||||
// Free up any remaining textstores.
|
||||
if (_istimactivated == true)
|
||||
{
|
||||
// Shut down the thread manager when the last TextStore goes away.
|
||||
// On XP, if we're called on a worker thread (during AppDomain shutdown)
|
||||
// we can't call call any methods on _threadManager. The problem is
|
||||
// that there's no proxy registered for ITfThreadMgr on OS versions
|
||||
// previous to Vista. Not calling Deactivate will leak the IMEs, but
|
||||
// in practice (1) they're singletons, so it's not unbounded; and (2)
|
||||
// most applications will share the thread with other AppDomains that
|
||||
// have a UI, in which case the IME won't be released until the process
|
||||
// shuts down in any case. In theory we could also work around this
|
||||
// problem by creating our own XP proxy/stub implementation, which would
|
||||
// be added to WPF setup....
|
||||
if (!appDomainShutdown || System.Environment.OSVersion.Version.Major >= 6)
|
||||
{
|
||||
_threadManager.Deactivate();
|
||||
}
|
||||
_istimactivated = false;
|
||||
}
|
||||
|
||||
// Release the empty dim.
|
||||
if (_dimEmpty != null)
|
||||
{
|
||||
if (_dimEmpty != null)
|
||||
{
|
||||
_dimEmpty.Dispose();
|
||||
}
|
||||
_dimEmpty = null;
|
||||
}
|
||||
|
||||
// Release the ThreadManager.
|
||||
// We don't do this in UnregisterTextStore because someone may have
|
||||
// called get_ThreadManager after the last TextStore was unregistered.
|
||||
if (_threadManager != null)
|
||||
{
|
||||
if (_threadManager != null)
|
||||
{
|
||||
_threadManager.Dispose();
|
||||
}
|
||||
_threadManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Called by framework's TextStore class. This method registers a
|
||||
// document with TSF. The TextServicesContext must maintain this list
|
||||
// to ensure all native resources are released after gc or uninitialization.
|
||||
public void RegisterTextStore(TextStore defaultTextStore)
|
||||
{
|
||||
_defaultTextStore = defaultTextStore;
|
||||
|
||||
ITfThreadMgrEx threadManager = ThreadManager;
|
||||
|
||||
if (threadManager != null)
|
||||
{
|
||||
ITfDocumentMgr doc;
|
||||
int editCookie = TF_INVALID_COOKIE;
|
||||
|
||||
// Activate TSF on this thread if this is the first TextStore.
|
||||
if (_istimactivated == false)
|
||||
{
|
||||
//temp variable created to retrieve the value
|
||||
// which is then stored in the critical data.
|
||||
if (InputMethod.ShowOSImeWindow)
|
||||
_clientId = threadManager.Activate();
|
||||
else
|
||||
_clientId = threadManager.ActivateEx(TfTmaeFlags.Uielementenabledonly);
|
||||
|
||||
_istimactivated = true;
|
||||
}
|
||||
|
||||
// Create a TSF document.
|
||||
doc = threadManager.CreateDocumentMgr();
|
||||
_defaultTextStore.DocumentManager = doc;
|
||||
|
||||
doc.CreateContext(_clientId, 0 /* flags */, _defaultTextStore, out _editContext, out editCookie);
|
||||
_defaultTextStore.EditCookie = editCookie;
|
||||
_contextOwnerServices = _editContext.QueryInterface<ITfContextOwnerServices>();
|
||||
|
||||
doc.Push(_editContext);
|
||||
|
||||
AdviseSinks();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetFocusOnDefaultTextStore()
|
||||
{
|
||||
SetFocusOnDim(TextStore.Current.DocumentManager);
|
||||
}
|
||||
|
||||
public void SetFocusOnEmptyDim()
|
||||
{
|
||||
SetFocusOnDim(EmptyDocumentManager);
|
||||
}
|
||||
|
||||
|
||||
#endregion public Methods
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// public Properties
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// The default ITfThreadMgrEx object.
|
||||
/// </summary>
|
||||
public ITfThreadMgrEx ThreadManager
|
||||
{
|
||||
// The ITfThreadMgr for this thread.
|
||||
get
|
||||
{
|
||||
if (_threadManager == null)
|
||||
{
|
||||
ITfThreadMgr threadMgr = null;
|
||||
try
|
||||
{
|
||||
// This might fail in CoreRT
|
||||
threadMgr = Tsf.GetThreadMgr();
|
||||
}
|
||||
catch (SharpGen.Runtime.SharpGenException)
|
||||
{
|
||||
threadMgr = null;
|
||||
}
|
||||
|
||||
// Dispose previous ITfThreadMgr in case something weird happens
|
||||
if (threadMgr != null)
|
||||
{
|
||||
if (threadMgr.IsThreadFocus)
|
||||
threadMgr.Deactivate();
|
||||
threadMgr.Dispose();
|
||||
}
|
||||
|
||||
_threadManager = TextServicesLoader.Load();
|
||||
|
||||
_uiElementMgr = _threadManager.QueryInterface<ITfUIElementMgr>();
|
||||
}
|
||||
|
||||
return _threadManager;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the created ITfContext object.
|
||||
/// </summary>
|
||||
public ITfContext EditContext
|
||||
{
|
||||
get { return _editContext; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the created ITfUIElementMgr object.
|
||||
/// </summary>
|
||||
public ITfUIElementMgr UIElementMgr
|
||||
{
|
||||
get { return _uiElementMgr; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the created ITfContextOwnerServices object.
|
||||
/// </summary>
|
||||
public ITfContextOwnerServices ContextOwnerServices
|
||||
{
|
||||
get { return _contextOwnerServices; }
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// public Events
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
private void SetFocusOnDim(ITfDocumentMgr dim)
|
||||
{
|
||||
ITfThreadMgrEx threadmgr = ThreadManager;
|
||||
|
||||
if (threadmgr != null)
|
||||
{
|
||||
ITfDocumentMgr prevDocMgr = threadmgr.AssociateFocus(InputMethod.WindowHandle, dim);
|
||||
}
|
||||
}
|
||||
|
||||
private void AdviseSinks()
|
||||
{
|
||||
var source = _uiElementMgr.QueryInterface<ITfSource>();
|
||||
var guid = IID_ITfUIElementSink;
|
||||
int sinkCookie = source.AdviseSink(guid, _defaultTextStore);
|
||||
_defaultTextStore.UIElementSinkCookie = sinkCookie;
|
||||
source.Dispose();
|
||||
|
||||
source = _editContext.QueryInterface<ITfSource>();
|
||||
guid = IID_ITfTextEditSink;
|
||||
sinkCookie = source.AdviseSink(guid, _defaultTextStore);
|
||||
_defaultTextStore.TextEditSinkCookie = sinkCookie;
|
||||
source.Dispose();
|
||||
}
|
||||
|
||||
private void UnadviseSinks()
|
||||
{
|
||||
var source = _uiElementMgr.QueryInterface<ITfSource>();
|
||||
|
||||
if (_defaultTextStore.UIElementSinkCookie != TF_INVALID_COOKIE)
|
||||
{
|
||||
source.UnadviseSink(_defaultTextStore.UIElementSinkCookie);
|
||||
_defaultTextStore.UIElementSinkCookie = TF_INVALID_COOKIE;
|
||||
}
|
||||
source.Dispose();
|
||||
|
||||
source = _editContext.QueryInterface<ITfSource>();
|
||||
if (_defaultTextStore.TextEditSinkCookie != TF_INVALID_COOKIE)
|
||||
{
|
||||
source.UnadviseSink(_defaultTextStore.TextEditSinkCookie);
|
||||
_defaultTextStore.TextEditSinkCookie = TF_INVALID_COOKIE;
|
||||
}
|
||||
source.Dispose();
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Properties
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
// Create an empty dim on demand.
|
||||
private ITfDocumentMgr EmptyDocumentManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_dimEmpty == null)
|
||||
{
|
||||
ITfThreadMgrEx threadManager = ThreadManager;
|
||||
if (threadManager == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ITfDocumentMgr dimEmptyTemp;
|
||||
// Create a TSF document.
|
||||
dimEmptyTemp = threadManager.CreateDocumentMgr();
|
||||
_dimEmpty = dimEmptyTemp;
|
||||
}
|
||||
return _dimEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Fields
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private TextStore _defaultTextStore;
|
||||
|
||||
private ITfContext _editContext;
|
||||
private ITfUIElementMgr _uiElementMgr;
|
||||
private ITfContextOwnerServices _contextOwnerServices;
|
||||
|
||||
// This is true if thread manager is activated.
|
||||
private bool _istimactivated;
|
||||
|
||||
// The root TSF object, created on demand.
|
||||
private ITfThreadMgrEx _threadManager;
|
||||
|
||||
// TSF ClientId from Activate call.
|
||||
private int _clientId;
|
||||
|
||||
// The empty dim for this thread. Created on demand.
|
||||
private ITfDocumentMgr _dimEmpty;
|
||||
|
||||
#endregion Private Fields
|
||||
}
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Microsoft.Win32;
|
||||
using ImeSharp.Native;
|
||||
using TsfSharp;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
// Creates ITfThreadMgr instances, the root object of the Text Services
|
||||
// Framework.
|
||||
internal class TextServicesLoader
|
||||
{
|
||||
public static readonly Guid CLSID_TF_ThreadMgr = new Guid("529a9e6b-6587-4f23-ab9e-9c7d683e3c50");
|
||||
public static readonly Guid IID_ITfThreadMgr = new Guid("aa80e801-2021-11d2-93e0-0060b067b86e");
|
||||
public static readonly Guid IID_ITfThreadMgrEx = new Guid("3e90ade3-7594-4cb0-bb58-69628f5f458c");
|
||||
public static readonly Guid IID_ITfThreadMgr2 = new Guid("0AB198EF-6477-4EE8-8812-6780EDB82D5E");
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Constructors
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Constructors
|
||||
|
||||
// Private ctor to prevent anyone from instantiating this static class.
|
||||
private TextServicesLoader() { }
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// public Properties
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Loads an instance of the Text Services Framework.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// May return null if no text services are available.
|
||||
/// </returns>
|
||||
public static ITfThreadMgrEx Load()
|
||||
{
|
||||
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
|
||||
Debug.WriteLine("CRASH: ImeSharp won't work on MTA thread!!!");
|
||||
|
||||
if (ServicesInstalled)
|
||||
{
|
||||
// NB: a COMException here means something went wrong initialzing Cicero.
|
||||
// Cicero will throw an exception if it doesn't think it should have been
|
||||
// loaded (no TIPs to run), you can check that in msctf.dll's NoTipsInstalled
|
||||
// which lives in nt\windows\advcore\ctf\lib\immxutil.cpp. If that's the
|
||||
// problem, ServicesInstalled is out of sync with Cicero's thinking.
|
||||
IntPtr ret;
|
||||
var hr = NativeMethods.CoCreateInstance(CLSID_TF_ThreadMgr,
|
||||
IntPtr.Zero,
|
||||
NativeMethods.CLSCTX_INPROC_SERVER,
|
||||
IID_ITfThreadMgrEx, out ret);
|
||||
|
||||
if (hr == NativeMethods.S_OK)
|
||||
return new ITfThreadMgrEx(ret);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return true if current OS version is Windows 7 or below.
|
||||
/// </summary>
|
||||
public static bool IsWindows7OrBelow()
|
||||
{
|
||||
if (Environment.OSVersion.Version.Major <= 5)
|
||||
return true;
|
||||
|
||||
if (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor <= 1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Informs the caller if text services are installed for the current user.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if one or more text services are installed for the current user, otherwise false.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// If this method returns false, TextServicesLoader.Load is guarenteed to return null.
|
||||
/// Callers can use this information to avoid overhead that would otherwise be
|
||||
/// required to support text services.
|
||||
/// </remarks>
|
||||
public static bool ServicesInstalled
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (s_servicesInstalledLock)
|
||||
{
|
||||
if (s_servicesInstalled == InstallState.Unknown)
|
||||
{
|
||||
s_servicesInstalled = TIPsWantToRun() ? InstallState.Installed : InstallState.NotInstalled;
|
||||
}
|
||||
}
|
||||
|
||||
return (s_servicesInstalled == InstallState.Installed);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion public Properties
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// public Events
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Private Methods
|
||||
|
||||
//
|
||||
// This method tries to stop Avalon from loading Cicero when there are no TIPs to run.
|
||||
// The perf tradeoff is a typically small number of registry checks versus loading and
|
||||
// initializing cicero.
|
||||
//
|
||||
// The Algorithm:
|
||||
//
|
||||
// Do a quick check vs. the global disable flag, return false if it is set.
|
||||
// For each key under HKLM\SOFTWARE\Microsoft\CTF\TIP (a TIP or category clsid)
|
||||
// If the the key has a LanguageProfile subkey (it's a TIP clsid)
|
||||
// Iterate under the matching TIP entry in HKCU.
|
||||
// For each key under the LanguageProfile (a particular LANGID)
|
||||
// For each key under the LANGID (an assembly GUID)
|
||||
// Try to read the Enable value.
|
||||
// If the value is set non-zero, then stop all processing and return true.
|
||||
// If the value is set zero, continue.
|
||||
// If the value does not exist, continue (default is disabled).
|
||||
// If any Enable values were found under HKCU for the TIP, then stop all processing and return false.
|
||||
// Else, no Enable values have been found thus far and we keep going to investigate HKLM.
|
||||
// Iterate under the TIP entry in HKLM.
|
||||
// For each key under the LanguageProfile (a particular LANGID)
|
||||
// For each key under the LANGID (an assembly GUID)
|
||||
// Try to read the Enable value.
|
||||
// If the value is set non-zero, then stop all processing and return true.
|
||||
// If the value does not exist, then stop all processing and return true (default is enabled).
|
||||
// If the value is set zero, continue.
|
||||
// If we finish iterating all entries under HKLM without returning true, return false.
|
||||
//
|
||||
|
||||
private static bool TIPsWantToRun()
|
||||
{
|
||||
object obj;
|
||||
RegistryKey key;
|
||||
bool tipsWantToRun = false;
|
||||
|
||||
key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\CTF", false);
|
||||
|
||||
// Is cicero disabled completely for the current user?
|
||||
if (key != null)
|
||||
{
|
||||
obj = key.GetValue("Disable Thread Input Manager");
|
||||
|
||||
if (obj is int && (int)obj != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loop through all the TIP entries for machine and current user.
|
||||
tipsWantToRun = IterateSubKeys(Registry.LocalMachine, "SOFTWARE\\Microsoft\\CTF\\TIP", new IterateHandler(SingleTIPWantsToRun), true) == EnableState.Enabled;
|
||||
|
||||
return tipsWantToRun;
|
||||
}
|
||||
|
||||
// Returns EnableState.Enabled if one or more TIPs are installed and
|
||||
// enabled for the current user.
|
||||
private static EnableState SingleTIPWantsToRun(RegistryKey keyLocalMachine, string subKeyName, bool localMachine)
|
||||
{
|
||||
EnableState result;
|
||||
|
||||
if (subKeyName.Length != CLSIDLength)
|
||||
return EnableState.Disabled;
|
||||
|
||||
// We want subkey\LanguageProfile key.
|
||||
// Loop through all the langid entries for TIP.
|
||||
|
||||
// First, check current user.
|
||||
result = IterateSubKeys(Registry.CurrentUser, "SOFTWARE\\Microsoft\\CTF\\TIP\\" + subKeyName + "\\LanguageProfile", new IterateHandler(IsLangidEnabled), false);
|
||||
|
||||
// Any explicit value short circuits the process.
|
||||
// Otherwise check local machine.
|
||||
if (result == EnableState.None || result == EnableState.Error)
|
||||
{
|
||||
result = IterateSubKeys(keyLocalMachine, subKeyName + "\\LanguageProfile", new IterateHandler(IsLangidEnabled), true);
|
||||
|
||||
if (result == EnableState.None)
|
||||
{
|
||||
result = EnableState.Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns EnableState.Enabled if the supplied subkey is a valid LANGID key with enabled
|
||||
// cicero assembly.
|
||||
private static EnableState IsLangidEnabled(RegistryKey key, string subKeyName, bool localMachine)
|
||||
{
|
||||
if (subKeyName.Length != LANGIDLength)
|
||||
return EnableState.Error;
|
||||
|
||||
// Loop through all the assembly entries for the langid
|
||||
return IterateSubKeys(key, subKeyName, new IterateHandler(IsAssemblyEnabled), localMachine);
|
||||
}
|
||||
|
||||
// Returns EnableState.Enabled if the supplied assembly key is enabled.
|
||||
private static EnableState IsAssemblyEnabled(RegistryKey key, string subKeyName, bool localMachine)
|
||||
{
|
||||
RegistryKey subKey;
|
||||
object obj;
|
||||
|
||||
if (subKeyName.Length != CLSIDLength)
|
||||
return EnableState.Error;
|
||||
|
||||
// Open the local machine assembly key.
|
||||
subKey = key.OpenSubKey(subKeyName);
|
||||
|
||||
if (subKey == null)
|
||||
return EnableState.Error;
|
||||
|
||||
// Try to read the "Enable" value.
|
||||
obj = subKey.GetValue("Enable");
|
||||
|
||||
if (obj is int)
|
||||
{
|
||||
return ((int)obj == 0) ? EnableState.Disabled : EnableState.Enabled;
|
||||
}
|
||||
|
||||
return EnableState.None;
|
||||
}
|
||||
|
||||
// Calls the supplied delegate on each of the children of keyBase.
|
||||
private static EnableState IterateSubKeys(RegistryKey keyBase, string subKey, IterateHandler handler, bool localMachine)
|
||||
{
|
||||
RegistryKey key;
|
||||
string[] subKeyNames;
|
||||
EnableState state;
|
||||
|
||||
key = keyBase.OpenSubKey(subKey, false);
|
||||
|
||||
if (key == null)
|
||||
return EnableState.Error;
|
||||
|
||||
subKeyNames = key.GetSubKeyNames();
|
||||
state = EnableState.Error;
|
||||
|
||||
foreach (string name in subKeyNames)
|
||||
{
|
||||
switch (handler(key, name, localMachine))
|
||||
{
|
||||
case EnableState.Error:
|
||||
break;
|
||||
case EnableState.None:
|
||||
if (localMachine) // For lm, want to return here right away.
|
||||
return EnableState.None;
|
||||
|
||||
// For current user, remember that we found no Enable value.
|
||||
if (state == EnableState.Error)
|
||||
{
|
||||
state = EnableState.None;
|
||||
}
|
||||
break;
|
||||
case EnableState.Disabled:
|
||||
state = EnableState.Disabled;
|
||||
break;
|
||||
case EnableState.Enabled:
|
||||
return EnableState.Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Properties
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Fields
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Private Fields
|
||||
|
||||
// String consts used to validate registry entires.
|
||||
private const int CLSIDLength = 38; // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
private const int LANGIDLength = 10; // 0x12345678
|
||||
|
||||
// Status of a TIP assembly.
|
||||
private enum EnableState
|
||||
{
|
||||
Error, // Invalid entry.
|
||||
None, // No explicit Enable entry on the assembly.
|
||||
Enabled, // Assembly is enabled.
|
||||
Disabled // Assembly is disabled.
|
||||
};
|
||||
|
||||
// Callback delegate for the IterateSubKeys method.
|
||||
private delegate EnableState IterateHandler(RegistryKey key, string subKeyName, bool localMachine);
|
||||
|
||||
// Install state.
|
||||
private enum InstallState
|
||||
{
|
||||
Unknown, // Haven't checked to see if any TIPs are installed yet.
|
||||
Installed, // Checked and installed.
|
||||
NotInstalled // Checked and not installed.
|
||||
}
|
||||
|
||||
// Cached install state value.
|
||||
// Writes are not thread safe, but we don't mind the neglible perf hit
|
||||
// of potentially writing it twice.
|
||||
private static InstallState s_servicesInstalled = InstallState.Unknown;
|
||||
private static object s_servicesInstalledLock = new object();
|
||||
|
||||
#endregion Private Fields
|
||||
}
|
||||
}
|
||||
@@ -1,963 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using ImeSharp.Native;
|
||||
using SharpGen.Runtime;
|
||||
using SharpGen.Runtime.Win32;
|
||||
using TsfSharp;
|
||||
|
||||
namespace ImeSharp
|
||||
{
|
||||
internal class TextStore : CallbackBase,
|
||||
ITextStoreACP,
|
||||
ITfContextOwnerCompositionSink,
|
||||
ITfTextEditSink,
|
||||
ITfUIElementSink
|
||||
{
|
||||
public static readonly Guid IID_ITextStoreACPSink = new Guid(0x22d44c94, 0xa419, 0x4542, 0xa2, 0x72, 0xae, 0x26, 0x09, 0x3e, 0xce, 0xcf);
|
||||
public static readonly Guid GUID_PROP_COMPOSING = new Guid("e12ac060-af15-11d2-afc5-00105a2799b5");
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Constructors
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region Constructors
|
||||
|
||||
// Creates a TextStore instance.
|
||||
public TextStore(IntPtr windowHandle)
|
||||
{
|
||||
_windowHandle = windowHandle;
|
||||
|
||||
|
||||
_viewCookie = Environment.TickCount;
|
||||
|
||||
_editCookie = Tsf.TF_INVALID_COOKIE;
|
||||
_uiElementSinkCookie = Tsf.TF_INVALID_COOKIE;
|
||||
_textEditSinkCookie = Tsf.TF_INVALID_COOKIE;
|
||||
|
||||
_IMEStringPool = ArrayPool<IMEString>.Shared;
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Methods - ITextStoreACP
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region ITextStoreACP
|
||||
|
||||
public void AdviseSink(Guid riid, IUnknown obj, int flags)
|
||||
{
|
||||
ITextStoreACPSink sink;
|
||||
|
||||
if (riid != IID_ITextStoreACPSink)
|
||||
throw new COMException("TextStore_CONNECT_E_CANNOTCONNECT");
|
||||
|
||||
sink = (obj as ComObject).QueryInterface<ITextStoreACPSink>();
|
||||
|
||||
if (sink == null)
|
||||
throw new COMException("TextStore_E_NOINTERFACE");
|
||||
|
||||
// It's legal to replace existing sink.
|
||||
if (_sink != null)
|
||||
_sink.Dispose();
|
||||
|
||||
(obj as ComObject).Dispose();
|
||||
|
||||
_sink = sink;
|
||||
}
|
||||
|
||||
public void UnadviseSink(IUnknown obj)
|
||||
{
|
||||
var sink = (obj as ComObject).QueryInterface<ITextStoreACPSink>();
|
||||
if (sink.NativePointer != _sink.NativePointer)
|
||||
throw new COMException("TextStore_CONNECT_E_NOCONNECTION");
|
||||
|
||||
_sink.Release();
|
||||
_sink = null;
|
||||
}
|
||||
|
||||
private bool _LockDocument(TsfSharp.TsLfFlags dwLockFlags)
|
||||
{
|
||||
if (_locked)
|
||||
return false;
|
||||
|
||||
_locked = true;
|
||||
_lockFlags = dwLockFlags;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ResetIfRequired()
|
||||
{
|
||||
if (!_commited)
|
||||
return;
|
||||
|
||||
_commited = false;
|
||||
|
||||
TsTextchange textChange;
|
||||
textChange.AcpStart = 0;
|
||||
textChange.AcpOldEnd = _inputBuffer.Count;
|
||||
textChange.AcpNewEnd = 0;
|
||||
_inputBuffer.Clear();
|
||||
|
||||
_sink.OnTextChange(0, textChange);
|
||||
|
||||
_acpStart = _acpEnd = 0;
|
||||
_sink.OnSelectionChange();
|
||||
_commitStart = _commitLength = 0;
|
||||
|
||||
//Debug.WriteLine("TextStore reset!!!");
|
||||
}
|
||||
|
||||
private void _UnlockDocument()
|
||||
{
|
||||
Result hr;
|
||||
_locked = false;
|
||||
_lockFlags = 0;
|
||||
|
||||
ResetIfRequired();
|
||||
|
||||
//if there is a queued lock, grant it
|
||||
if (_lockRequestQueue.Count > 0)
|
||||
{
|
||||
hr = RequestLock(_lockRequestQueue.Dequeue());
|
||||
}
|
||||
|
||||
//if any layout changes occurred during the lock, notify the manager
|
||||
if (_layoutChanged)
|
||||
{
|
||||
_layoutChanged = false;
|
||||
_sink.OnLayoutChange(TsLayoutCode.TsLcChange, _viewCookie);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _IsLocked(TsfSharp.TsLfFlags dwLockType)
|
||||
{
|
||||
return _locked && (_lockFlags & dwLockType) != 0;
|
||||
}
|
||||
|
||||
public Result RequestLock(TsfSharp.TsLfFlags dwLockFlags)
|
||||
{
|
||||
Result hrSession;
|
||||
|
||||
if (_sink == null)
|
||||
throw new COMException("TextStore_NoSink");
|
||||
|
||||
if (dwLockFlags == 0)
|
||||
throw new COMException("TextStore_BadLockFlags");
|
||||
|
||||
hrSession = Result.Fail;
|
||||
|
||||
if (_locked)
|
||||
{
|
||||
//the document is locked
|
||||
|
||||
if ((dwLockFlags & TsfSharp.TsLfFlags.Sync) == TsfSharp.TsLfFlags.Sync)
|
||||
{
|
||||
/*
|
||||
The caller wants an immediate lock, but this cannot be granted because
|
||||
the document is already locked.
|
||||
*/
|
||||
hrSession = (int)TsErrors.TsESynchronous;
|
||||
}
|
||||
else
|
||||
{
|
||||
//the request is asynchronous
|
||||
|
||||
//Queue the lock request
|
||||
_lockRequestQueue.Enqueue(dwLockFlags);
|
||||
hrSession = (int)TsErrors.TsSAsync;
|
||||
}
|
||||
|
||||
return hrSession;
|
||||
}
|
||||
|
||||
//lock the document
|
||||
_LockDocument(dwLockFlags);
|
||||
|
||||
//call OnLockGranted
|
||||
hrSession = _sink.OnLockGranted(dwLockFlags);
|
||||
|
||||
//unlock the document
|
||||
_UnlockDocument();
|
||||
|
||||
return hrSession;
|
||||
}
|
||||
|
||||
public TsStatus GetStatus()
|
||||
{
|
||||
TsStatus status = new TsStatus();
|
||||
status.DynamicFlags = 0;
|
||||
status.StaticFlags = 0;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public void QueryInsert(int acpTestStart, int acpTestEnd, uint cch, out int acpResultStart, out int acpResultEnd)
|
||||
{
|
||||
acpResultStart = acpResultEnd = 0;
|
||||
|
||||
// Fix possible crash
|
||||
if (_inputBuffer.Count == 0)
|
||||
return;
|
||||
|
||||
//Queryins
|
||||
if (acpTestStart > _inputBuffer.Count || acpTestEnd > _inputBuffer.Count)
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
|
||||
//Microsoft Pinyin seems does not init the result value, so we set the test value here, in case crash
|
||||
acpResultStart = acpTestStart;
|
||||
acpResultEnd = acpTestEnd;
|
||||
}
|
||||
|
||||
public uint GetSelection(uint index, ref TsSelectionAcp selection)
|
||||
{
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Read))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
//return NativeMethods.TS_E_NOLOCK;
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
//check the requested index
|
||||
if (-1 == (int)index)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else if (index > 1)
|
||||
{
|
||||
/*
|
||||
The index is too high. This app only supports one selection.
|
||||
*/
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
}
|
||||
|
||||
selection.AcpStart = _acpStart;
|
||||
selection.AcpEnd = _acpEnd;
|
||||
selection.Style.InterimCharFlag = _interimChar;
|
||||
|
||||
if (_interimChar)
|
||||
{
|
||||
/*
|
||||
fInterimChar will be set when an intermediate character has been
|
||||
set. One example of when this will happen is when an IME is being
|
||||
used to enter characters and a character has been set, but the IME
|
||||
is still active.
|
||||
*/
|
||||
selection.Style.Ase = TsActiveSelEnd.TsAeNone;
|
||||
}
|
||||
else
|
||||
{
|
||||
selection.Style.Ase = _activeSelectionEnd;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void SetSelection(uint count, ref TsSelectionAcp selections)
|
||||
{
|
||||
//this implementaiton only supports a single selection
|
||||
if (count != 1)
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Readwrite))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
//return NativeMethods.TS_E_NOLOCK;
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
_acpStart = selections.AcpStart;
|
||||
_acpEnd = selections.AcpEnd;
|
||||
_interimChar = selections.Style.InterimCharFlag;
|
||||
|
||||
if (_interimChar)
|
||||
{
|
||||
/*
|
||||
fInterimChar will be set when an intermediate character has been
|
||||
set. One example of when this will happen is when an IME is being
|
||||
used to enter characters and a character has been set, but the IME
|
||||
is still active.
|
||||
*/
|
||||
_activeSelectionEnd = TsActiveSelEnd.TsAeNone;
|
||||
}
|
||||
else
|
||||
{
|
||||
_activeSelectionEnd = selections.Style.Ase;
|
||||
}
|
||||
|
||||
//if the selection end is at the start of the selection, reverse the parameters
|
||||
int lStart = _acpStart;
|
||||
int lEnd = _acpEnd;
|
||||
|
||||
if (TsActiveSelEnd.TsAeStart == _activeSelectionEnd)
|
||||
{
|
||||
lStart = _acpEnd;
|
||||
lEnd = _acpStart;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void GetText(int acpStart, int acpEnd, System.IntPtr pchPlain, uint cchPlainReq, out uint cchPlainRet,
|
||||
ref TsfSharp.TsRuninfo rgRunInfo, uint cRunInfoReq, out uint cRunInfoRet, out int acpNext)
|
||||
{
|
||||
cchPlainRet = 0;
|
||||
cRunInfoRet = 0;
|
||||
acpNext = 0;
|
||||
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Read))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
bool fDoText = cchPlainReq > 0;
|
||||
bool fDoRunInfo = cRunInfoReq > 0;
|
||||
int cchTotal;
|
||||
|
||||
cchPlainRet = 0;
|
||||
acpNext = acpStart;
|
||||
|
||||
cchTotal = _inputBuffer.Count;
|
||||
|
||||
//validate the start pos
|
||||
if ((acpStart < 0) || (acpStart > cchTotal))
|
||||
{
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
}
|
||||
else
|
||||
{
|
||||
//are we at the end of the document
|
||||
if (acpStart == cchTotal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
int cchReq;
|
||||
|
||||
/*
|
||||
acpEnd will be -1 if all of the text up to the end is being requested.
|
||||
*/
|
||||
|
||||
if (acpEnd >= acpStart)
|
||||
{
|
||||
cchReq = acpEnd - acpStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
cchReq = cchTotal - acpStart;
|
||||
}
|
||||
|
||||
if (fDoText)
|
||||
{
|
||||
if (cchReq > cchPlainReq)
|
||||
{
|
||||
cchReq = (int)cchPlainReq;
|
||||
}
|
||||
|
||||
//extract the specified text range
|
||||
if (pchPlain != IntPtr.Zero && cchPlainReq > 0)
|
||||
{
|
||||
//_inputBuffer.CopyTo(acpStart, pchPlain, 0, cchReq);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var ptr = (char*)pchPlain;
|
||||
|
||||
for (int i = acpStart; i < cchReq; i++)
|
||||
{
|
||||
*ptr = _inputBuffer[i];
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//it is possible that only the length of the text is being requested
|
||||
cchPlainRet = (uint)cchReq;
|
||||
|
||||
if (fDoRunInfo)
|
||||
{
|
||||
/*
|
||||
Runs are used to separate text characters from formatting characters.
|
||||
|
||||
In this example, sequences inside and including the <> are treated as
|
||||
control sequences and are not displayed.
|
||||
|
||||
Plain text = "Text formatting."
|
||||
Actual text = "Text <B><I>formatting</I></B>."
|
||||
|
||||
If all of this text were requested, the run sequence would look like this:
|
||||
|
||||
prgRunInfo[0].type = TS_RT_PLAIN; //"Text "
|
||||
prgRunInfo[0].uCount = 5;
|
||||
|
||||
prgRunInfo[1].type = TS_RT_HIDDEN; //<B><I>
|
||||
prgRunInfo[1].uCount = 6;
|
||||
|
||||
prgRunInfo[2].type = TS_RT_PLAIN; //"formatting"
|
||||
prgRunInfo[2].uCount = 10;
|
||||
|
||||
prgRunInfo[3].type = TS_RT_HIDDEN; //</B></I>
|
||||
prgRunInfo[3].uCount = 8;
|
||||
|
||||
prgRunInfo[4].type = TS_RT_PLAIN; //"."
|
||||
prgRunInfo[4].uCount = 1;
|
||||
|
||||
TS_RT_OPAQUE is used to indicate characters or character sequences
|
||||
that are in the document, but are used privately by the application
|
||||
and do not map to text. Runs of text tagged with TS_RT_OPAQUE should
|
||||
NOT be included in the pchPlain or cchPlainOut [out] parameters.
|
||||
*/
|
||||
|
||||
/*
|
||||
This implementation is plain text, so the text only consists of one run.
|
||||
If there were multiple runs, it would be an error to have consecuative runs
|
||||
of the same type.
|
||||
*/
|
||||
rgRunInfo.Type = TsRunType.TsRtPlain;
|
||||
rgRunInfo.Count = (uint)cchReq;
|
||||
}
|
||||
|
||||
acpNext = acpStart + cchReq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TsTextchange SetText(int dwFlags, int acpStart, int acpEnd, string pchText, uint cch)
|
||||
{
|
||||
/*
|
||||
dwFlags can be:
|
||||
TS_ST_CORRECTION
|
||||
*/
|
||||
TsTextchange change = new TsTextchange();
|
||||
|
||||
//set the selection to the specified range
|
||||
TsSelectionAcp tsa = new TsSelectionAcp();
|
||||
tsa.AcpStart = acpStart;
|
||||
tsa.AcpEnd = acpEnd;
|
||||
tsa.Style.Ase = TsActiveSelEnd.TsAeStart;
|
||||
tsa.Style.InterimCharFlag = false;
|
||||
|
||||
SetSelection(1, ref tsa);
|
||||
|
||||
int start, end;
|
||||
InsertTextAtSelection(TsIasFlags.Noquery, pchText, cch, out start, out end, out change);
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
public IDataObject GetFormattedText(int startIndex, int endIndex)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public IUnknown GetEmbedded(int index, Guid guidService, Guid riid)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public RawBool QueryInsertEmbedded(Guid guidService, ref Formatetc formatEtc)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public TsTextchange InsertEmbedded(int flags, int startIndex, int endIndex, TsfSharp.IDataObject dataObjectRef)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public void InsertTextAtSelection(TsfSharp.TsIasFlags dwFlags, string pchText, uint cch, out int pacpStart, out int pacpEnd, out TsfSharp.TsTextchange pChange)
|
||||
{
|
||||
pacpStart = pacpEnd = 0;
|
||||
pChange = new TsTextchange();
|
||||
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Readwrite))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
int acpStart;
|
||||
int acpOldEnd;
|
||||
int acpNewEnd;
|
||||
|
||||
acpOldEnd = _acpEnd;
|
||||
|
||||
//set the start point after the insertion
|
||||
acpStart = _acpStart;
|
||||
|
||||
//set the end point after the insertion
|
||||
acpNewEnd = _acpStart + (int)cch;
|
||||
|
||||
if ((dwFlags & TsIasFlags.Queryonly) == TsIasFlags.Queryonly)
|
||||
{
|
||||
pacpStart = acpStart;
|
||||
pacpEnd = acpOldEnd;
|
||||
return;
|
||||
}
|
||||
|
||||
//insert the text
|
||||
_inputBuffer.RemoveRange(acpStart, acpOldEnd - acpStart);
|
||||
_inputBuffer.InsertRange(acpStart, pchText);
|
||||
|
||||
//set the selection
|
||||
_acpStart = acpStart;
|
||||
_acpEnd = acpNewEnd;
|
||||
|
||||
if ((dwFlags & TsIasFlags.Noquery) != TsIasFlags.Noquery)
|
||||
{
|
||||
pacpStart = acpStart;
|
||||
pacpEnd = acpNewEnd;
|
||||
}
|
||||
|
||||
//set the TS_TEXTCHANGE members
|
||||
pChange.AcpStart = acpStart;
|
||||
pChange.AcpOldEnd = acpOldEnd;
|
||||
pChange.AcpNewEnd = acpNewEnd;
|
||||
|
||||
//defer the layout change notification until the document is unlocked
|
||||
_layoutChanged = true;
|
||||
}
|
||||
|
||||
public void InsertEmbeddedAtSelection(int flags, IDataObject obj, out int startIndex, out int endIndex, out TsTextchange change)
|
||||
{
|
||||
startIndex = endIndex = 0;
|
||||
change = new TsTextchange();
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public void RequestSupportedAttrs(int flags, uint cFilterAttrs, ref Guid filterAttributes)
|
||||
{
|
||||
}
|
||||
|
||||
public void RequestAttrsAtPosition(int index, uint cFilterAttrs, ref Guid filterAttributes, int flags)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
|
||||
public void RequestAttrsTransitioningAtPosition(int position, uint cFilterAttrs, ref Guid filterAttributes, int flags)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public void FindNextAttrTransition(int startIndex, int haltIndex, uint cFilterAttrs, ref Guid filterAttributes, int flags, out int acpNext, out RawBool found, out int foundOffset)
|
||||
{
|
||||
acpNext = 0;
|
||||
found = false;
|
||||
foundOffset = 0;
|
||||
}
|
||||
|
||||
public uint RetrieveRequestedAttrs(uint ulCount, ref TsfSharp.TsAttrval aAttrValsRef)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int GetEndACP()
|
||||
{
|
||||
int acp = 0;
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Read))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
acp = _inputBuffer.Count;
|
||||
|
||||
return acp;
|
||||
}
|
||||
|
||||
public int GetActiveView()
|
||||
{
|
||||
return _viewCookie;
|
||||
}
|
||||
|
||||
public int GetACPFromPoint(int viewCookie, TsfSharp.Point tsfPoint, int dwFlags)
|
||||
{
|
||||
throw new COMException("", Result.NotImplemented.Code);
|
||||
}
|
||||
|
||||
public void GetTextExt(int viewCookie, int acpStart, int acpEnd, out Rect rect, out RawBool clipped)
|
||||
{
|
||||
clipped = false;
|
||||
rect = InputMethod.TextInputRect;
|
||||
|
||||
if (_viewCookie != viewCookie)
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
|
||||
//does the caller have a lock
|
||||
if (!_IsLocked(TsLfFlags.Read))
|
||||
{
|
||||
//the caller doesn't have a lock
|
||||
throw new COMException("", (int)TsErrors.TsENolock);
|
||||
}
|
||||
|
||||
//According to Microsoft's doc, an ime should not make empty request,
|
||||
//but some ime draw comp text themseleves, when empty req will be make
|
||||
//Check empty request
|
||||
//if (acpStart == acpEnd) {
|
||||
// return E_INVALIDARG;
|
||||
//}
|
||||
|
||||
NativeMethods.MapWindowPoints(_windowHandle, IntPtr.Zero, ref rect, 2);
|
||||
}
|
||||
|
||||
public Rect GetScreenExt(int viewCookie)
|
||||
{
|
||||
Rect rect = new Rect();
|
||||
|
||||
if (_viewCookie != viewCookie)
|
||||
throw new COMException("", Result.InvalidArg.Code);
|
||||
|
||||
NativeMethods.GetWindowRect(_windowHandle, out rect);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public IntPtr GetWnd(int viewCookie)
|
||||
{
|
||||
if (viewCookie != _viewCookie)
|
||||
{
|
||||
throw new COMException("", Result.False.Code);
|
||||
}
|
||||
|
||||
return _windowHandle;
|
||||
}
|
||||
|
||||
#endregion ITextStoreACP2
|
||||
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Public Methods - ITfContextOwnerCompositionSink
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region ITfContextOwnerCompositionSink
|
||||
|
||||
public RawBool OnStartComposition(ITfCompositionView view)
|
||||
{
|
||||
// Return true in ok to start the composition.
|
||||
RawBool ok = true;
|
||||
_compositionStart = _compositionLength = 0;
|
||||
_currentComposition.Clear();
|
||||
|
||||
InputMethod.OnTextCompositionStarted(this);
|
||||
_compViews.Add(view);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
public void OnUpdateComposition(ITfCompositionView view, ITfRange rangeNew)
|
||||
{
|
||||
var range = view.Range;
|
||||
var rangeacp = range.QueryInterface<ITfRangeACP>();
|
||||
|
||||
rangeacp.GetExtent(out _compositionStart, out _compositionLength);
|
||||
rangeacp.Dispose();
|
||||
range.Dispose();
|
||||
_compViews.Add(view);
|
||||
}
|
||||
|
||||
public void OnEndComposition(ITfCompositionView view)
|
||||
{
|
||||
var range = view.Range;
|
||||
var rangeacp = range.QueryInterface<ITfRangeACP>();
|
||||
|
||||
rangeacp.GetExtent(out _commitStart, out _commitLength);
|
||||
rangeacp.Dispose();
|
||||
range.Dispose();
|
||||
|
||||
// Ensure composition string reset
|
||||
_compositionStart = _compositionLength = 0;
|
||||
_currentComposition.Clear();
|
||||
|
||||
InputMethod.ClearCandidates();
|
||||
InputMethod.OnTextCompositionEnded(this);
|
||||
view.Dispose();
|
||||
foreach(var item in _compViews)
|
||||
item.Dispose();
|
||||
_compViews.Clear();
|
||||
}
|
||||
|
||||
#endregion ITfContextOwnerCompositionSink
|
||||
|
||||
#region ITfTextEditSink
|
||||
|
||||
public void OnEndEdit(ITfContext context, int ecReadOnly, ITfEditRecord editRecord)
|
||||
{
|
||||
ITfProperty property = context.GetProperty(GUID_PROP_COMPOSING);
|
||||
|
||||
ITfRangeACP rangeACP = TextServicesContext.Current.ContextOwnerServices.CreateRange(_compositionStart, _compositionStart + _compositionLength);
|
||||
Variant val = property.GetValue(ecReadOnly, rangeACP);
|
||||
property.Dispose();
|
||||
rangeACP.Dispose();
|
||||
if (val.Value == null || (int)val.Value == 0)
|
||||
{
|
||||
if (_commitLength == 0 || _inputBuffer.Count == 0)
|
||||
return;
|
||||
|
||||
//Debug.WriteLine("Composition result: {0}", new object[] { new string(_inputBuffer.GetRange(_commitStart, _commitLength).ToArray()) });
|
||||
|
||||
_commited = true;
|
||||
for (int i = 0; i < _commitLength; i++)
|
||||
InputMethod.OnTextCompositionResult(this, new string(_inputBuffer.GetRange(_commitStart, _commitLength).ToArray()));
|
||||
}
|
||||
|
||||
if (_commited)
|
||||
return;
|
||||
|
||||
if (_inputBuffer.Count == 0 && _compositionLength > 0) // Composition just ended
|
||||
return;
|
||||
|
||||
_currentComposition.Clear();
|
||||
for (int i = 0; i < _compositionLength; i++)
|
||||
_currentComposition.Add(_inputBuffer[_compositionStart + i]);
|
||||
|
||||
InputMethod.OnTextComposition(this, new IMEString(_currentComposition), _acpEnd);
|
||||
|
||||
//var compStr = new string(_currentComposition.ToArray());
|
||||
//compStr = compStr.Insert(_acpEnd, "|");
|
||||
//Debug.WriteLine("Composition string: {0}, cursor pos: {1}", compStr, _acpEnd);
|
||||
}
|
||||
|
||||
#endregion ITfTextEditSink
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Public Methods - ITfUIElementSink
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
#region ITfUIElementSink
|
||||
|
||||
public RawBool BeginUIElement(int dwUIElementId)
|
||||
{
|
||||
// Hide OS rendered Candidate list Window
|
||||
RawBool pbShow = InputMethod.ShowOSImeWindow;
|
||||
|
||||
OnUIElement(dwUIElementId, true);
|
||||
|
||||
return pbShow;
|
||||
}
|
||||
|
||||
public void UpdateUIElement(int dwUIElementId)
|
||||
{
|
||||
OnUIElement(dwUIElementId, false);
|
||||
}
|
||||
|
||||
public void EndUIElement(int dwUIElementId)
|
||||
{
|
||||
}
|
||||
|
||||
private void OnUIElement(int uiElementId, bool onStart)
|
||||
{
|
||||
if (InputMethod.ShowOSImeWindow || !_supportUIElement) return;
|
||||
|
||||
ITfUIElement uiElement = TextServicesContext.Current.UIElementMgr.GetUIElement(uiElementId);
|
||||
|
||||
ITfCandidateListUIElementBehavior candList;
|
||||
|
||||
try
|
||||
{
|
||||
candList = uiElement.QueryInterface<ITfCandidateListUIElementBehavior>();
|
||||
}
|
||||
catch (SharpGenException)
|
||||
{
|
||||
_supportUIElement = false;
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
uiElement.Dispose();
|
||||
}
|
||||
|
||||
uint selection = 0;
|
||||
uint currentPage = 0;
|
||||
uint count = 0;
|
||||
uint pageCount = 0;
|
||||
uint pageStart = 0;
|
||||
uint pageSize = 0;
|
||||
uint i, j;
|
||||
|
||||
selection = candList.GetSelection();
|
||||
currentPage = candList.GetCurrentPage();
|
||||
|
||||
count = candList.GetCount();
|
||||
|
||||
pageCount = candList.GetPageIndex(null, 0);
|
||||
|
||||
if (pageCount > 0)
|
||||
{
|
||||
uint[] pageStartIndexes = ArrayPool<uint>.Shared.Rent((int)pageCount);
|
||||
pageCount = candList.GetPageIndex(pageStartIndexes, pageCount);
|
||||
pageStart = pageStartIndexes[currentPage];
|
||||
|
||||
if (pageStart >= count - 1)
|
||||
{
|
||||
candList.Abort();
|
||||
ArrayPool<uint>.Shared.Return(pageStartIndexes);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPage < pageCount - 1)
|
||||
pageSize = Math.Min(count, pageStartIndexes[currentPage + 1]) - pageStart;
|
||||
else
|
||||
pageSize = count - pageStart;
|
||||
|
||||
ArrayPool<uint>.Shared.Return(pageStartIndexes);
|
||||
}
|
||||
|
||||
selection -= pageStart;
|
||||
|
||||
IMEString[] candidates = _IMEStringPool.Rent((int)pageSize);
|
||||
|
||||
IntPtr bStrPtr;
|
||||
for (i = pageStart, j = 0; i < count && j < pageSize; i++, j++)
|
||||
{
|
||||
bStrPtr = candList.GetString(i);
|
||||
candidates[j] = new IMEString(bStrPtr);
|
||||
}
|
||||
|
||||
//Debug.WriteLine("TSF========TSF");
|
||||
//Debug.WriteLine("pageStart: {0}, pageSize: {1}, selection: {2}, currentPage: {3} candidates:", pageStart, pageSize, selection, currentPage);
|
||||
//for (int k = 0; k < candidates.Length; k++)
|
||||
// Debug.WriteLine(" {2}{0}.{1}", k + 1, candidates[k], k == selection ? "*" : "");
|
||||
//Debug.WriteLine("TSF++++++++TSF");
|
||||
|
||||
InputMethod.CandidatePageStart = (int)pageStart;
|
||||
InputMethod.CandidatePageSize = (int)pageSize;
|
||||
InputMethod.CandidateSelection = (int)selection;
|
||||
InputMethod.CandidateList = candidates;
|
||||
|
||||
if (_currentComposition != null)
|
||||
{
|
||||
InputMethod.OnTextComposition(this, new IMEString(_currentComposition), _acpEnd);
|
||||
_IMEStringPool.Return(candidates);
|
||||
}
|
||||
|
||||
candList.Dispose();
|
||||
}
|
||||
|
||||
#endregion ITfUIElementSink
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Public Properties
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
public static TextStore Current
|
||||
{
|
||||
get
|
||||
{
|
||||
TextStore defaultTextStore = InputMethod.DefaultTextStore;
|
||||
if (defaultTextStore == null)
|
||||
{
|
||||
defaultTextStore = InputMethod.DefaultTextStore = new TextStore(InputMethod.WindowHandle);
|
||||
|
||||
defaultTextStore.Register();
|
||||
}
|
||||
|
||||
return defaultTextStore;
|
||||
}
|
||||
}
|
||||
|
||||
public ITfDocumentMgr DocumentManager
|
||||
{
|
||||
get { return _documentMgr; }
|
||||
set { _documentMgr = value; }
|
||||
}
|
||||
|
||||
// EditCookie for ITfContext.
|
||||
public int EditCookie
|
||||
{
|
||||
// get { return _editCookie; }
|
||||
set { _editCookie = value; }
|
||||
}
|
||||
|
||||
public int UIElementSinkCookie
|
||||
{
|
||||
get { return _uiElementSinkCookie; }
|
||||
set { _uiElementSinkCookie = value; }
|
||||
}
|
||||
|
||||
public int TextEditSinkCookie
|
||||
{
|
||||
get { return _textEditSinkCookie; }
|
||||
set { _textEditSinkCookie = value; }
|
||||
}
|
||||
|
||||
public bool SupportUIElement { get { return _supportUIElement; } }
|
||||
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
// This function calls TextServicesContext to create TSF document and start transitory extension.
|
||||
private void Register()
|
||||
{
|
||||
// Create TSF document and advise the sink to it.
|
||||
TextServicesContext.Current.RegisterTextStore(this);
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
//
|
||||
// Private Fields
|
||||
//
|
||||
//------------------------------------------------------
|
||||
|
||||
// The TSF document object. This is a native resource.
|
||||
private ITfDocumentMgr _documentMgr;
|
||||
|
||||
private int _viewCookie;
|
||||
|
||||
// The edit cookie TSF returns from CreateContext.
|
||||
private int _editCookie;
|
||||
private int _uiElementSinkCookie;
|
||||
private int _textEditSinkCookie;
|
||||
|
||||
private ITextStoreACPSink _sink;
|
||||
private IntPtr _windowHandle;
|
||||
private int _acpStart;
|
||||
private int _acpEnd;
|
||||
private bool _interimChar;
|
||||
private TsActiveSelEnd _activeSelectionEnd;
|
||||
private List<char> _inputBuffer = new List<char>();
|
||||
|
||||
private bool _locked;
|
||||
private TsLfFlags _lockFlags;
|
||||
private Queue<TsLfFlags> _lockRequestQueue = new Queue<TsLfFlags>();
|
||||
private bool _layoutChanged;
|
||||
|
||||
private List<char> _currentComposition = new List<char>();
|
||||
private int _compositionStart;
|
||||
private int _compositionLength;
|
||||
private int _commitStart;
|
||||
private int _commitLength;
|
||||
private bool _commited;
|
||||
|
||||
private bool _supportUIElement = true;
|
||||
private List<ITfCompositionView> _compViews = new List<ITfCompositionView>();
|
||||
|
||||
private ArrayPool<IMEString> _IMEStringPool;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,11 @@ namespace Microsoft.Xna.Framework {
|
||||
/// Used for displaying uncommitted IME text.
|
||||
/// </summary>
|
||||
public event EventHandler<TextEditingEventArgs> TextEditing;
|
||||
|
||||
/// <summary>
|
||||
/// Used for when a key is pressed, including modifiers.
|
||||
/// </summary>
|
||||
public event EventHandler<TextInputEventArgs> KeyDown;
|
||||
#endif
|
||||
|
||||
#endregion Events
|
||||
@@ -158,6 +163,11 @@ namespace Microsoft.Xna.Framework {
|
||||
EventHelpers.Raise(this, TextInput, e);
|
||||
}
|
||||
|
||||
protected void OnKeyDown(object sender, TextInputEventArgs e)
|
||||
{
|
||||
EventHelpers.Raise(this, KeyDown, e);
|
||||
}
|
||||
|
||||
protected void OnTextEditing(object sender, TextEditingEventArgs e)
|
||||
{
|
||||
EventHelpers.Raise(this, TextEditing, e);
|
||||
|
||||
@@ -75,6 +75,8 @@ internal static class Sdl
|
||||
TextEditing = 0x302,
|
||||
TextInput = 0x303,
|
||||
|
||||
TextEditingExt = 0x305,
|
||||
|
||||
MouseMotion = 0x400,
|
||||
MouseButtonDown = 0x401,
|
||||
MouseButtonup = 0x402,
|
||||
@@ -139,6 +141,8 @@ internal static class Sdl
|
||||
[FieldOffset(0)]
|
||||
public Keyboard.TextEditingEvent Edit;
|
||||
[FieldOffset(0)]
|
||||
public Keyboard.TextEditingExtEvent EditExt;
|
||||
[FieldOffset(0)]
|
||||
public Keyboard.TextInputEvent Text;
|
||||
[FieldOffset(0)]
|
||||
public Mouse.WheelEvent Wheel;
|
||||
@@ -241,8 +245,8 @@ internal static class Sdl
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate bool d_sdl_istextinputactive();
|
||||
public static d_sdl_istextinputactive SDL_IsTextInputActive = FuncLoader.LoadFunction<d_sdl_istextinputactive>(NativeLibrary, "SDL_IsTextInputActive");
|
||||
public delegate bool d_sdl_istextinputshown();
|
||||
public static d_sdl_istextinputshown SDL_IsTextInputShown = FuncLoader.LoadFunction<d_sdl_istextinputshown>(NativeLibrary, "SDL_IsTextInputShown");
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void d_sdl_starttextinput();
|
||||
@@ -841,6 +845,17 @@ internal static class Sdl
|
||||
public int Length;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct TextEditingExtEvent
|
||||
{
|
||||
public EventType Type;
|
||||
public uint Timestamp;
|
||||
public uint WindowId;
|
||||
public byte* Text;
|
||||
public int Start;
|
||||
public int Length;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct TextInputEvent
|
||||
{
|
||||
|
||||
@@ -147,14 +147,7 @@ namespace Microsoft.Xna.Framework
|
||||
character = '\0';
|
||||
}
|
||||
|
||||
if (char.IsControl(character) ||
|
||||
key == Keys.Left ||
|
||||
key == Keys.Right ||
|
||||
key == Keys.Up ||
|
||||
key == Keys.Down)
|
||||
{
|
||||
_view.CallTextInput(character, key);
|
||||
}
|
||||
_view.CallKeyDown(character, key);
|
||||
}
|
||||
else if (ev.Type == Sdl.EventType.KeyUp)
|
||||
{
|
||||
@@ -164,10 +157,24 @@ namespace Microsoft.Xna.Framework
|
||||
else if (ev.Type == Sdl.EventType.TextEditing)
|
||||
{
|
||||
string text;
|
||||
unsafe { text = ReadString(ev.Text.Text); }
|
||||
unsafe
|
||||
{
|
||||
text = ReadString(ev.Edit.Text);
|
||||
}
|
||||
|
||||
_view.CallTextEditing(text, ev.Edit.Start, ev.Edit.Length);
|
||||
}
|
||||
else if (ev.Type == Sdl.EventType.TextEditingExt)
|
||||
{
|
||||
string text;
|
||||
unsafe
|
||||
{
|
||||
text = ReadString(ev.EditExt.Text);
|
||||
Sdl.Free((IntPtr)ev.EditExt.Text);
|
||||
}
|
||||
|
||||
_view.CallTextEditing(text, ev.EditExt.Start, ev.EditExt.Length);
|
||||
}
|
||||
else if (ev.Type == Sdl.EventType.TextInput)
|
||||
{
|
||||
string text;
|
||||
|
||||
@@ -117,7 +117,8 @@ namespace Microsoft.Xna.Framework
|
||||
* By default SDL2 will hide IME popups since it probably assumes the game will implement their own suggestions box.
|
||||
* We don't want that, so this hint will allow the system native IME popups to show up when typing in the game.
|
||||
*/
|
||||
Sdl.SetHint("SDL_HINT_IME_SHOW_UI", "1");
|
||||
Sdl.SetHint("SDL_IME_SHOW_UI", "1");
|
||||
Sdl.SetHint("SDL_IME_SUPPORT_EXTENDED_TEXT", "1");
|
||||
|
||||
// when running NUnit tests entry assembly can be null
|
||||
if (Assembly.GetEntryAssembly() != null)
|
||||
@@ -339,6 +340,11 @@ namespace Microsoft.Xna.Framework
|
||||
OnTextInput(this, new TextInputEventArgs(c, key));
|
||||
}
|
||||
|
||||
public void CallKeyDown(char c, Keys key = Keys.None)
|
||||
{
|
||||
OnKeyDown(this, new TextInputEventArgs(c, key));
|
||||
}
|
||||
|
||||
public void CallTextEditing(string text, int start, int length)
|
||||
{
|
||||
OnTextEditing(this, new TextEditingEventArgs(text, start, length));
|
||||
|
||||
@@ -4,6 +4,11 @@ namespace Microsoft.Xna.Framework
|
||||
{
|
||||
public static class TextInput
|
||||
{
|
||||
public static bool IsTextInputShown()
|
||||
{
|
||||
return Sdl.SDL_IsTextInputShown();
|
||||
}
|
||||
|
||||
public static void StartTextInput()
|
||||
{
|
||||
Sdl.SDL_StartTextInput();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -43,8 +43,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsTest", "Barotrauma\B
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeployAll", "Deploy\DeployAll\DeployAll.csproj", "{C98FE0D0-BC7D-4806-B592-734B53016FD8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImeSharp", "Libraries\ImeSharp\ImeSharp.csproj", "{D42D8E48-A687-445D-AAF1-9E7E6EBF1DE6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||
Libraries\GameAnalytics\GA-SDK-MONO-SHARED\GA-SDK-MONO-SHARED.projitems*{95c4d59d-9be4-4278-b4f8-46c0ba1a3916}*SharedItemsImports = 5
|
||||
|
||||
Reference in New Issue
Block a user