diff options
author | yusukes@google.com <yusukes@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-01-20 04:11:25 +0000 |
---|---|---|
committer | yusukes@google.com <yusukes@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-01-20 04:11:25 +0000 |
commit | 269cba58576443c84e9e6134da52fe66dd3d9164 (patch) | |
tree | 3be568a45ba91d34ac3729d7fb0322af44b219ad | |
parent | 2dccf1b6351bbcdbea1a81a2563f1a5840a29507 (diff) | |
download | chromium_src-269cba58576443c84e9e6134da52fe66dd3d9164.zip chromium_src-269cba58576443c84e9e6134da52fe66dd3d9164.tar.gz chromium_src-269cba58576443c84e9e6134da52fe66dd3d9164.tar.bz2 |
Support intra-IME switcing (e.g. "Japanese Hiragana mode" to "Japanese HalfWidthKatakana mode").
Displays IME properties on the language menu.
BUG=none
TEST=manual
Review URL: http://codereview.chromium.org/517064
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@36607 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/chromeos/language_library.cc | 89 | ||||
-rw-r--r-- | chrome/browser/chromeos/language_library.h | 32 | ||||
-rw-r--r-- | chrome/browser/chromeos/language_menu_button.cc | 242 | ||||
-rw-r--r-- | chrome/browser/chromeos/language_menu_button.h | 28 |
4 files changed, 346 insertions, 45 deletions
diff --git a/chrome/browser/chromeos/language_library.cc b/chrome/browser/chromeos/language_library.cc index fcb0604..4d8899c 100644 --- a/chrome/browser/chromeos/language_library.cc +++ b/chrome/browser/chromeos/language_library.cc @@ -17,6 +17,28 @@ struct RunnableMethodTraits<chromeos::LanguageLibrary> { void ReleaseCallee(chromeos::LanguageLibrary* obj) {} }; +namespace { + +// Finds a property which has |new_prop.key| from |prop_list|, and replaces the +// property with |new_prop|. Returns true if such a property is found. +bool FindAndUpdateProperty(const chromeos::ImeProperty& new_prop, + chromeos::ImePropertyList* prop_list) { + for (size_t i = 0; i < prop_list->size(); ++i) { + chromeos::ImeProperty& prop = prop_list->at(i); + if (prop.key == new_prop.key) { + const int saved_id = prop.selection_item_id; + // Update the list except the radio id. As written in chromeos_language.h, + // |prop.selection_item_id| is dummy. + prop = new_prop; + prop.selection_item_id = saved_id; + return true; + } + } + return false; +} + +} // namespace + namespace chromeos { LanguageLibrary::LanguageLibrary() : language_status_connection_(NULL) { @@ -75,6 +97,22 @@ void LanguageLibrary::ChangeLanguage( } } +void LanguageLibrary::ActivateImeProperty(const std::string& key) { + DCHECK(!key.empty()); + if (EnsureLoaded()) { + chromeos::ActivateImeProperty( + language_status_connection_, key.c_str()); + } +} + +void LanguageLibrary::DeactivateImeProperty(const std::string& key) { + DCHECK(!key.empty()); + if (EnsureLoaded()) { + chromeos::DeactivateImeProperty( + language_status_connection_, key.c_str()); + } +} + bool LanguageLibrary::ActivateLanguage( LanguageCategory category, const std::string& id) { bool success = false; @@ -102,9 +140,27 @@ void LanguageLibrary::LanguageChangedHandler( language_library->UpdateCurrentLanguage(current_language); } +// static +void LanguageLibrary::RegisterPropertiesHandler( + void* object, const ImePropertyList& prop_list) { + LanguageLibrary* language_library = static_cast<LanguageLibrary*>(object); + language_library->RegisterProperties(prop_list); +} + +// static +void LanguageLibrary::UpdatePropertyHandler( + void* object, const ImePropertyList& prop_list) { + LanguageLibrary* language_library = static_cast<LanguageLibrary*>(object); + language_library->UpdateProperty(prop_list); +} + void LanguageLibrary::Init() { - language_status_connection_ = chromeos::MonitorLanguageStatus( - &LanguageChangedHandler, this); + chromeos::LanguageStatusMonitorFunctions monitor_functions; + monitor_functions.current_language = &LanguageChangedHandler; + monitor_functions.register_ime_properties = &RegisterPropertiesHandler; + monitor_functions.update_ime_property = &UpdatePropertyHandler; + language_status_connection_ + = chromeos::MonitorLanguageStatus(monitor_functions, this); } void LanguageLibrary::UpdateCurrentLanguage( @@ -125,4 +181,33 @@ void LanguageLibrary::UpdateCurrentLanguage( FOR_EACH_OBSERVER(Observer, observers_, LanguageChanged(this)); } +void LanguageLibrary::RegisterProperties(const ImePropertyList& prop_list) { + if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + this, &LanguageLibrary::RegisterProperties, prop_list)); + return; + } + + // |prop_list| might be empty. This means "clear all properties." + current_ime_properties_ = prop_list; + FOR_EACH_OBSERVER(Observer, observers_, ImePropertiesChanged(this)); +} + +void LanguageLibrary::UpdateProperty(const ImePropertyList& prop_list) { + if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + this, &LanguageLibrary::UpdateProperty, prop_list)); + return; + } + + for (size_t i = 0; i < prop_list.size(); ++i) { + FindAndUpdateProperty(prop_list[i], ¤t_ime_properties_); + } + FOR_EACH_OBSERVER(Observer, observers_, ImePropertiesChanged(this)); +} + } // namespace chromeos diff --git a/chrome/browser/chromeos/language_library.h b/chrome/browser/chromeos/language_library.h index 88c197c..f19c989 100644 --- a/chrome/browser/chromeos/language_library.h +++ b/chrome/browser/chromeos/language_library.h @@ -23,6 +23,7 @@ class LanguageLibrary { public: virtual ~Observer() = 0; virtual void LanguageChanged(LanguageLibrary* obj) = 0; + virtual void ImePropertiesChanged(LanguageLibrary* obj) = 0; }; // This gets the singleton LanguageLibrary @@ -54,6 +55,14 @@ class LanguageLibrary { // in src third_party/cros/ for details. void ChangeLanguage(LanguageCategory category, const std::string& id); + // Activates an IME property identified by |key|. Examples of keys are: + // "InputMode.Katakana", "InputMode.HalfWidthKatakana", "TypingMode.Romaji", + // and "TypingMode.Kana." + void ActivateImeProperty(const std::string& key); + + // Deactivates an IME property identified by |key|. + void DeactivateImeProperty(const std::string& key); + // Activates the language specified by |category| and |id|. Returns true // on success. bool ActivateLanguage(LanguageCategory category, const std::string& id); @@ -66,6 +75,10 @@ class LanguageLibrary { return current_language_; } + const ImePropertyList& current_ime_properties() const { + return current_ime_properties_; + } + private: friend struct DefaultSingletonTraits<LanguageLibrary>; @@ -73,10 +86,17 @@ class LanguageLibrary { ~LanguageLibrary(); // This method is called when there's a change in language status. - // This method is called on a background thread. static void LanguageChangedHandler( void* object, const InputLanguage& current_language); + // This method is called when an IME engine sends "RegisterProperties" signal. + static void RegisterPropertiesHandler( + void* object, const ImePropertyList& prop_list); + + // This method is called when an IME engine sends "UpdateProperty" signal. + static void UpdatePropertyHandler( + void* object, const ImePropertyList& prop_list); + // This methods starts the monitoring of language changes. void Init(); @@ -84,6 +104,12 @@ class LanguageLibrary { // This will notify all the Observers. void UpdateCurrentLanguage(const InputLanguage& current_language); + // Called by the handler to register IME properties. + void RegisterProperties(const ImePropertyList& prop_list); + + // Called by the handler to update IME properties. + void UpdateProperty(const ImePropertyList& prop_list); + // A reference to the language api, to allow callbacks when the language // status changes. LanguageStatusConnection* language_status_connection_; @@ -92,6 +118,10 @@ class LanguageLibrary { // The language (IME or XKB layout) which currently selected. InputLanguage current_language_; + // The IME properties which the current IME engine uses. The list might be + // empty when no IME is used. + ImePropertyList current_ime_properties_; + DISALLOW_COPY_AND_ASSIGN(LanguageLibrary); }; diff --git a/chrome/browser/chromeos/language_menu_button.cc b/chrome/browser/chromeos/language_menu_button.cc index 87092cc..38bad35 100644 --- a/chrome/browser/chromeos/language_menu_button.cc +++ b/chrome/browser/chromeos/language_menu_button.cc @@ -13,26 +13,59 @@ #include "grit/generated_resources.h" #include "grit/theme_resources.h" +// The language menu consists of 3 parts (in this order): +// +// (1) XKB layout names and IME languages names. The size of the list is +// always >= 1. +// (2) IME properties. This list might be empty. +// (3) "Configure IME..." button. +// +// Example of the menu (Japanese): +// +// ============================== (border of the popup window) +// [ ] US (|index| in the following functions is 0) +// [*] Anthy +// [ ] PinYin +// ------------------------------ (separator) +// [*] Hiragana (index = 5, The property has 2 radio groups) +// [ ] Katakana +// [ ] HalfWidthKatakana +// [*] Roman +// [ ] Kana +// ------------------------------ (separator) +// Configure IME... (index = 11) +// ============================== (border of the popup window) +// +// Example of the menu (Simplified Chinese): +// +// ============================== (border of the popup window) +// [ ] US +// [ ] Anthy +// [*] PinYin +// ------------------------------ (separator) +// Switch to full letter mode (The property has 2 command buttons) +// Switch to half punctuation mode +// ------------------------------ (separator) +// Configure IME... +// ============================== (border of the popup window) +// + namespace { -const int kRadioGroupNone = 0; -const int kRadioGroupLanguage = 1; +// Constants to specify the type of items in |model_|. +enum { + COMMAND_ID_LANGUAGES = 0, // US, Anthy, PinYin, ... + COMMAND_ID_IME_PROPERTIES, // Hiragana, Katakana, ... + COMMAND_ID_CONFIGURE_IME, // The "Configure IME..." button. +}; + +// A group ID for IME properties starts from 0. We use the huge value for the +// XKB/IME language list to avoid conflict. +const int kRadioGroupLanguage = 1 << 16; +const int kRadioGroupNone = -1; const size_t kMaxLanguageNameLen = 7; const wchar_t kSpacer[] = L"MMMMMMM"; -// Returns true if the |index| points to the "Configure IME" menu item. -bool IsIndexShowControlPanel( - int index, chromeos::InputLanguageList* language_list) { - DCHECK_GE(index, 0); - if (language_list->empty()) { - // If language_list is empty, then there's no separator. So "Configure IME" - // should be at index 0. - DCHECK_EQ(index, 0); - return index == 0; - } - return static_cast<size_t>(index) == (language_list->size() + 1); -} - // Converts chromeos::InputLanguage object into human readable string. Returns // a string for the drop-down menu if |for_menu| is true. Otherwise, returns a // string for the status area. @@ -73,11 +106,15 @@ namespace chromeos { LanguageMenuButton::LanguageMenuButton(Browser* browser) : MenuButton(NULL, std::wstring(), this, false), language_list_(LanguageLibrary::Get()->GetLanguages()), - // Since the constructor of |language_menu_| calls this->GetItemCount(), - // we have to initialize |language_list_| before hand. + model_(NULL), + // Be aware that the constructor of |language_menu_| calls GetItemCount() + // in this class. Therefore, GetItemCount() have to return 0 when + // |model_| is NULL. ALLOW_THIS_IN_INITIALIZER_LIST(language_menu_(this)), browser_(browser) { DCHECK(language_list_.get() && !language_list_->empty()); + // Update the model + RebuildModel(); // Grab the real estate. UpdateIcon(kSpacer); // Display the default XKB name (usually "US"). @@ -94,7 +131,7 @@ LanguageMenuButton::~LanguageMenuButton() { // LanguageMenuButton, menus::MenuModel implementation: int LanguageMenuButton::GetCommandIdAt(int index) const { - return index; // dummy + return 0; // dummy } bool LanguageMenuButton::IsLabelDynamicAt(int index) const { @@ -111,19 +148,35 @@ bool LanguageMenuButton::GetAcceleratorAt( bool LanguageMenuButton::IsItemCheckedAt(int index) const { DCHECK_GE(index, 0); DCHECK(language_list_.get()); - if (static_cast<size_t>(index) < language_list_->size()) { + + if (IndexIsInLanguageList(index)) { const InputLanguage& language = language_list_->at(index); return language == LanguageLibrary::Get()->current_language(); } + + if (GetPropertyIndex(index, &index)) { + const ImePropertyList& property_list + = LanguageLibrary::Get()->current_ime_properties(); + return property_list.at(index).is_selection_item_checked; + } + + // Separator(s) or the "Configure IME" button. return false; } int LanguageMenuButton::GetGroupIdAt(int index) const { DCHECK_GE(index, 0); - DCHECK(language_list_.get()); - if (static_cast<size_t>(index) < language_list_->size()) { + + if (IndexIsInLanguageList(index)) { return kRadioGroupLanguage; } + + if (GetPropertyIndex(index, &index)) { + const ImePropertyList& property_list + = LanguageLibrary::Get()->current_ime_properties(); + return property_list.at(index).selection_item_id; + } + return kRadioGroupNone; } @@ -133,15 +186,18 @@ bool LanguageMenuButton::HasIcons() const { } bool LanguageMenuButton::GetIconAt(int index, SkBitmap* icon) const { + // TODO(yusukes): Display IME icons. return false; } bool LanguageMenuButton::IsEnabledAt(int index) const { - // Just return true so all IMEs and XLB layouts listed could be clicked. + // Just return true so all IMEs, XKB layouts, and IME properties could be + // clicked. return true; } menus::MenuModel* LanguageMenuButton::GetSubmenuModelAt(int index) const { + // We don't use nested menus. return NULL; } @@ -154,54 +210,100 @@ void LanguageMenuButton::MenuWillShow() { } int LanguageMenuButton::GetItemCount() const { - DCHECK(language_list_.get()); - if (language_list_->empty()) { - return 1; // no separator; "Configure IME" only + if (!model_.get()) { + // Model is not constructed yet. This means that LanguageMenuButton is + // being constructed. Return zero. + return 0; } - return language_list_->size() + 2; // separator + "Configure IME" + return model_->GetItemCount(); } menus::MenuModel::ItemType LanguageMenuButton::GetTypeAt(int index) const { DCHECK_GE(index, 0); - DCHECK(language_list_.get()); - if (IsIndexShowControlPanel(index, language_list_.get())) { + + if (IndexPointsToConfigureImeMenuItem(index)) { return menus::MenuModel::TYPE_COMMAND; // "Configure IME" } - if (static_cast<size_t>(index) < language_list_->size()) { + + if (IndexIsInLanguageList(index)) { return menus::MenuModel::TYPE_RADIO; } - DCHECK_EQ(static_cast<size_t>(index), language_list_->size()); + if (GetPropertyIndex(index, &index)) { + const ImePropertyList& property_list + = LanguageLibrary::Get()->current_ime_properties(); + if (property_list.at(index).is_selection_item) { + return menus::MenuModel::TYPE_RADIO; + } + return menus::MenuModel::TYPE_COMMAND; + } + return menus::MenuModel::TYPE_SEPARATOR; } string16 LanguageMenuButton::GetLabelAt(int index) const { DCHECK_GE(index, 0); DCHECK(language_list_.get()); - if (IsIndexShowControlPanel(index, language_list_.get())) { + + if (IndexPointsToConfigureImeMenuItem(index)) { // TODO(yusukes): Use message catalog. return WideToUTF16(L"Configure IME..."); } - if (static_cast<size_t>(index) < language_list_->size()) { - std::string name = FormatInputLanguage(language_list_->at(index), true); - return UTF8ToUTF16(name); + + std::string name; + if (IndexIsInLanguageList(index)) { + name = FormatInputLanguage(language_list_->at(index), true); + } else if (GetPropertyIndex(index, &index)) { + const ImePropertyList& property_list + = LanguageLibrary::Get()->current_ime_properties(); + name = property_list.at(index).label; } - NOTREACHED(); - return WideToUTF16(L""); + + return UTF8ToUTF16(name); } void LanguageMenuButton::ActivatedAt(int index) { DCHECK_GE(index, 0); DCHECK(language_list_.get()); - if (IsIndexShowControlPanel(index, language_list_.get())) { + + if (IndexPointsToConfigureImeMenuItem(index)) { browser_->ShowControlPanel(); return; } - if (static_cast<size_t>(index) < language_list_->size()) { + + if (IndexIsInLanguageList(index)) { + // Inter-IME switching or IME-XKB switching. const InputLanguage& language = language_list_->at(index); LanguageLibrary::Get()->ChangeLanguage(language.category, language.id); return; } + + if (GetPropertyIndex(index, &index)) { + // Intra-IME switching (e.g. Japanese-Hiragana to Japanese-Katakana). + const ImePropertyList& property_list + = LanguageLibrary::Get()->current_ime_properties(); + const std::string key = property_list.at(index).key; + if (property_list.at(index).is_selection_item) { + // Radio button is clicked. + const int id = property_list.at(index).selection_item_id; + // First, deactivate all other properties in the same radio group. + for (int i = 0; i < static_cast<int>(property_list.size()); ++i) { + if (i != index && id == property_list.at(i).selection_item_id) { + LanguageLibrary::Get()->DeactivateImeProperty( + property_list.at(i).key); + } + } + // Then, activate the property clicked. + LanguageLibrary::Get()->ActivateImeProperty(key); + } else { + // Command button like "Switch to half punctuation mode" is clicked. + // We can always use "Deactivate" for command buttons. + LanguageLibrary::Get()->DeactivateImeProperty(key); + } + return; + } + + // Separators are not clickable. NOTREACHED(); } @@ -210,19 +312,24 @@ void LanguageMenuButton::ActivatedAt(int index) { void LanguageMenuButton::RunMenu(views::View* source, const gfx::Point& pt) { language_list_.reset(LanguageLibrary::Get()->GetLanguages()); + RebuildModel(); language_menu_.Rebuild(); language_menu_.UpdateStates(); language_menu_.RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT); } //////////////////////////////////////////////////////////////////////////////// -// LanguageMenuButton, PowerLibrary::Observer implementation: +// LanguageLibrary::Observer implementation: void LanguageMenuButton::LanguageChanged(LanguageLibrary* obj) { const std::string name = FormatInputLanguage(obj->current_language(), false); UpdateIcon(UTF8ToWide(name)); } +void LanguageMenuButton::ImePropertiesChanged(LanguageLibrary* obj) { + RebuildModel(); +} + void LanguageMenuButton::UpdateIcon(const std::wstring& name) { set_border(NULL); SetFont(ResourceBundle::GetSharedInstance().GetFont( @@ -235,7 +342,62 @@ void LanguageMenuButton::UpdateIcon(const std::wstring& name) { SchedulePaint(); } +void LanguageMenuButton::RebuildModel() { + model_.reset(new menus::SimpleMenuModel(NULL)); + string16 dummy_label = UTF8ToUTF16(""); + + // We "abuse" the command_id and group_id arguments of AddRadioItem method. + // A COMMAND_ID_XXX enum value is passed as command_id, and array index of + // |language_list_| or |property_list| is passed as group_id. + for (size_t i = 0; i < language_list_->size(); ++i) { + model_->AddRadioItem(COMMAND_ID_LANGUAGES, dummy_label, i); + } + model_->AddSeparator(); + + const ImePropertyList& property_list + = LanguageLibrary::Get()->current_ime_properties(); + for (size_t i = 0; i < property_list.size(); ++i) { + model_->AddRadioItem(COMMAND_ID_IME_PROPERTIES, dummy_label, i); + } + if (!property_list.empty()) { + model_->AddSeparator(); + } + + // Note: We use AddSeparator() for separators, and AddRadioItem() for all + // other items even if an item is not actually a radio item. + model_->AddRadioItem(COMMAND_ID_CONFIGURE_IME, dummy_label, 0 /* dummy */); +} + +bool LanguageMenuButton::IndexIsInLanguageList(int index) const { + DCHECK_GE(index, 0); + DCHECK(model_.get()); + + return ((model_->GetTypeAt(index) == menus::MenuModel::TYPE_RADIO) && + (model_->GetCommandIdAt(index) == COMMAND_ID_LANGUAGES)); +} + +bool LanguageMenuButton::GetPropertyIndex( + int index, int* property_index) const { + DCHECK_GE(index, 0); + DCHECK(property_index); + DCHECK(model_.get()); + + if ((model_->GetTypeAt(index) == menus::MenuModel::TYPE_RADIO) && + (model_->GetCommandIdAt(index) == COMMAND_ID_IME_PROPERTIES)) { + *property_index = model_->GetGroupIdAt(index); + return true; + } + return false; +} + +bool LanguageMenuButton::IndexPointsToConfigureImeMenuItem(int index) const { + DCHECK_GE(index, 0); + DCHECK(model_.get()); + + return ((model_->GetTypeAt(index) == menus::MenuModel::TYPE_RADIO) && + (model_->GetCommandIdAt(index) == COMMAND_ID_CONFIGURE_IME)); +} + // TODO(yusukes): Register and handle hotkeys for IME and XKB switching? } // namespace chromeos - diff --git a/chrome/browser/chromeos/language_menu_button.h b/chrome/browser/chromeos/language_menu_button.h index f1ebfbc..6d74a48 100644 --- a/chrome/browser/chromeos/language_menu_button.h +++ b/chrome/browser/chromeos/language_menu_button.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_CHROMEOS_LANGUAGE_MENU_BUTTON_H_ #define CHROME_BROWSER_CHROMEOS_LANGUAGE_MENU_BUTTON_H_ +#include "app/menus/simple_menu_model.h" #include "chrome/browser/chromeos/language_library.h" #include "chrome/browser/chromeos/status_area_button.h" #include "views/controls/menu/menu_2.h" @@ -25,7 +26,7 @@ class LanguageMenuButton : public views::MenuButton, explicit LanguageMenuButton(Browser* browser); virtual ~LanguageMenuButton(); - // views::Menu2Model implementation. + // menus::MenuModel implementation. virtual bool HasIcons() const; virtual int GetItemCount() const; virtual menus::MenuModel::ItemType GetTypeAt(int index) const; @@ -45,6 +46,7 @@ class LanguageMenuButton : public views::MenuButton, // LanguageLibrary::Observer implementation. virtual void LanguageChanged(LanguageLibrary* obj); + virtual void ImePropertiesChanged(LanguageLibrary* obj); private: // views::ViewMenuDelegate implementation. @@ -53,10 +55,32 @@ class LanguageMenuButton : public views::MenuButton, // Update the status area with |name|. void UpdateIcon(const std::wstring& name); + // Rebuilds |model_|. This function should be called whenever |language_list_| + // is updated, or ImePropertiesChanged() is called. + void RebuildModel(); + + // Returns true if the zero-origin |index| points to one of the input + // languages. + bool IndexIsInLanguageList(int index) const; + + // Returns true if the zero-origin |index| points to one of the IME + // properties. When returning true, |property_index| is updated so that + // property_list.at(property_index) points to the menu item. + bool GetPropertyIndex(int index, int* property_index) const; + + // Returns true if the zero-origin |index| points to the "Configure IME" menu + // item. + bool IndexPointsToConfigureImeMenuItem(int index) const; + // The current language list. scoped_ptr<InputLanguageList> language_list_; - // The language menu. + // We borrow menus::SimpleMenuModel implementation to maintain the current + // content of the pop-up menu. The menus::MenuModel is implemented using this + // |model_|. + scoped_ptr<menus::SimpleMenuModel> model_; + + // The language menu which pops up when the button in status area is clicked. views::Menu2 language_menu_; // The browser window that owns us. Browser* browser_; |