diff options
author | yusukes@google.com <yusukes@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-15 09:15:39 +0000 |
---|---|---|
committer | yusukes@google.com <yusukes@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-15 09:15:39 +0000 |
commit | 34fd3763e84b2347ab89ec5be7c7f73dac9ccb6a (patch) | |
tree | 1d9c85956a61bcf6012239a89c9ff1a317e9536f /chrome/browser | |
parent | fc12cfd609a24233288c6ab471929eb15cee3520 (diff) | |
download | chromium_src-34fd3763e84b2347ab89ec5be7c7f73dac9ccb6a.zip chromium_src-34fd3763e84b2347ab89ec5be7c7f73dac9ccb6a.tar.gz chromium_src-34fd3763e84b2347ab89ec5be7c7f73dac9ccb6a.tar.bz2 |
Implement "Language Switcher" for Chromium OS. This change enables users to switch their IME (input method) by clicking a menu button on the status area. Basic structure of the code is almost the same as power_menu_button and power_library.
Demo: http://dev.chromium.org/chromium-os/chromiumos-design-docs/text-input/demos
language_library.{cc,h}: UI-libcros glue. boilerplate code.
language_menu_button.{cc,h}: A button on the status area and its drop-down menu. Implements app/menus/menu_model.h interface.
status_area_view.{cc,h}: Put the language button on the status area.
BUG=494
TEST=Start Chromium OS. Click the menu button on the status area. Then verify that all IMEs you configured (via ibus-setup command, for now) is listed in the drop down menu.
Review URL: http://codereview.chromium.org/449050
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@34540 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/chromeos/language_library.cc | 100 | ||||
-rw-r--r-- | chrome/browser/chromeos/language_library.h | 86 | ||||
-rw-r--r-- | chrome/browser/chromeos/language_menu_button.cc | 241 | ||||
-rw-r--r-- | chrome/browser/chromeos/language_menu_button.h | 69 | ||||
-rwxr-xr-x | chrome/browser/chromeos/status_area_view.cc | 15 | ||||
-rw-r--r-- | chrome/browser/chromeos/status_area_view.h | 2 |
6 files changed, 511 insertions, 2 deletions
diff --git a/chrome/browser/chromeos/language_library.cc b/chrome/browser/chromeos/language_library.cc new file mode 100644 index 0000000..7db0a2b --- /dev/null +++ b/chrome/browser/chromeos/language_library.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/language_library.h" + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/chromeos/cros_library.h" + +// Allows InvokeLater without adding refcounting. This class is a Singleton and +// won't be deleted until it's last InvokeLater is run. +template <> +struct RunnableMethodTraits<chromeos::LanguageLibrary> { + void RetainCallee(chromeos::LanguageLibrary* obj) {} + void ReleaseCallee(chromeos::LanguageLibrary* obj) {} +}; + +namespace chromeos { + +LanguageLibrary::LanguageLibrary() : language_status_connection_(NULL) { + if (EnsureLoaded()) { + Init(); + } +} + +LanguageLibrary::~LanguageLibrary() { + if (EnsureLoaded()) { + chromeos::DisconnectLanguageStatus(language_status_connection_); + } +} + +LanguageLibrary::Observer::~Observer() { +} + +// static +LanguageLibrary* LanguageLibrary::Get() { + return Singleton<LanguageLibrary>::get(); +} + +// static +bool LanguageLibrary::EnsureLoaded() { + return CrosLibrary::EnsureLoaded(); +} + +void LanguageLibrary::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void LanguageLibrary::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +chromeos::InputLanguageList* LanguageLibrary::GetLanguages() { + chromeos::InputLanguageList* result = NULL; + if (EnsureLoaded()) { + result = chromeos::GetLanguages(language_status_connection_); + } + return result ? result : CreateFallbackInputLanguageList(); +} + +void LanguageLibrary::ChangeLanguage( + LanguageCategory category, const std::string& id) { + if (EnsureLoaded()) { + chromeos::ChangeLanguage(language_status_connection_, category, id.c_str()); + } +} + +// static +void LanguageLibrary::LanguageChangedHandler( + void* object, const chromeos::InputLanguage& current_language) { + LanguageLibrary* language_library = static_cast<LanguageLibrary*>(object); + language_library->UpdateCurrentLanguage(current_language); +} + +void LanguageLibrary::Init() { + language_status_connection_ = chromeos::MonitorLanguageStatus( + &LanguageChangedHandler, this); +} + +void LanguageLibrary::UpdateCurrentLanguage( + const chromeos::InputLanguage& current_language) { + // Make sure we run on UI thread. + if (!ChromeThread::CurrentlyOn(ChromeThread::UI)) { + DLOG(INFO) << "UpdateCurrentLanguage (Background thread)"; + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + // NewRunnableMethod() copies |current_language| by value. + NewRunnableMethod( + this, &LanguageLibrary::UpdateCurrentLanguage, current_language)); + return; + } + + DLOG(INFO) << "UpdateCurrentLanguage (UI thread)"; + current_language_ = current_language; + FOR_EACH_OBSERVER(Observer, observers_, LanguageChanged(this)); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/language_library.h b/chrome/browser/chromeos/language_library.h new file mode 100644 index 0000000..e79d159 --- /dev/null +++ b/chrome/browser/chromeos/language_library.h @@ -0,0 +1,86 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_LANGUAGE_LIBRARY_H_ +#define CHROME_BROWSER_CHROMEOS_LANGUAGE_LIBRARY_H_ + +#include <string> + +#include "base/observer_list.h" +#include "base/singleton.h" +#include "base/time.h" +#include "third_party/cros/chromeos_language.h" + +namespace chromeos { + +// This class handles the interaction with the ChromeOS language library APIs. +// Classes can add themselves as observers. Users can get an instance of this +// library class like this: LanguageLibrary::Get() +class LanguageLibrary { + public: + class Observer { + public: + virtual ~Observer() = 0; + virtual void LanguageChanged(LanguageLibrary* obj) = 0; + }; + + // This gets the singleton LanguageLibrary + static LanguageLibrary* Get(); + + // Makes sure the library is loaded, loading it if necessary. Returns true if + // the library has been successfully loaded. + static bool EnsureLoaded(); + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // Returns the list of IMEs and keyboard layouts we can select. If the cros + // library is not found or IBus/DBus daemon is not alive, this function + // returns a fallback language list (and never returns NULL). + InputLanguageList* GetLanguages(); + + // Changes the current IME engine to |id| and enable IME (when |category| + // is LANGUAGE_CATEGORY_IME). Changes the current XKB layout to |id| and + // disable IME (when |category| is LANGUAGE_CATEGORY_XKB). |id| is a unique + // identifier of a IME engine or XKB layout. Please check chromeos_language.h + // in src third_party/cros/ for details. + void ChangeLanguage(LanguageCategory category, const std::string& id); + + const InputLanguage& current_language() const { + return current_language_; + } + + private: + friend struct DefaultSingletonTraits<LanguageLibrary>; + + 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 methods starts the monitoring of language changes. + void Init(); + + // Called by the handler to update the language status. + // This will notify all the Observers. + void UpdateCurrentLanguage(const InputLanguage& current_language); + + // A reference to the language api, to allow callbacks when the language + // status changes. + LanguageStatusConnection* language_status_connection_; + ObserverList<Observer> observers_; + + // The language (IME or XKB layout) which currently selected. + InputLanguage current_language_; + + DISALLOW_COPY_AND_ASSIGN(LanguageLibrary); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_LANGUAGE_LIBRARY_H_ + diff --git a/chrome/browser/chromeos/language_menu_button.cc b/chrome/browser/chromeos/language_menu_button.cc new file mode 100644 index 0000000..87092cc --- /dev/null +++ b/chrome/browser/chromeos/language_menu_button.cc @@ -0,0 +1,241 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/language_menu_button.h" + +#include <string> + +#include "app/l10n_util.h" +#include "app/resource_bundle.h" +#include "base/time.h" +#include "chrome/browser/browser.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" + +namespace { + +const int kRadioGroupNone = 0; +const int kRadioGroupLanguage = 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. +std::string FormatInputLanguage( + const chromeos::InputLanguage& language, bool for_menu) { + std::string formatted = language.display_name; + if (formatted.empty()) { + formatted = language.id; + } + if (for_menu) { + switch (language.category) { + case chromeos::LANGUAGE_CATEGORY_XKB: + // TODO(yusukes): Use message catalog. + formatted += " (Layout)"; + break; + case chromeos::LANGUAGE_CATEGORY_IME: + // TODO(yusukes): Use message catalog. + formatted += " (IME)"; + break; + } + } else { + // For status area. Trim the string. + formatted = formatted.substr(0, kMaxLanguageNameLen); + // TODO(yusukes): Simple substr() does not work for non-ASCII string. + // TODO(yusukes): How can we ensure that the trimmed string does not + // overflow the area? + } + return formatted; +} + +} // namespace + +namespace chromeos { + +//////////////////////////////////////////////////////////////////////////////// +// LanguageMenuButton + +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. + ALLOW_THIS_IN_INITIALIZER_LIST(language_menu_(this)), + browser_(browser) { + DCHECK(language_list_.get() && !language_list_->empty()); + // Grab the real estate. + UpdateIcon(kSpacer); + // Display the default XKB name (usually "US"). + const std::string name = FormatInputLanguage(language_list_->at(0), false); + UpdateIcon(UTF8ToWide(name)); + LanguageLibrary::Get()->AddObserver(this); +} + +LanguageMenuButton::~LanguageMenuButton() { + LanguageLibrary::Get()->RemoveObserver(this); +} + +//////////////////////////////////////////////////////////////////////////////// +// LanguageMenuButton, menus::MenuModel implementation: + +int LanguageMenuButton::GetCommandIdAt(int index) const { + return index; // dummy +} + +bool LanguageMenuButton::IsLabelDynamicAt(int index) const { + // Menu content for the language button could change time by time. + return true; +} + +bool LanguageMenuButton::GetAcceleratorAt( + int index, menus::Accelerator* accelerator) const { + // Views for Chromium OS does not support accelerators yet. + return false; +} + +bool LanguageMenuButton::IsItemCheckedAt(int index) const { + DCHECK_GE(index, 0); + DCHECK(language_list_.get()); + if (static_cast<size_t>(index) < language_list_->size()) { + const InputLanguage& language = language_list_->at(index); + return language == LanguageLibrary::Get()->current_language(); + } + 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()) { + return kRadioGroupLanguage; + } + return kRadioGroupNone; +} + +bool LanguageMenuButton::HasIcons() const { + // TODO(yusukes): Display IME icons. + return false; +} + +bool LanguageMenuButton::GetIconAt(int index, SkBitmap* icon) const { + return false; +} + +bool LanguageMenuButton::IsEnabledAt(int index) const { + // Just return true so all IMEs and XLB layouts listed could be clicked. + return true; +} + +menus::MenuModel* LanguageMenuButton::GetSubmenuModelAt(int index) const { + return NULL; +} + +void LanguageMenuButton::HighlightChangedTo(int index) { + // Views for Chromium OS does not support this interface yet. +} + +void LanguageMenuButton::MenuWillShow() { + // Views for Chromium OS does not support this interface yet. +} + +int LanguageMenuButton::GetItemCount() const { + DCHECK(language_list_.get()); + if (language_list_->empty()) { + return 1; // no separator; "Configure IME" only + } + return language_list_->size() + 2; // separator + "Configure IME" +} + +menus::MenuModel::ItemType LanguageMenuButton::GetTypeAt(int index) const { + DCHECK_GE(index, 0); + DCHECK(language_list_.get()); + if (IsIndexShowControlPanel(index, language_list_.get())) { + return menus::MenuModel::TYPE_COMMAND; // "Configure IME" + } + if (static_cast<size_t>(index) < language_list_->size()) { + return menus::MenuModel::TYPE_RADIO; + } + + DCHECK_EQ(static_cast<size_t>(index), language_list_->size()); + 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())) { + // 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); + } + NOTREACHED(); + return WideToUTF16(L""); +} + +void LanguageMenuButton::ActivatedAt(int index) { + DCHECK_GE(index, 0); + DCHECK(language_list_.get()); + if (IsIndexShowControlPanel(index, language_list_.get())) { + browser_->ShowControlPanel(); + return; + } + if (static_cast<size_t>(index) < language_list_->size()) { + const InputLanguage& language = language_list_->at(index); + LanguageLibrary::Get()->ChangeLanguage(language.category, language.id); + return; + } + NOTREACHED(); +} + +//////////////////////////////////////////////////////////////////////////////// +// LanguageMenuButton, views::ViewMenuDelegate implementation: + +void LanguageMenuButton::RunMenu(views::View* source, const gfx::Point& pt) { + language_list_.reset(LanguageLibrary::Get()->GetLanguages()); + language_menu_.Rebuild(); + language_menu_.UpdateStates(); + language_menu_.RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT); +} + +//////////////////////////////////////////////////////////////////////////////// +// LanguageMenuButton, PowerLibrary::Observer implementation: + +void LanguageMenuButton::LanguageChanged(LanguageLibrary* obj) { + const std::string name = FormatInputLanguage(obj->current_language(), false); + UpdateIcon(UTF8ToWide(name)); +} + +void LanguageMenuButton::UpdateIcon(const std::wstring& name) { + set_border(NULL); + SetFont(ResourceBundle::GetSharedInstance().GetFont( + ResourceBundle::BaseFont).DeriveFont(0, gfx::Font::BOLD)); + SetEnabledColor(SK_ColorWHITE); + SetShowHighlighted(false); + SetText(name); + // TODO(yusukes): Show icon on the status area? + set_alignment(TextButton::ALIGN_RIGHT); + SchedulePaint(); +} + +// 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 new file mode 100644 index 0000000..f1ebfbc --- /dev/null +++ b/chrome/browser/chromeos/language_menu_button.h @@ -0,0 +1,69 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_LANGUAGE_MENU_BUTTON_H_ +#define CHROME_BROWSER_CHROMEOS_LANGUAGE_MENU_BUTTON_H_ + +#include "chrome/browser/chromeos/language_library.h" +#include "chrome/browser/chromeos/status_area_button.h" +#include "views/controls/menu/menu_2.h" +#include "views/controls/menu/view_menu_delegate.h" + +class Browser; +class SkBitmap; + +namespace chromeos { + +// The language menu button in the status area. +// This class will handle getting the IME/XKB status and populating the menu. +class LanguageMenuButton : public views::MenuButton, + public views::ViewMenuDelegate, + public menus::MenuModel, + public LanguageLibrary::Observer { + public: + explicit LanguageMenuButton(Browser* browser); + virtual ~LanguageMenuButton(); + + // views::Menu2Model implementation. + virtual bool HasIcons() const; + virtual int GetItemCount() const; + virtual menus::MenuModel::ItemType GetTypeAt(int index) const; + virtual int GetCommandIdAt(int index) const; + virtual string16 GetLabelAt(int index) const; + virtual bool IsLabelDynamicAt(int index) const; + virtual bool GetAcceleratorAt(int index, + menus::Accelerator* accelerator) const; + virtual bool IsItemCheckedAt(int index) const; + virtual int GetGroupIdAt(int index) const; + virtual bool GetIconAt(int index, SkBitmap* icon) const; + virtual bool IsEnabledAt(int index) const; + virtual menus::MenuModel* GetSubmenuModelAt(int index) const; + virtual void HighlightChangedTo(int index); + virtual void ActivatedAt(int index); + virtual void MenuWillShow(); + + // LanguageLibrary::Observer implementation. + virtual void LanguageChanged(LanguageLibrary* obj); + + private: + // views::ViewMenuDelegate implementation. + virtual void RunMenu(views::View* source, const gfx::Point& pt); + + // Update the status area with |name|. + void UpdateIcon(const std::wstring& name); + + // The current language list. + scoped_ptr<InputLanguageList> language_list_; + + // The language menu. + views::Menu2 language_menu_; + // The browser window that owns us. + Browser* browser_; + + DISALLOW_COPY_AND_ASSIGN(LanguageMenuButton); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_LANGUAGE_MENU_BUTTON_H_ diff --git a/chrome/browser/chromeos/status_area_view.cc b/chrome/browser/chromeos/status_area_view.cc index f3ba39d..bb58e11 100755 --- a/chrome/browser/chromeos/status_area_view.cc +++ b/chrome/browser/chromeos/status_area_view.cc @@ -14,6 +14,7 @@ #include "chrome/browser/browser.h" #include "chrome/browser/browser_window.h" #include "chrome/browser/chromeos/clock_menu_button.h" +#include "chrome/browser/chromeos/language_menu_button.h" #include "chrome/browser/chromeos/network_menu_button.h" #include "chrome/browser/chromeos/power_menu_button.h" #include "chrome/browser/chromeos/status_area_button.h" @@ -37,6 +38,9 @@ namespace chromeos { const int kLeftBorder = 1; // Number of pixels to separate the clock from the next item on the right. const int kClockSeparation = 4; +// Number of pixels to separate the language selector from the next item +// on the right. +const int kLanguageSeparation = 4; // BrowserWindowGtk tiles its image with this offset const int kCustomFrameBackgroundVerticalOffset = 15; @@ -126,6 +130,7 @@ StatusAreaView::StatusAreaView(Browser* browser, : browser_(browser), window_(window), clock_view_(NULL), + language_view_(NULL), network_view_(NULL), battery_view_(NULL), menu_view_(NULL) { @@ -134,6 +139,10 @@ StatusAreaView::StatusAreaView(Browser* browser, void StatusAreaView::Init() { ThemeProvider* theme = browser_->profile()->GetThemeProvider(); + // Language. + language_view_ = new LanguageMenuButton(browser_); + AddChildView(language_view_); + // Clock. clock_view_ = new ClockMenuButton(browser_); AddChildView(clock_view_); @@ -164,7 +173,7 @@ void StatusAreaView::Update() { gfx::Size StatusAreaView::GetPreferredSize() { // Start with padding. - int result_w = kLeftBorder + kClockSeparation; + int result_w = kLeftBorder + kClockSeparation + kLanguageSeparation; int result_h = 0; for (int i = 0; i < GetChildViewCount(); i++) { views::View* cur = GetChildViewAt(i); @@ -195,9 +204,11 @@ void StatusAreaView::Layout() { cur_x += cur_size.width(); - // Buttons have built in padding, but clock doesn't. + // Buttons have built in padding, but clock and language status don't. if (cur == clock_view_) cur_x += kClockSeparation; + else if (cur == language_view_) + cur_x += kLanguageSeparation; } } } diff --git a/chrome/browser/chromeos/status_area_view.h b/chrome/browser/chromeos/status_area_view.h index 95cfdf6..ee3a172 100644 --- a/chrome/browser/chromeos/status_area_view.h +++ b/chrome/browser/chromeos/status_area_view.h @@ -17,6 +17,7 @@ class Browser; namespace chromeos { class ClockMenuButton; +class LanguageMenuButton; class NetworkMenuButton; class PowerMenuButton; class StatusAreaButton; @@ -73,6 +74,7 @@ class StatusAreaView : public views::View, gfx::NativeWindow window_; ClockMenuButton* clock_view_; + LanguageMenuButton* language_view_; NetworkMenuButton* network_view_; PowerMenuButton* battery_view_; StatusAreaButton* menu_view_; |