blob: 3dfce5543a0ffedf18a5ceb22d3a527fd2efc45e [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "win32/tip/tip_ui_element_immersive.h"
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
#include <atlbase.h>
#include <atlcom.h>
#include <atlapp.h>
#include <atlmisc.h>
#include <atlstr.h>
#include <atlwin.h>
#include <msctf.h>
#include <memory>
#include "base/hash_tables.h"
#include "base/util.h"
#include "renderer/table_layout.h"
#include "renderer/win32/text_renderer.h"
#include "renderer/win32/win32_renderer_util.h"
#include "renderer/window_util.h"
#include "session/commands.pb.h"
#include "win32/tip/tip_composition_util.h"
#include "win32/tip/tip_dll_module.h"
#include "win32/tip/tip_edit_session.h"
#include "win32/tip/tip_private_context.h"
#include "win32/tip/tip_range_util.h"
#include "win32/tip/tip_ref_count.h"
#include "win32/tip/tip_text_service.h"
#include "win32/tip/tip_ui_element_delegate.h"
#include "win32/tip/tip_ui_handler_immersive.h"
#include "win32/tip/tip_ui_renderer_immersive.h"
namespace mozc {
namespace win32 {
namespace tsf {
namespace {
using ATL::CComPtr;
using ATL::CWindow;
using WTL::CBitmap;
using WTL::CBitmapHandle;
using WTL::CDC;
using WTL::CPaintDC;
using WTL::CPoint;
using WTL::CRect;
using WTL::CSize;
using ::mozc::commands::Candidates;
using ::mozc::commands::Output;
using ::mozc::commands::Preedit;
using ::mozc::renderer::TableLayout;
using ::mozc::renderer::WindowUtil;
using ::std::unique_ptr;
typedef ::mozc::commands::Candidates::Candidate Candidate;
typedef ::mozc::commands::Preedit_Segment Segment;
typedef ::mozc::commands::Preedit_Segment::Annotation Annotation;
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
const wchar_t kImmersiveUIWindowClassName[] =
L"Google Japanese Input Immersive UI Window";
#else
const wchar_t kImmersiveUIWindowClassName[] = L"Mozc Immersive UI Window";
#endif // GOOGLE_JAPANESE_INPUT_BUILD
#ifndef EVENT_OBJECT_IME_SHOW
#define EVENT_OBJECT_IME_SHOW 0x8027
#define EVENT_OBJECT_IME_HIDE 0x8028
#define EVENT_OBJECT_IME_CHANGE 0x8029
#endif // EVENT_OBJECT_IME_SHOW
// Represents the module handle of this module.
volatile HMODULE g_module = nullptr;
// True if the the DLL received DLL_PROCESS_DETACH notification.
volatile bool g_module_unloaded = false;
// Thread Local Storage (TLS) index to specify the current UI thread is
// initialized or not. If ::GetTlsValue(g_tls_index) returns non-zero
// value, the current thread is initialized.
volatile DWORD g_tls_index = TLS_OUT_OF_INDEXES;
struct RenderingInfo {
CRect target_rect;
Output output;
bool has_target_rect;
RenderingInfo()
: has_target_rect(false) {
}
};
size_t GetTargetPos(const commands::Output &output) {
if (!output.has_candidates() || !output.candidates().has_category()) {
return 0;
}
switch (output.candidates().category()) {
case commands::PREDICTION:
case commands::SUGGESTION:
return 0;
case commands::CONVERSION: {
const Preedit &preedit = output.preedit();
size_t offset = 0;
for (int i = 0; i < preedit.segment_size(); ++i) {
const Segment &segment = preedit.segment(i);
const Annotation &annotation = segment.annotation();
if (annotation == Segment::HIGHLIGHT) {
return offset;
}
offset += Util::WideCharsLen(segment.value());
}
return offset;
}
default:
return 0;
}
}
// This function updates RendererCommand::CharacterPosition to emulate
// IMM32-based client. Ideally we'd better to define new field for TSF Mozc
// into which the result of ITfContextView::GetTextExt is stored.
bool FillRenderInfo(TipTextService *text_service,
ITfContext *context,
TfEditCookie read_cookie,
RenderingInfo *info) {
TipPrivateContext *private_context =
text_service->GetPrivateContext(context);
if (private_context == nullptr) {
return false;
}
info->output.Clear();
info->target_rect = CRect();
info->has_target_rect = false;
const Output &output = private_context->last_output();
CComPtr<ITfCompositionView> composition_view =
TipCompositionUtil::GetComposition(context, read_cookie);
if (!composition_view) {
return false;
}
CComPtr<ITfRange> composition_range;
if (FAILED(composition_view->GetRange(&composition_range))) {
return false;
}
if (!composition_range) {
return false;
}
CComPtr<ITfRange> target_range;
if (FAILED(composition_range->Clone(&target_range))) {
return false;
}
if (FAILED(target_range->Collapse(read_cookie, TF_ANCHOR_START))) {
return false;
}
{
const size_t target_pos = GetTargetPos(output);
LONG shifted = 0;
if (FAILED(target_range->ShiftStart(
read_cookie, target_pos, &shifted, nullptr))) {
return false;
}
if (FAILED(target_range->ShiftEnd(
read_cookie, target_pos + 1, &shifted, nullptr))) {
return false;
}
}
CComPtr<ITfContextView> context_view;
if (FAILED(context->GetActiveView(&context_view)) || !context_view) {
return false;
}
RECT document_rect = {};
if (FAILED(context_view->GetScreenExt(&document_rect))) {
return false;
}
RECT text_rect = {};
bool clipped = false;
const HRESULT hr = TipRangeUtil::GetTextExt(
context_view, read_cookie, target_range, &text_rect, &clipped);
if (SUCCEEDED(hr)) {
info->target_rect = text_rect;
info->has_target_rect = true;
} else if (hr == TF_E_NOLAYOUT) {
// This is not a fatal error but |text_rect| is not available yet.
} else {
return false;
}
info->output.CopyFrom(output);
return true;
}
CRect ToCRect(const Rect &rect) {
return WTL::CRect(rect.Left(), rect.Top(), rect.Right(), rect.Bottom());
}
// Returns the smallest index of the given candidate list which satisfies
// candidates.candidate(i) == |candidate_index|.
// This function returns the size of the given candidate list when there
// aren't any candidates satisfying the above condition.
int GetCandidateArrayIndexByCandidateIndex(const Candidates &candidates,
int candidate_index) {
for (size_t i = 0; i < candidates.candidate_size(); ++i) {
const Candidate &candidate = candidates.candidate(i);
if (candidate.index() == candidate_index) {
return i;
}
}
return candidates.candidate_size();
}
// Returns the smallest index of the given candidate list which satisfies
// |candidates.focused_index| == |candidates.candidate(i).index()|.
// This function returns the size of the given candidate list when there
// aren't any candidates satisfying the above condition.
int GetFocusedArrayIndex(const Candidates &candidates) {
const int kInvalidIndex = candidates.candidate_size();
if (!candidates.has_focused_index()) {
return kInvalidIndex;
}
const int focused_index = candidates.focused_index();
return GetCandidateArrayIndexByCandidateIndex(candidates, focused_index);
}
class TipImmersiveUiElementImpl : public ITfCandidateListUIElementBehavior {
public:
TipImmersiveUiElementImpl(TipTextService *text_service,
ITfContext *context,
HWND window_handle)
: text_service_(text_service),
context_(context),
delegate_(TipUiElementDelegateFactory::Create(
text_service, context,
TipUiElementDelegateFactory::kImmersiveCandidateWindow)),
working_area_(renderer::win32::WorkingAreaFactory::Create()),
text_renderer_(renderer::win32::TextRenderer::Create()),
window_(window_handle),
window_visible_(false) {
}
// Destructor is kept as non-virtual because this class is designed to be
// destroyed only by "delete this" in Release() method.
// TODO(yukawa): put "final" keyword to the class declaration when C++11
// is allowed.
~TipImmersiveUiElementImpl() {
}
// The IUnknown interface methods.
virtual STDMETHODIMP QueryInterface(REFIID interface_id, void **object) {
if (!object) {
return E_INVALIDARG;
}
*object = nullptr;
// Find a matching interface from the ones implemented by this object.
// This object implements IUnknown and ITfEditSession.
if (::IsEqualIID(interface_id, IID_IUnknown)) {
*object = static_cast<IUnknown *>(
static_cast<ITfCandidateListUIElement *>(this));
} else if (IsEqualIID(interface_id, IID_ITfUIElement)) {
*object = static_cast<ITfUIElement *>(
static_cast<ITfCandidateListUIElement *>(this));
} else if (IsEqualIID(interface_id, IID_ITfCandidateListUIElement)) {
*object = static_cast<ITfCandidateListUIElement *>(this);
} else if (IsEqualIID(interface_id,
IID_ITfCandidateListUIElementBehavior)) {
*object = static_cast<ITfCandidateListUIElementBehavior *>(this);
}
if (*object == nullptr) {
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
virtual ULONG STDMETHODCALLTYPE AddRef() {
return ref_count_.AddRefImpl();
}
virtual ULONG STDMETHODCALLTYPE Release() {
const ULONG count = ref_count_.ReleaseImpl();
if (count == 0) {
delete this;
}
return count;
}
void OnUpdate() {
// When RequestEditSession fails, it does not maintain the reference count.
// So we need to ensure that AddRef/Release should be called at least once
// per object.
CComPtr<ITfEditSession> edit_session(new UpdateUiEditSession(
text_service_, context_, this));
HRESULT edit_session_result = S_OK;
const HRESULT result = context_->RequestEditSession(
text_service_->GetClientID(),
edit_session,
TF_ES_ASYNCDONTCARE | TF_ES_READ, &edit_session_result);
}
// Returns true when handled.
bool HandleMouseEvent(
UINT nFlags, const CPoint &point, bool select_candidate) {
const Candidates candidates = output_.candidates();
for (size_t i = 0; i < candidates.candidate_size(); ++i) {
const Candidate &candidate = candidates.candidate(i);
const CRect rect = ToCRect(table_layout_.GetRowRect(i));
if (rect.PtInRect(point)) {
if (select_candidate) {
TipEditSession::SelectCandidateAsync(
text_service_, context_, candidate.id());
return true;
} else {
const int focused_array_index = GetFocusedArrayIndex(candidates);
if (i != focused_array_index) {
TipEditSession::HilightCandidateAsync(
text_service_, context_, candidate.id());
return true;
}
return false;
}
}
}
return false;
}
void ShowWindow(bool content_changed) {
window_.ShowWindow(SW_SHOWNA);
if (!window_visible_) {
::NotifyWinEvent(
EVENT_OBJECT_IME_SHOW, window_.m_hWnd, OBJID_WINDOW, CHILDID_SELF);
} else if (content_changed) {
::NotifyWinEvent(
EVENT_OBJECT_IME_CHANGE, window_.m_hWnd, OBJID_WINDOW, CHILDID_SELF);
}
window_visible_ = true;
}
void HideWindow() {
window_.ShowWindow(SW_HIDE);
if (window_visible_) {
::NotifyWinEvent(
EVENT_OBJECT_IME_HIDE, window_.m_hWnd, OBJID_WINDOW, CHILDID_SELF);
}
window_visible_ = false;
}
void Render(const RenderingInfo &info) {
// Should be compared here before |output_| is synced to |info.output|.
const bool content_changed =
(target_rect_ != info.target_rect) ||
(output_.SerializeAsString() != info.output.SerializeAsString());
output_.CopyFrom(info.output);
if (info.has_target_rect) {
target_rect_ = info.target_rect;
RenderImpl();
}
BOOL shown = FALSE;
delegate_->IsShown(&shown);
if (!shown) {
HideWindow();
} else {
ShowWindow(content_changed);
}
}
LRESULT WindowProc(
HWND window_handle, UINT message, WPARAM wparam, LPARAM lparam) {
switch (message) {
case WM_MOZC_IMMERSIVE_WINDOW_UPDATE:
OnUpdate();
return 0;
case WM_MOUSEACTIVATE:
return MA_NOACTIVATE;
case WM_LBUTTONDOWN:
HandleMouseEvent(static_cast<UINT>(wparam),
CPoint(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)),
false);
return ::DefWindowProcW(window_handle, message, wparam, lparam);
case WM_LBUTTONUP:
HandleMouseEvent(static_cast<UINT>(wparam),
CPoint(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)),
true);
return ::DefWindowProcW(window_handle, message, wparam, lparam);
case WM_MOUSEMOVE:
if ((static_cast<UINT>(wparam) & MK_LBUTTON) == MK_LBUTTON) {
HandleMouseEvent(static_cast<UINT>(wparam),
CPoint(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)),
false);
}
return ::DefWindowProcW(window_handle, message, wparam, lparam);
case WM_SETCURSOR:
::SetCursor(::LoadCursor(NULL, IDC_ARROW));
return 0;
default:
return ::DefWindowProcW(window_handle, message, wparam, lparam);
}
}
private:
void RenderImpl() {
if (!output_.has_candidates()) {
return;
}
CSize size;
int left_offset = 0;
CBitmap bitmap(TipUiRendererImmersive::Render(
output_.candidates(), text_renderer_.get(), &table_layout_,
&size, &left_offset));
const CPoint target_point(target_rect_.left, target_rect_.bottom);
Rect new_position(target_point.x - left_offset,
target_point.y,
size.cx,
size.cy);
{
const Rect preedit_rect(target_rect_.left,
target_rect_.top,
target_rect_.Width(),
target_rect_.Height());
const Size window_size(size.cx, size.cy);
const Point zero_point_offset(left_offset, 0);
Rect working_area;
CRect area;
if (working_area_->GetWorkingAreaFromPoint(target_point, &area)) {
working_area = Rect(area.left, area.top, area.Width(), area.Height());
}
new_position =
WindowUtil::GetWindowRectForMainWindowFromTargetPointAndPreedit(
Point(target_point.x, target_point.y),
preedit_rect,
window_size,
zero_point_offset,
working_area,
false);
}
CDC memdc;
memdc.CreateCompatibleDC();
const CBitmapHandle old_bitmap = memdc.SelectBitmap(bitmap);
CPoint src_left_top(0, 0);
CPoint new_top_left(new_position.Left(), new_position.Top());
CSize new_size(new_position.Width(), new_position.Height());
BLENDFUNCTION func = {AC_SRC_OVER, 0, 255, 0};
::UpdateLayeredWindow(window_.m_hWnd, nullptr, &new_top_left, &new_size,
memdc, &src_left_top, 0, &func, ULW_ALPHA);
memdc.SelectBitmap(old_bitmap);
}
// This class is an implementation class for the ITfEditSession classes,
// which is an observer for exclusively read the date from the text store.
class UpdateUiEditSession : public ITfEditSession {
public:
// Destructor is kept as non-virtual because this class is designed to be
// destroyed only by "delete this" in Release() method.
// TODO(yukawa): put "final" keyword to the class declaration when C++11
// is allowed.
~UpdateUiEditSession();
// The IUnknown interface methods.
virtual STDMETHODIMP QueryInterface(REFIID interface_id, void **object);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
// The ITfEditSession interface method.
// This function is called back by the TSF thread manager when an edit
// request is granted.
virtual HRESULT STDMETHODCALLTYPE DoEditSession(TfEditCookie read_cookie);
UpdateUiEditSession(
TipTextService *text_service, ITfContext *context,
TipImmersiveUiElementImpl *ui_element);
private:
TipRefCount ref_count_;
CComPtr<TipTextService> text_service_;
CComPtr<ITfContext> context_;
CComPtr<TipImmersiveUiElementImpl> ui_element_;
DISALLOW_COPY_AND_ASSIGN(UpdateUiEditSession);
};
// The ITfUIElement interface methods
virtual HRESULT STDMETHODCALLTYPE GetDescription(BSTR *description) {
return delegate_->GetDescription(description);
}
virtual HRESULT STDMETHODCALLTYPE GetGUID(GUID *guid) {
return delegate_->GetGUID(guid);
}
virtual HRESULT STDMETHODCALLTYPE Show(BOOL show) {
return delegate_->Show(show);
}
virtual HRESULT STDMETHODCALLTYPE IsShown(BOOL *show) {
return delegate_->IsShown(show);
}
// The ITfCandidateListUIElement interface methods
virtual HRESULT STDMETHODCALLTYPE GetUpdatedFlags(DWORD *flags) {
return delegate_->GetUpdatedFlags(flags);
}
virtual HRESULT STDMETHODCALLTYPE GetDocumentMgr(
ITfDocumentMgr **document_manager) {
return delegate_->GetDocumentMgr(document_manager);
}
virtual HRESULT STDMETHODCALLTYPE GetCount(UINT *count) {
return delegate_->GetCount(count);
}
virtual HRESULT STDMETHODCALLTYPE GetSelection(UINT *index) {
return delegate_->GetSelection(index);
}
virtual HRESULT STDMETHODCALLTYPE GetString(UINT index, BSTR *text) {
return delegate_->GetString(index, text);
}
virtual HRESULT STDMETHODCALLTYPE GetPageIndex(UINT *index, UINT size,
UINT *page_count) {
return delegate_->GetPageIndex(index, size, page_count);
}
virtual HRESULT STDMETHODCALLTYPE SetPageIndex(UINT *index,
UINT page_count) {
return delegate_->SetPageIndex(index, page_count);
}
virtual HRESULT STDMETHODCALLTYPE GetCurrentPage(UINT *current_page) {
return delegate_->GetCurrentPage(current_page);
}
// The ITfCandidateListUIElementBehavior interface methods
virtual HRESULT STDMETHODCALLTYPE SetSelection(UINT index) {
return delegate_->SetSelection(index);
}
virtual HRESULT STDMETHODCALLTYPE Finalize() {
return delegate_->Finalize();
}
virtual HRESULT STDMETHODCALLTYPE Abort() {
return delegate_->Abort();
}
TipRefCount ref_count_;
CComPtr<TipTextService> text_service_;
CComPtr<ITfContext> context_;
unique_ptr<TipUiElementDelegate> delegate_;
unique_ptr<renderer::win32::WorkingAreaInterface> working_area_;
unique_ptr<renderer::win32::TextRenderer> text_renderer_;
CWindow window_;
bool window_visible_;
TableLayout table_layout_;
CRect target_rect_;
Output output_;
DISALLOW_COPY_AND_ASSIGN(TipImmersiveUiElementImpl);
};
HWND GetOwnerWindow(ITfContext *context) {
CComPtr<ITfContextView> context_view;
if (FAILED(context->GetActiveView(&context_view)) || !context_view) {
return nullptr;
}
HWND window_handle = nullptr;
if (FAILED(context_view->GetWnd(&window_handle)) ||
window_handle == nullptr) {
window_handle = ::GetFocus();
}
return window_handle;
}
class WindowMap
: public hash_map<HWND, TipImmersiveUiElementImpl *> {
};
class ThreadLocalInfo {
public:
ThreadLocalInfo() {}
WindowMap *window_map() {
return &window_map_;
}
private:
WindowMap window_map_;
DISALLOW_COPY_AND_ASSIGN(ThreadLocalInfo);
};
ThreadLocalInfo *GetThreadLocalInfo() {
if (g_module_unloaded) {
return nullptr;
}
if (g_tls_index == TLS_OUT_OF_INDEXES) {
return nullptr;
}
ThreadLocalInfo *info = static_cast<ThreadLocalInfo *>(
::TlsGetValue(g_tls_index));
if (info != nullptr) {
// already initialized.
return info;
}
info = new ThreadLocalInfo();
::TlsSetValue(g_tls_index, info);
return info;
}
void EnsureThreadLocalInfoDestroyed() {
if (g_module_unloaded) {
return;
}
if (g_tls_index == TLS_OUT_OF_INDEXES) {
return;
}
ThreadLocalInfo *info = static_cast<ThreadLocalInfo *>(
::TlsGetValue(g_tls_index));
if (info == nullptr) {
// already destroyed.
return;
}
delete info;
::TlsSetValue(g_tls_index, nullptr);
}
LRESULT WINAPI WindowProc(HWND window_handle,
UINT message,
WPARAM wparam,
LPARAM lparam) {
ThreadLocalInfo *info = GetThreadLocalInfo();
if (info == nullptr) {
return ::DefWindowProcW(window_handle, message, wparam, lparam);
}
const WindowMap::iterator it = info->window_map()->find(window_handle);
if (it == info->window_map()->end()) {
return ::DefWindowProcW(window_handle, message, wparam, lparam);
}
switch (message) {
case WM_NCDESTROY: {
const LRESULT result =
it->second->WindowProc(window_handle, message, wparam, lparam);
info->window_map()->erase(it);
return result;
}
default:
return it->second->WindowProc(window_handle, message, wparam, lparam);
}
}
TipImmersiveUiElementImpl::UpdateUiEditSession::~UpdateUiEditSession() {}
// The IUnknown interface methods.
STDMETHODIMP TipImmersiveUiElementImpl::UpdateUiEditSession::QueryInterface(
REFIID interface_id, void **object) {
if (!object) {
return E_INVALIDARG;
}
// Find a matching interface from the ones implemented by this object.
// This object implements IUnknown and ITfEditSession.
if (::IsEqualIID(interface_id, IID_IUnknown)) {
*object = static_cast<IUnknown *>(this);
} else if (IsEqualIID(interface_id, IID_ITfEditSession)) {
*object = static_cast<ITfEditSession *>(this);
} else {
*object = nullptr;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
ULONG STDMETHODCALLTYPE
TipImmersiveUiElementImpl::UpdateUiEditSession::AddRef() {
return ref_count_.AddRefImpl();
}
ULONG STDMETHODCALLTYPE
TipImmersiveUiElementImpl::UpdateUiEditSession::Release() {
const ULONG count = ref_count_.ReleaseImpl();
if (count == 0) {
delete this;
}
return count;
}
// The ITfEditSession interface method.
// This function is called back by the TSF thread manager when an edit
// request is granted.
HRESULT STDMETHODCALLTYPE
TipImmersiveUiElementImpl::UpdateUiEditSession::DoEditSession(
TfEditCookie read_cookie) {
RenderingInfo info;
if (FillRenderInfo(text_service_, context_, read_cookie, &info)) {
ui_element_->Render(info);
}
return S_OK;
}
TipImmersiveUiElementImpl::UpdateUiEditSession::UpdateUiEditSession(
TipTextService *text_service, ITfContext *context,
TipImmersiveUiElementImpl *ui_element)
: text_service_(text_service),
context_(context),
ui_element_(ui_element) {
}
} // namespace
ITfUIElement *TipUiElementImmersive::New(
TipTextService *text_service,
ITfContext *context,
HWND *window_handle) {
if (window_handle == nullptr) {
return nullptr;
}
*window_handle = nullptr;
ThreadLocalInfo *info = GetThreadLocalInfo();
if (info == nullptr) {
return nullptr;
}
const HWND owner_window = GetOwnerWindow(context);
if (!::IsWindow(owner_window)) {
return nullptr;
}
const HWND window = ::CreateWindowExW(
WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE,
kImmersiveUIWindowClassName,
L"",
WS_POPUP,
0, 0, 0, 0,
owner_window,
nullptr,
g_module,
nullptr);
if (window == nullptr) {
return nullptr;
}
TipImmersiveUiElementImpl *impl =
new TipImmersiveUiElementImpl(text_service, context, window);
(*info->window_map())[window] = impl;
*window_handle = window;
return static_cast<ITfCandidateListUIElement *>(impl);
}
void TipUiElementImmersive::OnActivate() {
GetThreadLocalInfo();
}
void TipUiElementImmersive::OnDeactivate() {
EnsureThreadLocalInfoDestroyed();
}
bool TipUiElementImmersive::OnDllProcessAttach(HINSTANCE module_handle,
bool static_loading) {
g_module = module_handle;
g_tls_index = ::TlsAlloc();
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_IME;
wc.lpfnWndProc = WindowProc;
wc.hInstance = module_handle;
wc.lpszClassName = kImmersiveUIWindowClassName;
const ATOM atom = ::RegisterClassExW(&wc);
if (atom == INVALID_ATOM) {
return false;
}
return true;
}
void TipUiElementImmersive::OnDllProcessDetach(HINSTANCE module_handle,
bool process_shutdown) {
if (g_tls_index != TLS_OUT_OF_INDEXES) {
::TlsFree(g_tls_index);
g_tls_index = TLS_OUT_OF_INDEXES;
}
::UnregisterClass(kImmersiveUIWindowClassName, module_handle);
g_module_unloaded = true;
}
} // namespace tsf
} // namespace win32
} // namespace mozc