blob: bdaee645a6cea80b20001e7800a4e48e48837ece [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_lang_bar_menu.h"
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
#include <atlbase.h>
#include <atlapp.h>
#include <atlmisc.h>
#include <atlgdi.h>
#include <atluser.h>
#include <ole2.h>
#include <olectl.h>
#include <strsafe.h>
#include <limits>
#include "base/system_util.h"
#include "base/win_util.h"
#include "win32/base/text_icon.h"
#include "win32/base/tsf_profile.h"
#include "win32/tip/tip_dll_module.h"
#include "win32/tip/tip_resource.h"
namespace mozc {
namespace win32 {
namespace tsf {
namespace {
using WTL::CBitmap;
using WTL::CDC;
using WTL::CIcon;
using WTL::CMenu;
using WTL::CMenuItemInfo;
using WTL::CSize;
const int kDefaultDPI = 96;
// Represents the cookie for the sink to an TipLangBarButton object.
const int kTipLangBarMenuCookie = (('M' << 24) |
('o' << 16) |
('z' << 8) |
('c' << 0));
// "MS ゴシック"
const char kTextIconFont[] =
"\xEF\xBC\xAD\xEF\xBC\xB3"
"\x20\xE3\x82\xB4\xE3\x82\xB7\xE3\x83\x83\xE3\x82\xAF";
// TODO(yukawa): Refactor LangBar code so that we can configure following
// settings as a part of initialization.
string GetIconStringIfNecessary(UINT icon_id) {
switch (icon_id) {
case IDI_DIRECT_NT:
return "A";
case IDI_HIRAGANA_NT:
// "あ"
return "\xE3\x81\x82";
case IDI_FULL_KATAKANA_NT:
// "ア"
return "\xE3\x82\xA2";
case IDI_HALF_ALPHANUMERIC_NT:
return "_A";
case IDI_FULL_ALPHANUMERIC_NT:
// "A"
return "\xEF\xBC\xA1";
case IDI_HALF_KATAKANA_NT:
// "_ア"
return "_" "\xEF\xBD\xB1";
}
return string();
}
// Loads an icon which is appropriate for the current theme.
// An icon ID 0 represents "no icon".
HICON LoadIconFromResource(HINSTANCE instance,
UINT icon_id_for_non_theme,
UINT icon_id_for_theme) {
// We use a 32-bpp icon if we can observe the uxtheme is running.
UINT id = icon_id_for_non_theme;
if (icon_id_for_theme != 0) {
const wchar_t kThemeDll[] = L"uxtheme.dll";
typedef BOOL (WINAPI *FPIsThemeActive)();
// Util::GetSystemModuleHandle is not safe when the specified DLL is
// unloaded by other threads.
const HMODULE theme_dll =
WinUtil::GetSystemModuleHandleAndIncrementRefCount(kThemeDll);
if (theme_dll != nullptr) {
FPIsThemeActive is_thread_active = reinterpret_cast<FPIsThemeActive>(
::GetProcAddress(theme_dll, "IsThemeActive"));
if (is_thread_active != nullptr && is_thread_active()) {
id = icon_id_for_theme;
}
}
if (theme_dll != nullptr) {
::FreeLibrary(theme_dll);
}
}
if (id == 0) {
return nullptr;
}
const auto icon_size = ::GetSystemMetrics(SM_CYSMICON);
// Replace some text icons with on-the-fly image drawn with MS-Gothic.
const auto &icon_text = GetIconStringIfNecessary(id);
if (!icon_text.empty()) {
const COLORREF text_color = ::GetSysColor(COLOR_WINDOWTEXT);
return TextIcon::CreateMonochromeIcon(
icon_size, icon_size, icon_text, kTextIconFont, text_color);
}
return static_cast<HICON>(::LoadImage(
instance, MAKEINTRESOURCE(id),
IMAGE_ICON, icon_size, icon_size, LR_CREATEDIBSECTION));
}
// Retrieves the bitmap handle loaded by using an icon ID.
// Returns true if the specified icons is available as bitmaps.
// Caller can set nullptr for |color| and/or |mask| to represent not to recieve
// the specified handle even if it exists. Caller should releases any returned
// bitmap handle and this function releases any handle which is not recieved
// by the caller.
bool LoadIconAsBitmap(HINSTANCE instance,
UINT icon_id_for_non_theme,
UINT icon_id_for_theme,
HBITMAP *color,
HBITMAP *mask) {
if (color != nullptr) {
*color = nullptr;
}
if (mask != nullptr) {
*mask = nullptr;
}
CIcon icon(LoadIconFromResource(
instance, icon_id_for_non_theme, icon_id_for_theme));
if (icon.IsNull()) {
return false;
}
ICONINFO icon_info = {};
if (!::GetIconInfo(icon, &icon_info)) {
return false;
}
CBitmap color_bitmap(icon_info.hbmColor);
CBitmap mask_bitmap(icon_info.hbmMask);
if (color != nullptr) {
*color = color_bitmap.Detach();
}
if (mask != nullptr) {
*mask = mask_bitmap.Detach();
}
return true;
}
} // namespace
HRESULT TipLangBarMenuDataArray::Init(
HINSTANCE instance,
const TipLangBarMenuItem *menu,
int count) {
HRESULT result = S_OK;
// Attach menu texts and icons.
for (int i = 0; i < count; ++i) {
TipLangBarMenuData data = {};
int length = 0;
HICON icon = nullptr;
if ((menu[i].flags_ & TF_LBMENUF_SEPARATOR) == 0) {
// Retrieve the menu text and button icon.
length = ::LoadString(instance,
menu[i].text_id_,
&data.text_[0],
arraysize(data.text_));
}
data.flags_ = menu[i].flags_;
data.item_id_ = menu[i].item_id_;
data.text_id_ = menu[i].text_id_;
data.length_ = length;
data.icon_id_for_non_theme_ = menu[i].icon_id_for_non_theme_;
data.icon_id_for_theme_ = menu[i].icon_id_for_theme_;
data_.push_back(data);
}
return result;
}
size_t TipLangBarMenuDataArray::size() const {
return data_.size();
}
TipLangBarMenuData* TipLangBarMenuDataArray::data(size_t i) {
if (i >= size()) {
return nullptr;
}
return &data_[i];
}
// Implements the constructor of the TipLangBarButton class.
TipLangBarButton::TipLangBarButton(TipLangBarCallback *langbar_callback,
const GUID &guid,
bool is_menu,
bool show_in_tray)
: item_sink_(nullptr),
langbar_callback_(nullptr),
status_(0),
context_menu_enabled_(true) {
// Initialize its TF_LANGBARITEMINFO object, which contains the properties of
// this item and is copied to the TSF manager in GetInfo() function.
item_info_.clsidService = TsfProfile::GetTextServiceGuid();
item_info_.guidItem = guid;
// The visibility of a langbar-item in the minimized langbar is actually
// controlled by TF_LBI_STYLE_SHOWNINTRAY flag despite the fact that the
// document says "This flag is not currently supported".
// http://msdn.microsoft.com/en-us/library/ms629078.aspx
// http://b/2275633
item_info_.dwStyle = 0;
if (is_menu) {
item_info_.dwStyle |= TF_LBI_STYLE_BTN_MENU;
} else {
item_info_.dwStyle |= TF_LBI_STYLE_BTN_BUTTON;
}
if (show_in_tray) {
item_info_.dwStyle |= TF_LBI_STYLE_SHOWNINTRAY;
}
item_info_.ulSort = 0;
item_info_.szDescription[0] = L'\0';
// Save the TipLangBarCallback object who owns this button, and increase its
// reference count not to prevent the object from being deleted
langbar_callback_ = langbar_callback;
langbar_callback_->AddRef();
}
// Implements the destructor of the TipLangBarButton class.
TipLangBarButton::~TipLangBarButton() {
// Release the owner TipLangBarCallback object.
if (langbar_callback_ != nullptr) {
langbar_callback_->Release();
}
langbar_callback_ = nullptr;
}
// Implements the ITfLangBarItem::GetInfo() function.
// This function is called by Windows to update this button menu.
STDMETHODIMP TipLangBarButton::GetInfo(TF_LANGBARITEMINFO *item_info) {
// Just copies the cached TF_LANGBARITEMINFO object.
*item_info = item_info_;
return S_OK;
}
// Implements the ITfLangBarItem::GetStatus() function.
// This fucntion is called by Windows to retrieve the current status of this
// button menu.
STDMETHODIMP TipLangBarButton::GetStatus(DWORD *status) {
*status = status_;
return S_OK;
}
// Implements the ITfLangBarItem::Show() function.
// This function is called by Windows to notify the display status of this
// button menu has been updated.
STDMETHODIMP TipLangBarButton::Show(BOOL show) {
// Just return becasue this button is always shown, i.e. we do not have to
// manage the display state of this button menu.
return E_NOTIMPL;
}
// Implements the ITfLangBarItem::GetTooltipString() function.
// This function is called when Windows to retrieve the tool-tip string of
// this button menu.
STDMETHODIMP TipLangBarButton::GetTooltipString(BSTR *tooltip) {
// Created a COM string from the description and copy it.
*tooltip = ::SysAllocString(&item_info_.szDescription[0]);
return (*tooltip ? S_OK : E_OUTOFMEMORY);
}
// Implements the ITfLangBarItemButton::OnClick() function.
// This function is not used for a button menu.
STDMETHODIMP TipLangBarButton::OnClick(TfLBIClick click,
POINT point,
const RECT *rect) {
if (IsMenuButton()) {
// ITfLangBarItem object is a button menu.
return S_OK;
}
if (click == TF_LBI_CLK_LEFT) {
return langbar_callback_->OnItemClick(item_info_.szDescription);
}
// If context menu is disabled, do nothing.
if (!context_menu_enabled_) {
return S_OK;
}
CMenu menu;
menu.CreatePopupMenu();
for (size_t i = 0; i < menu_data_size(); ++i) {
TipLangBarMenuData *data = menu_data(i);
const UINT id = static_cast<UINT>(data->item_id_);
const wchar_t *text = data->text_;
CMenuItemInfo info;
if (data->flags_ == TF_LBMENUF_SEPARATOR) {
info.fMask |= MIIM_FTYPE;
info.fType |= MFT_SEPARATOR;
} else {
info.fMask |= MIIM_ID;
info.wID = id;
info.fMask |= MIIM_FTYPE;
info.fType |= MFT_STRING;
info.fMask |= MIIM_STRING;
info.dwTypeData = data->text_;
switch (data->flags_) {
case TF_LBMENUF_RADIOCHECKED:
info.fMask |= MIIM_STATE;
info.fState |= MFS_CHECKED;
info.fMask |= MIIM_FTYPE;
info.fType |= MFT_RADIOCHECK;
break;
case TF_LBMENUF_CHECKED:
info.fMask |= MIIM_STATE;
info.fState |= MFS_CHECKED;
break;
case TF_LBMENUF_SUBMENU:
// TODO(yukawa): Support this.
break;
case TF_LBMENUF_GRAYED:
info.fMask |= MIIM_STATE;
info.fState |= MFS_GRAYED;
break;
default:
info.fMask |= MIIM_STATE;
info.fState |= MFS_ENABLED;
break;
}
}
menu.InsertMenuItemW(i, TRUE, &info);
}
// Caveats: TPM_NONOTIFY is important because the attached window may
// change the menu state unless this flag is specified. We actually suffered
// from this issue with Internet Explorer 10 on Windows 8. b/10217103.
const DWORD kMenuFlags = TPM_NONOTIFY | TPM_RETURNCMD | TPM_LEFTALIGN |
TPM_TOPALIGN | TPM_LEFTBUTTON;
const BOOL result = menu.TrackPopupMenu(
kMenuFlags, point.x, point.y, ::GetFocus());
if (!result) {
return E_FAIL;
}
return langbar_callback_->OnMenuSelect(
static_cast<TipLangBarCallback::ItemId>(result));
}
// Implements the ITfLangBarItemButton::GetText() function.
// This function is called by Windows to retrive the text label of this
// button menu.
STDMETHODIMP TipLangBarButton::GetText(BSTR *text) {
*text = ::SysAllocString(&item_info_.szDescription[0]);
return (*text ? S_OK : E_OUTOFMEMORY);
}
// Implements the ITfSource::AdviseSink() funtion.
STDMETHODIMP TipLangBarButton::AdviseSink(REFIID interface_id,
IUnknown *unknown,
DWORD *cookie) {
// Return if the caller tries to start advising any events except the
// ITfLangBarItemSink events.
if (!IsEqualIID(IID_ITfLangBarItemSink, interface_id))
return CONNECT_E_CANNOTCONNECT;
// Exit if this object has a sink which advising ITfLangBarItemSink events.
if (item_sink_ != nullptr)
return CONNECT_E_ADVISELIMIT;
// Retrieve the ITfLangBarItemSink interface from the given object and store
// it into |item_sink_|.
HRESULT result = unknown->QueryInterface(
IID_ITfLangBarItemSink, reinterpret_cast<void **>(&item_sink_));
if (result != S_OK) {
item_sink_ = nullptr;
return result;
}
// Return the cookie of this object.
*cookie = kTipLangBarMenuCookie;
return result;
}
STDMETHODIMP TipLangBarButton::UnadviseSink(DWORD cookie) {
// Return if the given cookie
if (cookie != kTipLangBarMenuCookie || item_sink_ == nullptr)
return CONNECT_E_NOCONNECTION;
// Release the copy of this event.
item_sink_->Release();
item_sink_ = nullptr;
return S_OK;
}
// Initializes an TipLangBarButton instance.
// This function is called by a text service to provide the information
// required for creating a menu button. A text service MUST call this function
// before calling the ITfLangBarItemMgr::AddItem() function and adding this
// button menu to a language bar.
HRESULT TipLangBarButton::Init(HINSTANCE instance,
int string_id,
const TipLangBarMenuItem *menu,
int count) {
HRESULT result = S_OK;
// Retrieve the text label from the resource.
// This string is also used as a tool-tip text.
::LoadString(instance, string_id, &item_info_.szDescription[0],
arraysize(item_info_.szDescription));
// Create a new TipLangBarMenuItem object.
menu_data_.Init(instance, menu, count);
return S_OK;
}
HRESULT TipLangBarButton::OnUpdate(DWORD update_flag) {
// For some reason, this method might be called when the event sink is not
// available. See b/2977835 for details.
if (item_sink_ == nullptr) {
return E_FAIL;
}
return item_sink_->OnUpdate(update_flag);
}
HRESULT TipLangBarButton::SetEnabled(bool enabled) {
HRESULT result = S_OK;
if (enabled) {
status_ &= ~TF_LBI_STATUS_DISABLED;
} else {
status_ |= TF_LBI_STATUS_DISABLED;
}
result = TipLangBarButton::OnUpdate(TF_LBI_STATUS);
return result;
}
bool TipLangBarButton::CanContextMenuDisplay32bppIcon() {
// Windows XP does not support a 32-bpp icon for a context menu icon on the
// LangBar. See http://b/2260057 for details.
if (!SystemUtil::IsVistaOrLater()) {
return false;
}
// We always use a non-theme icon for a context menu icon on the LangBar
// unless the current display mode is 32-bpp. We cannot assume we can
// display a 32-bpp icon for a context menu icon on the LangBar unless the
// current display mode is 32-bpp. See http://b/2260057
CDC display_dc(::GetDC(nullptr));
return !display_dc.IsNull() &&
display_dc.GetDeviceCaps(PLANES) == 1 &&
display_dc.GetDeviceCaps(BITSPIXEL) == 32;
}
TipLangBarMenuData* TipLangBarButton::menu_data(size_t i) {
return menu_data_.data(i);
}
size_t TipLangBarButton::menu_data_size() const {
return menu_data_.size();
}
const TF_LANGBARITEMINFO* TipLangBarButton::item_info() const {
return &item_info_;
}
bool TipLangBarButton::IsMenuButton() const {
return (item_info_.dwStyle & TF_LBI_STYLE_BTN_MENU) ==
TF_LBI_STYLE_BTN_MENU;
}
void TipLangBarButton::SetContextMenuEnabled(bool enabled) {
context_menu_enabled_ = enabled;
}
// Basic implements the IUnknown::QueryInterface() function.
STDMETHODIMP TipLangBarButton::QueryInterfaceBase(
REFIID interface_id, void **object) {
if (!object) {
return E_INVALIDARG;
}
// Find a matching interface from the ones implemented by this object
// (i.e. IUnknown, ITfLangBarItem, ITfLangBarItemButton, ITfSource).
if (::IsEqualIID(interface_id, __uuidof(IMozcLangBarItem))) {
IMozcLangBarItem *ptr = static_cast<IMozcLangBarItem *>(this);
ptr->AddRef();
*object = ptr;
return S_OK;
}
if (::IsEqualIID(interface_id, IID_IUnknown)) {
ITfLangBarItemButton *ptr = static_cast<ITfLangBarItemButton *>(this);
ptr->AddRef();
*object = static_cast<IUnknown*>(ptr);
return S_OK;
}
if (::IsEqualIID(interface_id, IID_ITfLangBarItem)) {
ITfLangBarItem *ptr = static_cast<ITfLangBarItem *>(this);
ptr->AddRef();
*object = ptr;
return S_OK;
}
if (::IsEqualIID(interface_id, IID_ITfLangBarItemButton)) {
ITfLangBarItemButton *ptr = static_cast<ITfLangBarItemButton *>(this);
ptr->AddRef();
*object = ptr;
return S_OK;
}
if (::IsEqualIID(interface_id, IID_ITfSource)) {
ITfSource *ptr = static_cast<ITfSource *>(this);
ptr->AddRef();
*object = ptr;
return S_OK;
}
// This object does not implement the given interface.
*object = nullptr;
return E_NOINTERFACE;
}
void TipLangBarButton::SetDescription(const wstring &description) {
::StringCchCopy(item_info_.szDescription,
arraysize(item_info_.szDescription),
description.c_str());
}
TipLangBarMenuButton::TipLangBarMenuButton(
TipLangBarCallback *langbar_callback, const GUID &guid, bool show_in_tray)
: TipLangBarButton(langbar_callback, guid, true, show_in_tray),
menu_icon_id_for_theme_(0),
menu_icon_id_for_non_theme_(0) {
}
// Implements the IUnknown::QueryInterface() function.
// This function is used by Windows to retrieve the interfaces implemented by
// this class.
STDMETHODIMP TipLangBarMenuButton::QueryInterface(
REFIID interface_id, void** object) {
return QueryInterfaceBase(interface_id, object);
}
STDMETHODIMP_(ULONG) TipLangBarMenuButton::AddRef() {
return ref_count_.AddRefImpl();
}
STDMETHODIMP_(ULONG) TipLangBarMenuButton::Release() {
const ULONG count = ref_count_.ReleaseImpl();
if (count == 0) {
delete this;
}
return count;
}
STDMETHODIMP TipLangBarMenuButton::InitMenu(ITfMenu *menu) {
HRESULT result = S_OK;
// Do nothing if the element is not menu botton.
if (!IsMenuButton()) {
return result;
}
// Add the menu items of this object to the given ITfMenu object.
for (size_t i = 0; i < menu_data_size(); ++i) {
const TipLangBarMenuData* data = menu_data(i);
const UINT icon_id_for_theme =
CanContextMenuDisplay32bppIcon() ? data->icon_id_for_theme_
: data->icon_id_for_non_theme_;
CBitmap bitmap;
CBitmap mask;
// If LoadIconAsBitmap fails, |bitmap.m_hBitmap| and |mask.m_hBitmap|
// remain nullptr bitmap handles.
LoadIconAsBitmap(TipDllModule::module_handle(),
data->icon_id_for_non_theme_,
icon_id_for_theme,
&bitmap.m_hBitmap,
&mask.m_hBitmap);
result = menu->AddMenuItem(i, data->flags_, bitmap, mask,
data->text_, data->length_,
nullptr);
if (result != S_OK) {
break;
}
}
return result;
}
STDMETHODIMP TipLangBarMenuButton::OnMenuSelect(UINT menu_id) {
// Call the TipLangBarCallback::OnMenuSelect() function to dispatch the
// given event.
TipLangBarMenuData* data = menu_data(menu_id);
if (data == nullptr) {
return E_INVALIDARG;
}
if (data->item_id_ == TipLangBarCallback::kCancel) {
return S_OK;
}
const HRESULT result = langbar_callback_->OnMenuSelect(
static_cast<TipLangBarCallback::ItemId>(data->item_id_));
return result;
}
// Implements the ITfLangBarItem::GetInfo() function.
// This function is called by Windows to update this button menu.
STDMETHODIMP TipLangBarMenuButton::GetInfo(TF_LANGBARITEMINFO *item_info) {
HRESULT result = S_OK;
if (item_info == nullptr) {
return E_INVALIDARG;
}
// Just copies the cached TF_LANGBARITEMINFO object.
*item_info = *this->item_info();
CIcon icon;
if (FAILED(GetIcon(&icon.m_hIcon)) || icon.IsNull()) {
return S_OK;
}
ICONINFO icon_info = {};
const BOOL get_icon_succeeded = icon.GetIconInfo(&icon_info);
CBitmap color(icon_info.hbmColor);
CBitmap mask(icon_info.hbmMask);
if (!get_icon_succeeded) {
return S_OK;
}
if (color.IsNull() && !mask.IsNull()) {
item_info->dwStyle |= TF_LBI_STYLE_TEXTCOLORICON;
return S_OK;
}
return S_OK;
}
STDMETHODIMP TipLangBarMenuButton::GetIcon(HICON *icon) {
if (icon == nullptr) {
return E_INVALIDARG;
}
// Excerpt: http://msdn.microsoft.com/en-us/library/ms628718.aspx
// The caller must free this icon when it is no longer required by
// calling DestroyIcon.
*icon = LoadIconFromResource(TipDllModule::module_handle(),
menu_icon_id_for_non_theme_,
menu_icon_id_for_theme_);
return (*icon ? S_OK : E_FAIL);
}
// Initializes an ImeButtonMenu instance.
// This function allocates resources for an ImeButtonMenu instance.
HRESULT TipLangBarMenuButton::Init(HINSTANCE instance,
UINT string_id,
const TipLangBarMenuItem* menu,
int count,
UINT menu_icon_id_for_non_theme,
UINT menu_icon_id_for_theme) {
menu_icon_id_for_theme_ = menu_icon_id_for_theme;
menu_icon_id_for_non_theme_ = menu_icon_id_for_non_theme;
return TipLangBarButton::Init(instance, string_id, menu, count);
}
TipLangBarToggleButton::TipLangBarToggleButton(
TipLangBarCallback *langbar_callback,
const GUID &guid,
bool is_menu,
bool show_in_tray)
: TipLangBarButton(langbar_callback, guid, is_menu, show_in_tray),
menu_selected_(0),
disabled_(false) {
}
TipLangBarToggleButton::~TipLangBarToggleButton() {
}
// Implements the IUnknown::QueryInterface() function.
// This function is used by Windows to retrieve the interfaces implemented by
// this class.
STDMETHODIMP TipLangBarToggleButton::QueryInterface(
REFIID interface_id, void** object) {
if (!object) {
return E_INVALIDARG;
}
if (::IsEqualIID(interface_id, __uuidof(IMozcLangBarToggleItem))) {
*object = static_cast<IMozcLangBarToggleItem *>(this);
AddRef();
return S_OK;
}
return QueryInterfaceBase(interface_id, object);
}
STDMETHODIMP_(ULONG) TipLangBarToggleButton::AddRef() {
return ref_count_.AddRefImpl();
}
STDMETHODIMP_(ULONG) TipLangBarToggleButton::Release() {
const ULONG count = ref_count_.ReleaseImpl();
if (count == 0) {
delete this;
}
return count;
}
// Implements the ITfLangBarItem::GetInfo() function.
// This function is called by Windows to update this button menu.
STDMETHODIMP TipLangBarToggleButton::GetInfo(TF_LANGBARITEMINFO *item_info) {
HRESULT result = S_OK;
if (item_info == nullptr) {
return E_INVALIDARG;
}
// Just copies the cached TF_LANGBARITEMINFO object.
*item_info = *this->item_info();
if (FAILED(result)) {
return result;
}
CIcon icon;
if (FAILED(GetIcon(&icon.m_hIcon)) || icon.IsNull()) {
return S_OK;
}
ICONINFO icon_info = {};
const BOOL get_icon_succeeded = icon.GetIconInfo(&icon_info);
CBitmap color(icon_info.hbmColor);
CBitmap mask(icon_info.hbmMask);
if (!get_icon_succeeded) {
return S_OK;
}
if (color.IsNull() && !mask.IsNull()) {
item_info->dwStyle |= TF_LBI_STYLE_TEXTCOLORICON;
return S_OK;
}
return S_OK;
}
STDMETHODIMP TipLangBarToggleButton::InitMenu(ITfMenu *menu) {
HRESULT result = S_OK;
// Do nothing if the langbar item is not menu botton.
if (!IsMenuButton()) {
return result;
}
// Add the menu items of this object to the given ITfMenu object.
for (size_t i = 0; i < menu_data_size(); ++i) {
const TipLangBarMenuData* data = menu_data(i);
result = menu->AddMenuItem(i, data->flags_, nullptr, nullptr,
data->text_, data->length_,
nullptr);
if (result != S_OK) {
break;
}
}
return result;
}
STDMETHODIMP TipLangBarToggleButton::OnMenuSelect(UINT menu_id) {
// Call the TipLangBarCallback::OnMenuSelect() function to dispatch the
// given event.
TipLangBarMenuData* data = menu_data(menu_id);
if (data == nullptr) {
return E_INVALIDARG;
}
if (data->item_id_ == TipLangBarCallback::kCancel) {
return S_OK;
}
const HRESULT result = langbar_callback_->OnMenuSelect(
static_cast<TipLangBarCallback::ItemId>(data->item_id_));
if (result != S_OK) {
return result;
}
TipLangBarMenuData* selected = menu_data(menu_selected_);
selected->flags_ &= ~TF_LBMENUF_RADIOCHECKED;
data->flags_ |= TF_LBMENUF_RADIOCHECKED;
menu_selected_ = menu_id;
return result;
}
STDMETHODIMP TipLangBarToggleButton::GetIcon(HICON* icon) {
if (icon == nullptr) {
return E_INVALIDARG;
}
// MSIME 2012 shows special icon when the LangBar item is disabled. Here we
// adopt this behavior for the consistency.
// TODO(yukawa): Refactor this design. We should split TipLangBarToggleButton
// into two different classes anyway.
const TipLangBarMenuData &data = !IsMenuButton() && disabled_
? menu_data_for_disabled_ : *menu_data(menu_selected_);
// Excerpt: http://msdn.microsoft.com/en-us/library/ms628718.aspx
// The caller must free this icon when it is no longer required by
// calling DestroyIcon.
*icon = LoadIconFromResource(TipDllModule::module_handle(),
data.icon_id_for_non_theme_,
data.icon_id_for_theme_);
return (*icon ? S_OK : E_FAIL);
}
HRESULT TipLangBarToggleButton::Init(
HINSTANCE instance,
int string_id,
const TipLangBarMenuItem* menu,
int count,
const TipLangBarMenuItem &menu_for_disabled) {
TipLangBarMenuDataArray array;
if (SUCCEEDED(array.Init(instance, &menu_for_disabled, 1))) {
menu_data_for_disabled_ = *array.data(0);
}
wchar_t buffer[arraysize(menu_data_for_disabled_.text_)];
::LoadString(instance, string_id, buffer, arraysize(buffer));
description_for_enabled_ = buffer;
return TipLangBarButton::Init(instance, string_id, menu, count);
}
// Implements the IMozcToggleButtonMenu::SelectMenuItem() function.
HRESULT TipLangBarToggleButton::SelectMenuItem(UINT menu_id) {
// Now SelectMenuItem may be called frequently to update LangbarItem for
// every key input. So we call TipLangBarButton::OnUpdate only if any item
// state is updated.
bool item_state_changed = false;
for (size_t i = 0; i < menu_data_size(); ++i) {
TipLangBarMenuData* data = menu_data(i);
if (data->item_id_ == menu_id) {
if ((data->flags_ & TF_LBMENUF_RADIOCHECKED) != 0 ||
menu_selected_ != static_cast<UINT>(i)) {
item_state_changed |= true;
}
data->flags_ |= TF_LBMENUF_RADIOCHECKED;
menu_selected_ = static_cast<UINT>(i);
} else {
if (data->flags_ != 0) {
item_state_changed |= true;
}
data->flags_ &= ~TF_LBMENUF_RADIOCHECKED;
}
}
if (item_state_changed) {
TipLangBarButton::OnUpdate(TF_LBI_ICON | TF_LBI_STATUS | TF_LBI_TEXT);
}
return S_OK;
}
HRESULT TipLangBarToggleButton::SetEnabled(bool enabled) {
disabled_ = !enabled;
// For menu-button, use parent implementation.
if (IsMenuButton()) {
return TipLangBarButton::SetEnabled(enabled);
}
// For button type element, adopt special behavior to be consistent with
// MSIME 2012's behavior.
// TODO(yukawa): Refactor this design. We should split TipLangBarToggleButton
// into two different classes anyway.
SetContextMenuEnabled(enabled);
if (enabled) {
const HRESULT result = SelectMenuItem(menu_data(menu_selected_)->item_id_);
SetDescription(description_for_enabled_);
TipLangBarButton::OnUpdate(TF_LBI_ICON | TF_LBI_STATUS | TF_LBI_TEXT);
return result;
}
SetDescription(menu_data_for_disabled_.text_);
TipLangBarButton::OnUpdate(TF_LBI_ICON | TF_LBI_STATUS | TF_LBI_TEXT);
return S_OK;
}
// Implements the constructor of the TipSystemLangBarMenu class.
TipSystemLangBarMenu::TipSystemLangBarMenu(TipLangBarCallback *langbar_callback,
const GUID &guid) {
// Save the TipLangBarCallback object who owns this button, and increase its
// reference count not to prevent the object from being deleted
langbar_callback_ = langbar_callback;
langbar_callback_->AddRef();
}
// Implements the destructor of the TipSystemLangBarMenu class.
TipSystemLangBarMenu::~TipSystemLangBarMenu() {
// Release the owner TipLangBarCallback object.
if (langbar_callback_ != nullptr) {
langbar_callback_->Release();
}
langbar_callback_ = nullptr;
}
// Implements the IUnknown::QueryInterface() function.
// This function is used by Windows to retrieve the interfaces implemented by
// this class.
STDMETHODIMP TipSystemLangBarMenu::QueryInterface(REFIID interface_id,
void **object) {
if (!object) {
return E_INVALIDARG;
}
// Find a matching interface from the ones implemented by this object
// (i.e. IUnknown, ITfSystemLangBarItemSink, ITfSource).
if (IsEqualIID(interface_id, IID_IUnknown) ||
IsEqualIID(interface_id, IID_ITfSystemLangBarItemSink)) {
*object = static_cast<ITfSystemLangBarItemSink*>(this);
AddRef();
return S_OK;
} else {
// This object does not implement the given interface.
*object = nullptr;
return E_NOINTERFACE;
}
}
STDMETHODIMP_(ULONG) TipSystemLangBarMenu::AddRef() {
return ref_count_.AddRefImpl();
}
STDMETHODIMP_(ULONG) TipSystemLangBarMenu::Release() {
const ULONG count = ref_count_.ReleaseImpl();
if (count == 0) {
delete this;
}
return count;
}
// Implements the ITfLangBarItemButton::InitMenu() function.
// This function is called by Windows to create the button menu.
STDMETHODIMP TipSystemLangBarMenu::InitMenu(ITfMenu *menu) {
HRESULT result = S_OK;
// Add the menu items of this object to the given ITfMenu object.
for (size_t i = 0; i < menu_data_.size(); ++i) {
const TipLangBarMenuData* data = menu_data_.data(i);
const UINT icon_id_for_theme =
TipLangBarButton::CanContextMenuDisplay32bppIcon()
? data->icon_id_for_theme_
: data->icon_id_for_non_theme_;
CBitmap bitmap;
CBitmap mask;
// If LoadIconAsBitmap fails, |bitmap.m_hBitmap| and |mask.m_hBitmap|
// remain nullptr bitmap handles.
LoadIconAsBitmap(TipDllModule::module_handle(),
data->icon_id_for_non_theme_,
icon_id_for_theme,
&bitmap.m_hBitmap,
&mask.m_hBitmap);
result = menu->AddMenuItem(i, data->flags_, bitmap, mask,
data->text_, data->length_,
nullptr);
if (result != S_OK) {
break;
}
}
return result;
}
// Implements the ITfLangBarItemButton::OnMenuSelect() function.
// This function is called by Windows to notify a user selects an item in
// this button menu.
STDMETHODIMP TipSystemLangBarMenu::OnMenuSelect(UINT menu_id) {
// Call the TipLangBarCallback::OnMenuSelect() function to dispatch the
// given event.
TipLangBarMenuData* data = menu_data_.data(menu_id);
if (data == nullptr) {
return E_INVALIDARG;
}
if (data->item_id_ == TipLangBarCallback::kCancel) {
return S_OK;
}
const HRESULT result = langbar_callback_->OnMenuSelect(
static_cast<TipLangBarCallback::ItemId>(data->item_id_));
return S_OK;
}
// Initializes an TipLangBarButton instance.
// This function is called by a text service to provide the information
// required for creating a menu button. A text service MUST call this function
// before calling the ITfLangBarItemMgr::AddItem() function and adding this
// button menu to a language bar.
HRESULT TipSystemLangBarMenu::Init(HINSTANCE instance,
const TipLangBarMenuItem *menu,
int count) {
return menu_data_.Init(instance, menu, count);
}
} // namespace tsf
} // namespace win32
} // namespace mozc