Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

#459 IME UI does not follow the cursor in Windows Terminal #1919

Merged
merged 33 commits into from
Nov 22, 2019
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b57310d
Add TSF3.0 IME UserControl
Jun 20, 2019
17b624d
Trying to figure out why Surface Pro 6 isn't working right
Jul 8, 2019
1a69d5c
Add documentation, getting ready for review
Jul 9, 2019
77c6197
Minor code formatting updates
Jul 9, 2019
466fd96
More changes to get ready for draft PR
Jul 9, 2019
078c69f
Add Exception handling around std::wstring operations
Jul 9, 2019
3a097ca
Add TSF3.0 IME UserControl
Jun 20, 2019
578578c
Trying to figure out why Surface Pro 6 isn't working right
Jul 8, 2019
882e97f
Add documentation, getting ready for review
Jul 9, 2019
28e99d0
Minor code formatting updates
Jul 9, 2019
b890187
More changes to get ready for draft PR
Jul 9, 2019
4cec73a
Add Exception handling around std::wstring operations
Jul 9, 2019
8309559
Merge branch 'dev/philnach/459-TSF30-IME_2' of https://github.com/phi…
Jul 10, 2019
f309dfe
Removing Outputdebugstrings
Jul 10, 2019
2afe5f3
Apply Formatting fixes that caused code formatting failures
Jul 11, 2019
ebf3d8c
The FontWidth and Height properties weren't used on the TSFInputControl
Jul 11, 2019
41a373b
applying const to where appropriate
philnach Jul 25, 2019
c3c42e9
Merging with upstream
Oct 23, 2019
35c456a
Fix incorrect merge
Oct 23, 2019
f9e3ab9
More const changes
Nov 19, 2019
714d00a
Merge branch 'master' into dev/philnach/459-TSF30-IME_2
Nov 19, 2019
39aea27
Code Analysis Errors
Nov 19, 2019
cec486a
Remove TAB OFFSET Hack
Nov 20, 2019
e781a85
Add GitHub issues for all TODOs in TSFInputControl
Nov 20, 2019
a1b5c3a
Add more TODO github linked issues
Nov 20, 2019
8354fc1
Fix Code Formatting errors
Nov 20, 2019
30bfc3e
Update src/cascadia/TerminalControl/TSFInputControl.cpp
philnach Nov 21, 2019
04d1a63
Move Color conversion to WinRT Utils library
Nov 21, 2019
facb878
Add missing file header
Nov 21, 2019
d2a0ae8
Address PR feedback, switched to TYPED_EVENT, newer Exception Macro, …
Nov 21, 2019
4bafb32
Switch to GETSET_PROPERTY macro
Nov 21, 2019
9108b41
Adjust fontsize as supposed to fixed size
Nov 21, 2019
12c1ac6
LogicalDPI was not the right call it's always 96
Nov 21, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
350 changes: 350 additions & 0 deletions src/cascadia/TerminalControl/TSFInputControl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,350 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "pch.h"
philnach marked this conversation as resolved.
Show resolved Hide resolved
#include "TSFInputControl.h"
#include "TSFInputControl.g.cpp"

#include <Utils.h>

using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Graphics::Display;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::Text;
using namespace winrt::Windows::UI::Text::Core;
using namespace winrt::Windows::UI::Xaml;

namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
TSFInputControl::TSFInputControl() :
_editContext{ nullptr }
{
_Create();
}

// Method Description:
// - Creates XAML controls for displaying user input and hooks up CoreTextEditContext handlers
// for handling text input from the Text Services Framework.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TSFInputControl::_Create()
{
// TextBlock for user input form TSF
_textBlock = Controls::TextBlock();
_textBlock.Visibility(Visibility::Collapsed);
_textBlock.IsTextSelectionEnabled(false);
_textBlock.TextDecorations(TextDecorations::Underline);

// Canvas for controlling exact position of the TextBlock
_canvas = Windows::UI::Xaml::Controls::Canvas();
_canvas.Visibility(Visibility::Collapsed);

// add the Textblock to the Canvas
_canvas.Children().Append(_textBlock);

// set the content of this control to be the Canvas
this->Content(_canvas);

// Create a CoreTextEditingContext for since we are acting like a custom edit control
auto manager = Core::CoreTextServicesManager::GetForCurrentView();
_editContext = manager.CreateEditContext();

// sets the Input Pane display policy to Manual for now so that it can manually show the
// software keyboard when the control gains focus and dismiss it when the control loses focus.
// TODO GitHub #3639: Should Input Pane display policy be Automatic
_editContext.InputPaneDisplayPolicy(Core::CoreTextInputPaneDisplayPolicy::Manual);

// set the input scope to Text because this control is for any text.
_editContext.InputScope(Core::CoreTextInputScope::Text);

_editContext.TextRequested({ this, &TSFInputControl::_textRequestedHandler });

_editContext.SelectionRequested({ this, &TSFInputControl::_selectionRequestedHandler });

_editContext.FocusRemoved({ this, &TSFInputControl::_focusRemovedHandler });

_editContext.TextUpdating({ this, &TSFInputControl::_textUpdatingHandler });

_editContext.SelectionUpdating({ this, &TSFInputControl::_selectionUpdatingHandler });

_editContext.FormatUpdating({ this, &TSFInputControl::_formatUpdatingHandler });

_editContext.LayoutRequested({ this, &TSFInputControl::_layoutRequestedHandler });

_editContext.CompositionStarted({ this, &TSFInputControl::_compositionStartedHandler });

_editContext.CompositionCompleted({ this, &TSFInputControl::_compositionCompletedHandler });
}

// Method Description:
// - NotifyFocusEnter handler for notifying CoreEditTextContext of focus enter
// when TerminalControl receives focus.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TSFInputControl::NotifyFocusEnter()
{
if (_editContext != nullptr)
{
_editContext.NotifyFocusEnter();
}
}

// Method Description:
// - NotifyFocusEnter handler for notifying CoreEditTextContext of focus leaving.
// when TerminalControl no longer has focus.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TSFInputControl::NotifyFocusLeave()
{
if (_editContext != nullptr)
{
// _editContext.NotifyFocusLeave(); TODO GitHub #3645: Enabling causes IME to no longer show up, need to determine if required
}
}

// Method Description:
// - Handler for LayoutRequested event by CoreEditContext responsible
// for returning the current position the IME should be placed
// in screen coordinates on the screen. TSFInputControls internal
// XAML controls (TextBlock/Canvas) are also positioned and updated.
// NOTE: documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request.
// - args: CoreTextLayoutRequestedEventArgs to be updated with position information.
// Return Value:
// - <none>
void TSFInputControl::_layoutRequestedHandler(CoreTextEditContext sender, CoreTextLayoutRequestedEventArgs const& args)
{
auto request = args.Request();

// Get window in screen coordinates, this is the entire window including tabs
const auto windowBounds = CoreWindow::GetForCurrentThread().Bounds();

// Get the cursor position in text buffer position
auto cursorArgs = winrt::make_self<CursorPositionEventArgs>();
_CurrentCursorPositionHandlers(*this, *cursorArgs);
const COORD cursorPos = { gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().X), gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().Y) };

// Get Font Info as we use this is the pixel size for characters in the display
auto fontArgs = winrt::make_self<FontInfoEventArgs>();
_CurrentFontInfoHandlers(*this, *fontArgs);

const float fontWidth = fontArgs->FontSize().Width;
const float fontHeight = fontArgs->FontSize().Height;

// Convert text buffer cursor position to client coordinate position within the window
COORD clientCursorPos;
COORD screenCursorPos;
THROW_IF_FAILED(ShortMult(cursorPos.X, gsl::narrow<SHORT>(fontWidth), &clientCursorPos.X));
THROW_IF_FAILED(ShortMult(cursorPos.Y, gsl::narrow<SHORT>(fontHeight), &clientCursorPos.Y));

// Convert from client coordinate to screen coordinate by adding window position
THROW_IF_FAILED(ShortAdd(clientCursorPos.X, gsl::narrow_cast<SHORT>(windowBounds.X), &screenCursorPos.X));
THROW_IF_FAILED(ShortAdd(clientCursorPos.Y, gsl::narrow_cast<SHORT>(windowBounds.Y), &screenCursorPos.Y));

// get any offset (margin + tabs, etc..) of the control within the window
const auto offsetPoint = this->TransformToVisual(nullptr).TransformPoint(winrt::Windows::Foundation::Point(0, 0));
DHowett-MSFT marked this conversation as resolved.
Show resolved Hide resolved

// add the margin offsets if any
const auto currentMargin = this->Margin();
THROW_IF_FAILED(ShortAdd(screenCursorPos.X, gsl::narrow_cast<SHORT>(offsetPoint.X), &screenCursorPos.X));
THROW_IF_FAILED(ShortAdd(screenCursorPos.Y, gsl::narrow_cast<SHORT>(offsetPoint.Y), &screenCursorPos.Y));

// Get scale factor for view
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
philnach marked this conversation as resolved.
Show resolved Hide resolved

// Set the selection layout bounds
Rect selectionRect = Rect(screenCursorPos.X, screenCursorPos.Y, 0, fontHeight);
request.LayoutBounds().TextBounds(ScaleRect(selectionRect, scaleFactor));

// Set the control bounds of the whole control
Rect controlRect = Rect(screenCursorPos.X, screenCursorPos.Y, 0, fontHeight);
request.LayoutBounds().ControlBounds(ScaleRect(controlRect, scaleFactor));

// position textblock to cursor position
_canvas.SetLeft(_textBlock, clientCursorPos.X);
_canvas.SetTop(_textBlock, static_cast<double>(clientCursorPos.Y));

// width is cursor to end of canvas
_textBlock.Width(200); // TODO GitHub #3640: Determine proper Width
_textBlock.Height(fontHeight);

// calculate FontSize in pixels from DIPs
const float logicalDpi = DisplayInformation::GetForCurrentView().LogicalDpi();
philnach marked this conversation as resolved.
Show resolved Hide resolved
const double fontSizePx = (fontHeight * 72) / logicalDpi;
_textBlock.FontSize(fontSizePx);

_textBlock.FontFamily(Media::FontFamily(fontArgs->FontFace()));
}

// Method Description:
// - Handler for CompositionStarted event by CoreEditContext responsible
// for making internal TSFInputControl controls visisble.
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextCompositionStartedEventArgs. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_compositionStartedHandler(CoreTextEditContext sender, CoreTextCompositionStartedEventArgs const& /*args*/)
{
_canvas.Visibility(Visibility::Visible);
_textBlock.Visibility(Visibility::Visible);
}

// Method Description:
// - Handler for CompositionCompleted event by CoreEditContext responsible
// for making internal TSFInputControl controls visisble.
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextCompositionCompletedEventArgs. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_compositionCompletedHandler(CoreTextEditContext sender, CoreTextCompositionCompletedEventArgs const& /*args*/)
{
// only need to do work if the current buffer has text
if (!_inputBuffer.empty())
{
const auto hstr = to_hstring(_inputBuffer.c_str());

// call event handler with data handled by parent
_compositionCompletedHandlers(hstr);

// clear the buffer for next round
_inputBuffer.clear();
_textBlock.Text(L"");

// tell the input server that we've cleared the buffer
CoreTextRange emptyTextRange;
emptyTextRange.StartCaretPosition = 0;
emptyTextRange.EndCaretPosition = 0;

// indicate text is now 0
_editContext.NotifyTextChanged(emptyTextRange, 0, emptyTextRange);
_editContext.NotifySelectionChanged(emptyTextRange);

// hide the controls until composition starts again
_canvas.Visibility(Visibility::Collapsed);
_textBlock.Visibility(Visibility::Collapsed);
}
}

// Method Description:
// - Handler for FocusRemoved event by CoreEditContext responsible
// for removing focus for the TSFInputControl control accordingly
// when focus was forecibly removed from text input control. (TODO GitHub #3644)
// NOTE: Documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - object: CoreTextCompositionStartedEventArgs. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_focusRemovedHandler(CoreTextEditContext sender, winrt::Windows::Foundation::IInspectable const& /*object*/)
{
}

// Method Description:
// - Handler for TextRequested event by CoreEditContext responsible
// for returning the range of text requested.
// NOTE: Documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextTextRequestedEventArgs to be updated with requested range text.
// Return Value:
// - <none>
void TSFInputControl::_textRequestedHandler(CoreTextEditContext sender, CoreTextTextRequestedEventArgs const& args)
{
// the range the TSF wants to know about
const auto range = args.Request().Range();

try
{
const auto textRequested = _inputBuffer.substr(range.StartCaretPosition, static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition));

args.Request().Text(winrt::to_hstring(textRequested.c_str()));
}
CATCH_LOG();
}

// Method Description:
// - Handler for SelectionRequested event by CoreEditContext responsible
// for returning the currently selected text.
// TSFInputControl currently doesn't allow selection, so nothing happens.
// NOTE: Documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextSelectionRequestedEventArgs for providing data for the SelectionRequested event. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_selectionRequestedHandler(CoreTextEditContext sender, CoreTextSelectionRequestedEventArgs const& /*args*/)
{
}

// Method Description:
// - Handler for SelectionUpdating event by CoreEditContext responsible
// for handling modifications to the range of text currently selected.
// TSFInputControl doesn't currently allow selection, so nothing happens.
// NOTE: Documentation says application should set its selection range accordingly
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextSelectionUpdatingEventArgs for providing data for the SelectionUpdating event. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_selectionUpdatingHandler(CoreTextEditContext sender, CoreTextSelectionUpdatingEventArgs const& /*args*/)
{
}

// Method Description:
// - Handler for TextUpdating event by CoreEditContext responsible
// for handling text updates.
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextTextUpdatingEventArgs contains new text to update buffer with.
// Return Value:
// - <none>
void TSFInputControl::_textUpdatingHandler(CoreTextEditContext sender, CoreTextTextUpdatingEventArgs const& args)
{
const auto text = args.Text();
const auto range = args.Range();

try
{
_inputBuffer = _inputBuffer.replace(
range.StartCaretPosition,
static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition),
text.c_str());

_textBlock.Text(_inputBuffer);

// Notify the TSF that the update succeeded
args.Result(CoreTextTextUpdatingResult::Succeeded);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();

// indicate updating failed.
args.Result(CoreTextTextUpdatingResult::Failed);
}
}

// Method Description:
// - Handler for FormatUpdating event by CoreEditContext responsible
// for handling different format updates for a particular range of text.
// TSFInputControl doesn't do anything with this event.
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextFormatUpdatingEventArgs Provides data for the FormatUpdating event. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_formatUpdatingHandler(CoreTextEditContext sender, CoreTextFormatUpdatingEventArgs const& /*args*/)
{
}

DEFINE_EVENT(TSFInputControl, CompositionCompleted, _compositionCompletedHandlers, TerminalControl::CompositionCompletedEventArgs);
}
Loading