Index: chrome/browser/chromeos/text_input/candidate_window.cc |
diff --git a/chrome/browser/chromeos/text_input/candidate_window.cc b/chrome/browser/chromeos/text_input/candidate_window.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c7cc055988fde5cbddd5bbaf9de493b177117722 |
--- /dev/null |
+++ b/chrome/browser/chromeos/text_input/candidate_window.cc |
@@ -0,0 +1,948 @@ |
+// 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. |
+// |
+// TODO(satorux): |
+// - Implement a horizontal candidate window. |
+// - Implement a scroll bar or an indicator showing where you are in the |
+// candidate window. |
+ |
+#include <algorithm> |
+#include <string> |
+#include <vector> |
+ |
+#include "app/app_paths.h" |
+#include "app/gfx/canvas.h" |
+#include "app/gfx/font.h" |
+#include "app/resource_bundle.h" |
+#include "base/at_exit.h" |
+#include "base/file_path.h" |
+#include "base/observer_list.h" |
+#include "base/path_service.h" |
+#include "base/process_util.h" |
+#include "base/scoped_ptr.h" |
+#include "base/string_util.h" |
+#include "chrome/browser/chromeos/cros/cros_library.h" |
+#include "chrome/common/chrome_paths.h" |
+#include "third_party/cros/chromeos_cros_api.h" |
+#include "third_party/cros/chromeos_ime.h" |
+#include "views/controls/label.h" |
+#include "views/controls/textfield/textfield.h" |
+#include "views/event.h" |
+#include "views/fill_layout.h" |
+#include "views/focus/accelerator_handler.h" |
+#include "views/grid_layout.h" |
+#include "views/screen.h" |
+#include "views/widget/root_view.h" |
+#include "views/widget/widget.h" |
+#include "views/widget/widget_gtk.h" |
+#include "views/window/non_client_view.h" |
+#include "views/window/window.h" |
+#include "views/window/window_delegate.h" |
+ |
+namespace { |
+ |
+// Colors used in the candidate window UI. |
+const SkColor kFrameColor = SkColorSetRGB(0x96, 0x96, 0x96); |
+const SkColor kShortcutBackgroundColor = SkColorSetARGB(0x10, 0x3, 0x4, 0xf); |
+const SkColor kSelectedRowBackgroundColor = SkColorSetRGB(0xd1, 0xea, 0xff); |
+const SkColor kDefaultBackgroundColor = SkColorSetRGB(0xff, 0xff, 0xff); |
+const SkColor kSelectedRowFrameColor = SkColorSetRGB(0x7f, 0xac, 0xdd); |
+const SkColor kFooterTopColor = SkColorSetRGB(0xff, 0xff, 0xff); |
+const SkColor kFooterBottomColor = SkColorSetRGB(0xee, 0xee, 0xee); |
+const SkColor kShortcutColor = SkColorSetRGB(0x61, 0x61, 0x61); |
+const SkColor kDisabledShortcutColor = SkColorSetRGB(0xcc, 0xcc, 0xcc); |
+ |
+// The minimum width of candidate labels in the vertical candidate |
+// window. We use this value to prevent the candidate window from being |
+// too narrow when all candidates are short. |
+const int kMinCandidateLabelWidth = 100; |
+ |
+// Wraps the given view with some padding, and returns it. |
+views::View* WrapWithPadding(views::View* view, const gfx::Insets& insets) { |
+ views::View* wrapper = new views::View; |
+ // Use GridLayout to give some insets inside. |
+ views::GridLayout* layout = new views::GridLayout(wrapper); |
+ wrapper->SetLayoutManager(layout); // |wrapper| owns |layout|. |
+ layout->SetInsets(insets); |
+ |
+ views::ColumnSet* column_set = layout->AddColumnSet(0); |
+ column_set->AddColumn( |
+ views::GridLayout::FILL, views::GridLayout::FILL, |
+ 1, views::GridLayout::USE_PREF, 0, 0); |
+ layout->StartRow(0, 0); |
+ |
+ // Add the view contents. |
+ layout->AddView(view); // |view| is owned by |wraper|, not |layout|. |
+ return wrapper; |
+} |
+ |
+} // namespace |
+ |
+namespace chromeos { |
+ |
+class CandidateView; |
+ |
+// CandidateWindowView is the main container of the candidate window UI. |
+class CandidateWindowView : public views::View { |
+ public: |
+ // Should we show candidates vertically or horizontally? |
+ enum Orientation { |
+ kVertical, |
+ kHorizontal, |
+ }; |
+ |
+ // The object can be monitored by the observer. |
+ class Observer { |
+ public: |
+ virtual ~Observer() {} |
+ // The function is called when a candidate is committed. |
+ // See comments at NotifyCandidateClicke() in chromeos_ime.h for |
+ // details about the parameters. |
+ virtual void OnCandidateCommitted(int index, int button, int flag) = 0; |
+ }; |
+ |
+ explicit CandidateWindowView(views::Widget* parent_frame); |
+ virtual ~CandidateWindowView() {} |
+ void Init(); |
+ |
+ // Adds the given observer. The ownership is not transferred. |
+ void AddObserver(Observer* observer) { |
+ observers_.AddObserver(observer); |
+ } |
+ |
+ // Removes the given observer. |
+ void RemoveObserver(Observer* observer) { |
+ observers_.RemoveObserver(observer); |
+ } |
+ |
+ // Selects the candidate specified by the index in the current page |
+ // (zero-origin). Changes the appearance of the selected candidate, |
+ // updates the information in the candidate window as needed. |
+ void SelectCandidateAt(int index_in_page); |
+ |
+ // The function is called when a candidate is being dragged. From the |
+ // given point, locates the candidate under the mouse cursor, and |
+ // selects it. |
+ void OnCandidateDragged(const gfx::Point& point); |
+ |
+ // Commits the candidate currently being selected. |
+ void CommitCandidate(); |
+ |
+ // Hides the auxiliary text. |
+ void HideAuxiliaryText(); |
+ |
+ // Shows the auxiliary text. |
+ void ShowAuxiliaryText(); |
+ |
+ // Updates the auxiliary text. |
+ void UpdateAuxiliaryText(const std::string& utf8_text); |
+ |
+ // Updates candidates of the candidate window from |lookup_table|. |
+ void UpdateCandidates(const ImeLookupTable& lookup_table); |
+ |
+ private: |
+ // Initializes the candidate views if needed. |
+ void MaybeInitializeCandidateViews(int num_views, |
+ Orientation orientation); |
+ |
+ // Creates the footer area, where we show status information. |
+ // For instance, we show a cursor position like 2/19. |
+ views::View* CreateFooterArea(); |
+ |
+ // Creates the header area, where we show auxiliary text. |
+ views::View* CreateHeaderArea(); |
+ |
+ // Resizes the parent frame and schedules painting. This needs to be |
+ // called when the visible contents of the candidate window are |
+ // modified. |
+ void ResizeAndSchedulePaint(); |
+ |
+ // The orientation of the candidate window. |
+ Orientation orientation_; |
+ |
+ // The lookup table (candidates). |
+ ImeLookupTable lookup_table_; |
+ // The index in the current page of the candidate currently being selected. |
+ int selected_candidate_index_in_page_; |
+ // The observers of the object. |
+ ObserverList<Observer> observers_; |
+ |
+ // The parent frame. |
+ views::Widget* parent_frame_; |
+ |
+ // Views created in the class will be part of tree of |this|, so these |
+ // child views will be deleted when |this| is deleted. |
+ |
+ // The candidate area is where candidates are rendered. |
+ views::View* candidate_area_; |
+ // The footer area is where some status messages are shown if needed. |
+ views::View* footer_area_; |
+ // We use this when we show something in the footer area. |
+ scoped_ptr<views::View> footer_area_contents_; |
+ // We use this when we show nothing in the footer area. |
+ scoped_ptr<views::View> footer_area_place_holder_; |
+ // The header area is where the auxiliary text is shown, if the |
+ // auxiliary text is provided. If it is not provided, we show nothing. |
+ // For instance, we show pinyin text like "zhong'guo", but we show |
+ // nothing with Japanese IME. |
+ views::View* header_area_; |
+ // We use this when we show something in the header area. |
+ scoped_ptr<views::View> header_area_contents_; |
+ // We use this when we show nothing in the header area. |
+ scoped_ptr<views::View> header_area_place_holder_; |
+ // The candidate views are used for rendering candidates. |
+ std::vector<CandidateView*> candidate_views_; |
+ // The auxiliary text label is shown in the auxiliary text area. |
+ views::Label* auxiliary_text_label_; |
+ // The footer label is shown in the footer area. |
+ views::Label* footer_label_; |
+}; |
+ |
+// CandidateRow renderes a row of a candidate. |
+class CandidateView : public views::View { |
+ public: |
+ CandidateView(CandidateWindowView* parent_candidate_window, |
+ int index_in_page, |
+ CandidateWindowView::Orientation orientation); |
+ virtual ~CandidateView() {} |
+ void Init(); |
+ |
+ // Sets candidate text with the given text. |
+ void SetCandidateText(const std::wstring& text); |
+ |
+ // Selects the candidate row. Changes the appearance to make it look |
+ // like a selected candidate. |
+ void Select(); |
+ |
+ // Unselects the candidate row. Changes the appearance to make it look |
+ // like an unselected candidate. |
+ void Unselect(); |
+ |
+ // Disables the candidate row. Changes the appearance to make it look |
+ // like unclickable area. |
+ void Disable(); |
+ |
+ private: |
+ // View::OnMousePressed() implementation. |
+ virtual bool OnMousePressed(const views::MouseEvent& event); |
+ |
+ // View::OnMouseDragged() implementation. |
+ virtual bool OnMouseDragged(const views::MouseEvent& event); |
+ |
+ // View::OnMouseReleased() implementation. |
+ virtual void OnMouseReleased(const views::MouseEvent& event, |
+ bool canceled); |
+ |
+ // Zero-origin index in the current page. |
+ int index_in_page_; |
+ |
+ // The orientation of the candidate view. |
+ CandidateWindowView::Orientation orientation_; |
+ |
+ // The parent candidate window that contains this view. |
+ CandidateWindowView* parent_candidate_window_; |
+ |
+ // Views created in the class will be part of tree of |this|, so these |
+ // child views will be deleted when |this| is deleted. |
+ |
+ // The shortcut label renders shortcut numbers like 1, 2, and 3. |
+ views::Label* shortcut_label_; |
+ // The candidate label renders candidates. |
+ views::Label* candidate_label_; |
+}; |
+ |
+// VerticalCandidateLabel is used for rendering candidate text in |
+// the vertical candidate window. |
+class VerticalCandidateLabel : public views::Label { |
+ virtual ~VerticalCandidateLabel() {} |
+ |
+ // Returns the preferred size, but guarantees that the width has at |
+ // least kMinCandidateLabelWidth pixels. |
+ virtual gfx::Size GetPreferredSize() { |
+ gfx::Size size = Label::GetPreferredSize(); |
+ // Hack. +2 is needed to prevent labels from getting elided like |
+ // "abc..." in some cases. TODO(satorux): Figure out why it's |
+ // necessary. |
+ size.set_width(size.width() + 2); |
+ if (size.width() < kMinCandidateLabelWidth) { |
+ size.set_width(kMinCandidateLabelWidth); |
+ } |
+ return size; |
+ } |
+}; |
+ |
+// CandidateWindowController controls the CandidateWindow. |
+class CandidateWindowController : public CandidateWindowView::Observer { |
+ public: |
+ CandidateWindowController(); |
+ virtual ~CandidateWindowController(); |
+ void Init(); |
+ |
+ // Returns the work area of the monitor nearest the candidate window. |
+ gfx::Rect GetMonitorWorkAreaNearestWindow(); |
+ |
+ // CandidateWindowView::Observer implementation. |
+ virtual void OnCandidateCommitted(int index, |
+ int button, |
+ int flags); |
+ |
+ private: |
+ // Creates the candidate window view. |
+ void CreateView(); |
+ |
+ // The function is called when |HideAuxiliaryText| signal is received in |
+ // libcros. |ime_library| is a void pointer to this object. |
+ static void OnHideAuxiliaryText(void* ime_library); |
+ |
+ // The function is called when |HideLookupTable| signal is received in |
+ // libcros. |ime_library| is a void pointer to this object. |
+ static void OnHideLookupTable(void* ime_library); |
+ |
+ // The function is called when |SetCursorLocation| signal is received |
+ // in libcros. |ime_library| is a void pointer to this object. |
+ static void OnSetCursorLocation(void* ime_library, |
+ int x, |
+ int y, |
+ int width, |
+ int height); |
+ |
+ // The function is called when |UpdateAuxiliaryText| signal is received |
+ // in libcros. |ime_library| is a void pointer to this object. |
+ static void OnUpdateAuxiliaryText(void* ime_library, |
+ const std::string& utf8_text, |
+ bool visible); |
+ |
+ // The function is called when |UpdateLookupTable| signal is received |
+ // in libcros. |ime_library| is a void pointer to this object. |
+ static void OnUpdateLookupTable(void* ime_library, |
+ const ImeLookupTable& lookup_table); |
+ |
+ // The connection is used for communicating with IME code in libcros. |
+ ImeStatusConnection* ime_status_connection_; |
+ |
+ // The candidate window view. |
+ CandidateWindowView* candidate_window_; |
+ |
+ // This is the outer frame of the candidate window view. The frame will |
+ // own |candidate_window_|. |
+ scoped_ptr<views::Widget> frame_; |
+}; |
+ |
+CandidateView::CandidateView( |
+ CandidateWindowView* parent_candidate_window, |
+ int index_in_page, |
+ CandidateWindowView::Orientation orientation) |
+ : index_in_page_(index_in_page), |
+ orientation_(orientation), |
+ parent_candidate_window_(parent_candidate_window), |
+ shortcut_label_(NULL), |
+ candidate_label_(NULL) { |
+} |
+ |
+void CandidateView::Init() { |
+ views::GridLayout* layout = new views::GridLayout(this); |
+ SetLayoutManager(layout); // |this| owns |layout|. |
+ |
+ // Choose the character used for the shortcut label. |
+ const wchar_t kShortcutCharacters[] = L"1234567890ABCDEF"; |
+ // The default character should not be used but just in case. |
+ wchar_t shortcut_character = L'?'; |
+ if (index_in_page_ < static_cast<int>(arraysize(kShortcutCharacters) - 1)) { |
+ shortcut_character = kShortcutCharacters[index_in_page_]; |
+ } |
+ |
+ // Create the shortcut label. The label will eventually be part of the |
+ // tree of |this| via |wrapped_shortcut_label|, hence it's deleted when |
+ // |this| is deleted. |
+ shortcut_label_ = new views::Label( |
+ StringPrintf(L"%lc", shortcut_character)); |
+ |
+ // Wrap it with padding. |
+ const gfx::Insets kVerticalShortcutLabelInsets(1, 6, 1, 6); |
+ const gfx::Insets kHorizontalShortcutLabelInsets(1, 1, 1, 1); |
+ const gfx::Insets insets = (orientation_ == CandidateWindowView::kVertical ? |
+ kVerticalShortcutLabelInsets : |
+ kHorizontalShortcutLabelInsets); |
+ views::View* wrapped_shortcut_label = |
+ WrapWithPadding(shortcut_label_, insets); |
+ // Make the font bold. |
+ gfx::Font font = shortcut_label_->GetFont(); |
+ gfx::Font bold_font = font.DeriveFont(0, gfx::Font::BOLD); |
+ shortcut_label_->SetFont(bold_font); |
+ // TODO(satorux): Maybe we need to use language specific fonts for |
+ // candidate_label, like Chinese font for Chinese IME? |
+ |
+ // Add decoration based on the orientation. |
+ if (orientation_ == CandidateWindowView::kVertical) { |
+ // Set the background color. |
+ wrapped_shortcut_label->set_background( |
+ views::Background::CreateSolidBackground( |
+ kShortcutBackgroundColor)); |
+ } |
+ shortcut_label_->SetColor(kShortcutColor); |
+ |
+ // Create the candidate label. The label will be added to |this| as a |
+ // child view, hence it's deleted when |this| is deleted. |
+ if (orientation_ == CandidateWindowView::kVertical) { |
+ candidate_label_ = new VerticalCandidateLabel; |
+ } else { |
+ candidate_label_ = new views::Label; |
+ } |
+ candidate_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); |
+ |
+ // Initialize the column set with two columns. |
+ views::ColumnSet* column_set = layout->AddColumnSet(0); |
+ column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, |
+ 0, views::GridLayout::USE_PREF, 0, 0); |
+ column_set->AddPaddingColumn(0, 4); |
+ column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, |
+ 0, views::GridLayout::USE_PREF, 0, 0); |
+ column_set->AddPaddingColumn(0, 4); |
+ |
+ // Add the shortcut label and the candidate label. |
+ layout->StartRow(0, 0); |
+ // |wrapped_shortcut_label| and |candidate_label_| will be owned by |this|. |
+ layout->AddView(wrapped_shortcut_label); |
+ layout->AddView(candidate_label_); |
+} |
+ |
+void CandidateView::SetCandidateText(const std::wstring& text) { |
+ shortcut_label_->SetColor(kShortcutColor); |
+ candidate_label_->SetText(text); |
+} |
+ |
+void CandidateView::Select() { |
+ set_background( |
+ views::Background::CreateSolidBackground(kSelectedRowBackgroundColor)); |
+ set_border(views::Border::CreateSolidBorder(1, kSelectedRowFrameColor)); |
+} |
+ |
+void CandidateView::Unselect() { |
+ set_background(NULL); |
+ set_border(NULL); |
+} |
+ |
+void CandidateView::Disable() { |
+ shortcut_label_->SetColor(kDisabledShortcutColor); |
+ candidate_label_->SetText(L""); |
+} |
+ |
+bool CandidateView::OnMousePressed(const views::MouseEvent& event) { |
+ // Select the candidate. We'll commit the candidate when the mouse |
+ // button is released. |
+ parent_candidate_window_->SelectCandidateAt(index_in_page_); |
+ // Request MouseDraggged and MouseReleased events. |
+ return true; |
+} |
+ |
+bool CandidateView::OnMouseDragged(const views::MouseEvent& event) { |
+ gfx::Point location_in_candidate_window = event.location(); |
+ views::View::ConvertPointToView(this, parent_candidate_window_, |
+ &location_in_candidate_window); |
+ // Notify the candidate window that a candidate is now being dragged. |
+ parent_candidate_window_->OnCandidateDragged(location_in_candidate_window); |
+ // Request MouseReleased event. |
+ return true; |
+} |
+ |
+void CandidateView::OnMouseReleased(const views::MouseEvent& event, |
+ bool canceled) { |
+ // Commit the current candidate unless it's canceled. |
+ if (!canceled) { |
+ parent_candidate_window_->CommitCandidate(); |
+ } |
+} |
+ |
+CandidateWindowView::CandidateWindowView( |
+ views::Widget* parent_frame) |
+ : orientation_(kVertical), |
+ selected_candidate_index_in_page_(0), |
+ parent_frame_(parent_frame), |
+ candidate_area_(NULL), |
+ footer_area_(NULL), |
+ header_area_(NULL), |
+ auxiliary_text_label_(NULL), |
+ footer_label_(NULL) { |
+} |
+ |
+void CandidateWindowView::Init() { |
+ // Set the background and the border of the view. |
+ set_background( |
+ views::Background::CreateSolidBackground(kDefaultBackgroundColor)); |
+ set_border(views::Border::CreateSolidBorder(1, kFrameColor)); |
+ |
+ // Create the header area. |
+ header_area_ = CreateHeaderArea(); |
+ // Create the candidate area. |
+ candidate_area_ = new views::View; |
+ // Create the footer area. |
+ footer_area_ = CreateFooterArea(); |
+ |
+ // Set the window layout of the view |
+ views::GridLayout* layout = new views::GridLayout(this); |
+ SetLayoutManager(layout); // |this| owns layout|. |
+ views::ColumnSet* column_set = layout->AddColumnSet(0); |
+ column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, |
+ 0, views::GridLayout::USE_PREF, 0, 0); |
+ |
+ // Add the header area. |
+ layout->StartRow(0, 0); |
+ layout->AddView(header_area_); // |header_area_| is owned by |this|. |
+ |
+ // Add the candidate area. |
+ layout->StartRow(0, 0); |
+ layout->AddView(candidate_area_); // |candidate_area_| is owned by |this|. |
+ |
+ // Add the footer area. |
+ layout->StartRow(0, 0); |
+ layout->AddView(footer_area_); // |footer_area_| is owned by |this|. |
+} |
+ |
+void CandidateWindowView::HideAuxiliaryText() { |
+ // Put the place holder to the header area. |
+ header_area_->RemoveAllChildViews(false); // Don't delete child views. |
+ header_area_->AddChildView(header_area_place_holder_.get()); |
+ ResizeAndSchedulePaint(); |
+} |
+ |
+void CandidateWindowView::ShowAuxiliaryText() { |
+ // Put contents to the header area. |
+ header_area_->RemoveAllChildViews(false); // Don't delete child views. |
+ header_area_->AddChildView(header_area_contents_.get()); |
+ ResizeAndSchedulePaint(); |
+} |
+ |
+void CandidateWindowView::UpdateAuxiliaryText(const std::string& utf8_text) { |
+ auxiliary_text_label_->SetText(UTF8ToWide(utf8_text)); |
+} |
+ |
+void CandidateWindowView::UpdateCandidates( |
+ const ImeLookupTable& lookup_table) { |
+ // HACK: ibus-pinyin sets page_size to 5. For now, we use the magic |
+ // number here to determine the orientation. |
+ // TODO(satorux): We should get the orientation information from |
+ // lookup_table. |
+ Orientation orientation = kVertical; |
+ if (lookup_table.page_size == 5) { |
+ orientation = kHorizontal; |
+ } |
+ // Initialize candidate views if necessary. |
+ MaybeInitializeCandidateViews(lookup_table.page_size, |
+ orientation); |
+ lookup_table_ = lookup_table; |
+ orientation_ = orientation; |
+ |
+ // Update the candidates in the current page. |
+ const int start_from = (lookup_table_.current_page_index * |
+ lookup_table_.page_size); |
+ for (size_t i = 0; i < candidate_views_.size(); ++i) { |
+ const size_t index_in_page = i; |
+ const size_t candidate_index = start_from + index_in_page; |
+ if (candidate_index < lookup_table_.candidates.size()) { |
+ candidate_views_[index_in_page]->SetCandidateText( |
+ UTF8ToWide(lookup_table_.candidates[candidate_index])); |
+ } else { |
+ // Disable the empty row. |
+ candidate_views_[index_in_page]->Disable(); |
+ } |
+ } |
+ |
+ // Select the current candidate per the lookup table. |
+ // TODO(satorux): Rename cursor_row_index to cursor_index_in_page. |
+ SelectCandidateAt(lookup_table_.cursor_row_index); |
+} |
+ |
+void CandidateWindowView::MaybeInitializeCandidateViews( |
+ int num_views, |
+ Orientation orientation) { |
+ // If the requested number of views matches the number of current views, |
+ // just reuse these. |
+ if (num_views == static_cast<int>(candidate_views_.size()) && |
+ orientation == orientation_) { |
+ return; |
+ } |
+ |
+ // Clear the existing candidate_views if any. |
+ for (size_t i = 0; i < candidate_views_.size(); ++i) { |
+ candidate_area_->RemoveChildView(candidate_views_[i]); |
+ } |
+ candidate_views_.clear(); |
+ |
+ views::GridLayout* layout = new views::GridLayout(candidate_area_); |
+ // |candidate_area_| owns |layout|. |
+ candidate_area_->SetLayoutManager(layout); |
+ // Initialize the column set. |
+ views::ColumnSet* column_set = layout->AddColumnSet(0); |
+ if (orientation == kVertical) { |
+ column_set->AddColumn(views::GridLayout::FILL, |
+ views::GridLayout::FILL, |
+ 0, views::GridLayout::USE_PREF, 0, 0); |
+ } else { |
+ for (int i = 0; i < num_views; ++i) { |
+ column_set->AddColumn(views::GridLayout::FILL, |
+ views::GridLayout::FILL, |
+ 0, views::GridLayout::USE_PREF, 0, 0); |
+ } |
+ } |
+ |
+ // Set insets so the border of the selected candidate is drawn inside of |
+ // the border of the main candidate window, but we don't have the inset |
+ // at the top and the bottom as we have the borders of the header and |
+ // footer areas. |
+ const gfx::Insets kCandidateAreaInsets(0, 1, 0, 1); |
+ layout->SetInsets(kCandidateAreaInsets.top(), |
+ kCandidateAreaInsets.left(), |
+ kCandidateAreaInsets.bottom(), |
+ kCandidateAreaInsets.right()); |
+ |
+ // Add views to the candidate area. |
+ if (orientation == kHorizontal) { |
+ layout->StartRow(0, 0); |
+ } |
+ for (int i = 0; i < num_views; ++i) { |
+ CandidateView* candidate_row = new CandidateView(this, i, orientation); |
+ candidate_row->Init(); |
+ candidate_views_.push_back(candidate_row); |
+ if (orientation == kVertical) { |
+ layout->StartRow(0, 0); |
+ } |
+ // |candidate_row| will be owned by candidate_area_|. |
+ layout->AddView(candidate_row); |
+ } |
+} |
+ |
+views::View* CandidateWindowView::CreateHeaderArea() { |
+ // |header_area_place_holder_| will not be owned by another view. |
+ // This will be deleted by scoped_ptr. |
+ // |
+ // This is because we swap the contents of |header_area_| between |
+ // |header_area_place_holder_| (to show nothing) and |
+ // |header_area_contents_| (to show something). In other words, |
+ // |header_area_| only contains one of the two views hence cannot own |
+ // the two views at the same time. |
+ header_area_place_holder_.reset(new views::View); |
+ header_area_place_holder_->set_parent_owned(false); // Won't be owened. |
+ |
+ // |auxiliary_text_label_| will be owned by |header_area_contents_|. |
+ auxiliary_text_label_ = new views::Label; |
+ auxiliary_text_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); |
+ |
+ const gfx::Insets kHeaderInsets(2, 2, 2, 4); |
+ // |header_area_contents_| will not be owned by another view. |
+ // See a comment at |header_area_place_holder_| for why. |
+ header_area_contents_.reset( |
+ WrapWithPadding(auxiliary_text_label_, kHeaderInsets)); |
+ header_area_contents_->set_parent_owned(false); // Won't be owened. |
+ header_area_contents_->set_border( |
+ views::Border::CreateSolidBorder(1, kFrameColor)); |
+ header_area_contents_->set_background( |
+ views::Background::CreateVerticalGradientBackground( |
+ kFooterTopColor, |
+ kFooterBottomColor)); |
+ |
+ views::View* header_area = new views::View; |
+ header_area->SetLayoutManager(new views::FillLayout); |
+ // Initialize the header area with the place holder (i.e. show nothing). |
+ header_area->AddChildView(header_area_place_holder_.get()); |
+ return header_area; |
+} |
+ |
+views::View* CandidateWindowView::CreateFooterArea() { |
+ // |footer_area_place_holder_| will not be owned by another view. |
+ // See also the comment about |header_area_place_holder_| in |
+ // CreateHeaderArea(). |
+ footer_area_place_holder_.reset(new views::View); |
+ footer_area_place_holder_->set_parent_owned(false); // Won't be owened. |
+ |
+ footer_label_ = new views::Label(); |
+ footer_label_->SetHorizontalAlignment(views::Label::ALIGN_RIGHT); |
+ |
+ const gfx::Insets kFooterInsets(2, 2, 2, 4); |
+ footer_area_contents_.reset( |
+ WrapWithPadding(footer_label_, kFooterInsets)); |
+ footer_area_contents_->set_parent_owned(false); // Won't be owened. |
+ footer_area_contents_->set_border( |
+ views::Border::CreateSolidBorder(1, kFrameColor)); |
+ footer_area_contents_->set_background( |
+ views::Background::CreateVerticalGradientBackground( |
+ kFooterTopColor, |
+ kFooterBottomColor)); |
+ |
+ views::View* footer_area = new views::View; |
+ footer_area->SetLayoutManager(new views::FillLayout); |
+ // Initialize the footer area with the place holder (i.e. show nothing). |
+ footer_area->AddChildView(footer_area_place_holder_.get()); |
+ return footer_area; |
+} |
+ |
+void CandidateWindowView::SelectCandidateAt(int index_in_page) { |
+ // Ignore click on out of range views. |
+ if (index_in_page >= lookup_table_.num_candidates_in_current_page) { |
+ return; |
+ } |
+ |
+ // Remember the currently selected candidate index in the current page. |
+ selected_candidate_index_in_page_ = index_in_page; |
+ |
+ // Unselect all the candidate first. Theoretically, we could remember |
+ // the lastly selected candidate and only unselect it, but unselecting |
+ // everything is simpler. |
+ for (size_t i = 0; i < candidate_views_.size(); ++i) { |
+ candidate_views_[i]->Unselect(); |
+ } |
+ // Select the candidate specified by index_in_page. |
+ candidate_views_[index_in_page]->Select(); |
+ |
+ // Update the cursor indexes in the model. |
+ lookup_table_.cursor_row_index = index_in_page; |
+ lookup_table_.cursor_absolute_index = |
+ (lookup_table_.page_size * lookup_table_.current_page_index + |
+ index_in_page); |
+ |
+ // Update the footer area. |
+ footer_area_->RemoveAllChildViews(false); // Don't delete child views. |
+ if (orientation_ == kVertical) { |
+ // Show information about the cursor and the page in the footer area. |
+ footer_label_->SetText( |
+ StringPrintf(L"%d/%d", |
+ lookup_table_.cursor_absolute_index + 1, |
+ lookup_table_.candidates.size())); |
+ footer_area_->AddChildView(footer_area_contents_.get()); |
+ } else { |
+ // Show nothing in the footer area if the orientation is horizontal. |
+ footer_area_->AddChildView(footer_area_place_holder_.get()); |
+ } |
+ |
+ ResizeAndSchedulePaint(); |
+} |
+ |
+void CandidateWindowView::OnCandidateDragged( |
+ const gfx::Point& location) { |
+ for (size_t i = 0; i < candidate_views_.size(); ++i) { |
+ gfx::Point converted_location = location; |
+ views::View::ConvertPointToView(this, candidate_views_[i], |
+ &converted_location); |
+ if (candidate_views_[i]->HitTest(converted_location)) { |
+ SelectCandidateAt(i); |
+ break; |
+ } |
+ } |
+} |
+ |
+void CandidateWindowView::CommitCandidate() { |
+ // For now, we don't distinguish left and right clicks. |
+ const int button = 1; // Left button. |
+ const int key_modifilers = 0; |
+ FOR_EACH_OBSERVER(Observer, observers_, |
+ OnCandidateCommitted(selected_candidate_index_in_page_, |
+ button, |
+ key_modifilers)); |
+} |
+ |
+void CandidateWindowView::ResizeAndSchedulePaint() { |
+ // Resize the parent frame, with the current candidate window size. |
+ gfx::Size size = GetPreferredSize(); |
+ gfx::Rect bounds; |
+ parent_frame_->GetBounds(&bounds, false); |
+ bounds.set_width(size.width()); |
+ bounds.set_height(size.height()); |
+ parent_frame_->SetBounds(bounds); |
+ |
+ SchedulePaint(); |
+} |
+ |
+void CandidateWindowController::Init() { |
+ // Initialize the IME status connection. |
+ ImeStatusMonitorFunctions functions; |
+ functions.hide_auxiliary_text = |
+ &CandidateWindowController::OnHideAuxiliaryText; |
+ functions.hide_lookup_table = |
+ &CandidateWindowController::OnHideLookupTable; |
+ functions.set_cursor_location = |
+ &CandidateWindowController::OnSetCursorLocation; |
+ functions.update_auxiliary_text = |
+ &CandidateWindowController::OnUpdateAuxiliaryText; |
+ functions.update_lookup_table = |
+ &CandidateWindowController::OnUpdateLookupTable; |
+ ime_status_connection_ = MonitorImeStatus(functions, this); |
+ CHECK(ime_status_connection_) |
+ << "MonitorImeStatus() failed."; |
+ |
+ // Create the candidate window view. |
+ CreateView(); |
+} |
+ |
+void CandidateWindowController::CreateView() { |
+ // Create a non-decorated frame. |
+ frame_.reset(views::Widget::CreatePopupWidget( |
+ views::Widget::NotTransparent, |
+ views::Widget::AcceptEvents, |
+ views::Widget::DeleteOnDestroy)); |
+ // The size is initially zero. |
+ frame_->Init(NULL, gfx::Rect(0, 0)); |
+ |
+ // Create the candidate window. |
+ candidate_window_ = new CandidateWindowView(frame_.get()); |
+ candidate_window_->Init(); |
+ candidate_window_->AddObserver(this); |
+ |
+ // Put the candidate window view on the frame. The frame is resized |
+ // later when the candidate window is shown. |
+ views::RootView* root_view = frame_->GetRootView(); |
+ // |root_view| owns the |candidate_window_|, thus |frame_| effectively |
+ // owns |candidate_window_|. |
+ root_view->SetContentsView(candidate_window_); |
+} |
+ |
+CandidateWindowController::CandidateWindowController() |
+ : ime_status_connection_(NULL), |
+ frame_(NULL) { |
+} |
+ |
+CandidateWindowController::~CandidateWindowController() { |
+ candidate_window_->RemoveObserver(this); |
+ chromeos::DisconnectImeStatus(ime_status_connection_); |
+} |
+ |
+gfx::Rect CandidateWindowController::GetMonitorWorkAreaNearestWindow() { |
+ return views::Screen::GetMonitorWorkAreaNearestWindow( |
+ frame_->GetNativeView()); |
+} |
+ |
+void CandidateWindowController::OnHideAuxiliaryText(void* ime_library) { |
+ CandidateWindowController* controller = |
+ static_cast<CandidateWindowController*>(ime_library); |
+ |
+ controller->candidate_window_->HideAuxiliaryText(); |
+} |
+ |
+void CandidateWindowController::OnHideLookupTable(void* ime_library) { |
+ CandidateWindowController* controller = |
+ static_cast<CandidateWindowController*>(ime_library); |
+ |
+ controller->frame_->Hide(); |
+} |
+ |
+void CandidateWindowController::OnSetCursorLocation(void* ime_library, |
+ int x, |
+ int y, |
+ int width, |
+ int height) { |
+ CandidateWindowController* controller = |
+ static_cast<CandidateWindowController*>(ime_library); |
+ |
+ // TODO(satorux): This has to be computed runtime. |
+ const int kHorizontalOffset = 30; |
+ |
+ gfx::Rect frame_bounds; |
+ controller->frame_->GetBounds(&frame_bounds, false); |
+ |
+ gfx::Rect screen_bounds = |
+ controller->GetMonitorWorkAreaNearestWindow(); |
+ |
+ // The default position. |
+ frame_bounds.set_x(x - kHorizontalOffset); |
+ frame_bounds.set_y(y + height); |
+ |
+ // Handle overflow at the left and the top. |
+ frame_bounds.set_x(std::max(frame_bounds.x(), screen_bounds.x())); |
+ frame_bounds.set_y(std::max(frame_bounds.y(), screen_bounds.y())); |
+ |
+ // Handle overflow at the right. |
+ const int right_overflow = frame_bounds.right() - screen_bounds.right(); |
+ if (right_overflow > 0) { |
+ frame_bounds.set_x(frame_bounds.x() - right_overflow); |
+ } |
+ |
+ // Handle overflow at the bottom. |
+ const int bottom_overflow = frame_bounds.bottom() - screen_bounds.bottom(); |
+ if (bottom_overflow > 0) { |
+ frame_bounds.set_y(frame_bounds.y() - height - frame_bounds.height()); |
+ } |
+ |
+ controller->frame_->SetBounds(frame_bounds); |
+} |
+ |
+void CandidateWindowController::OnUpdateAuxiliaryText( |
+ void* ime_library, |
+ const std::string& utf8_text, |
+ bool visible) { |
+ CandidateWindowController* controller = |
+ static_cast<CandidateWindowController*>(ime_library); |
+ // HACK for ibus-anthy: ibus-anthy sends us page information like |
+ // "( 1 / 19 )" as auxiliary text. We should ignore this as we show the |
+ // same information in the footer area (i.e. don't want to show the same |
+ // information in two places). |
+ // |
+ // TODO(satorux): Remove this once we remove ibus-anthy from Chromium OS. |
+ if (utf8_text.size() >= 2 && |
+ utf8_text[0] == '(' && utf8_text[utf8_text.size() -1] == ')') { |
+ // Hide the auxiliary text in case something is shown already. |
+ controller->candidate_window_->HideAuxiliaryText(); |
+ return; // Ignore the given auxiliary text. |
+ } |
+ |
+ // If it's not visible, hide the auxiliary text and return. |
+ if (!visible) { |
+ controller->candidate_window_->HideAuxiliaryText(); |
+ return; |
+ } |
+ controller->candidate_window_->UpdateAuxiliaryText(utf8_text); |
+ controller->candidate_window_->ShowAuxiliaryText(); |
+} |
+ |
+void CandidateWindowController::OnUpdateLookupTable( |
+ void* ime_library, |
+ const ImeLookupTable& lookup_table) { |
+ CandidateWindowController* controller = |
+ static_cast<CandidateWindowController*>(ime_library); |
+ |
+ // If it's not visible, hide the window and return. |
+ if (!lookup_table.visible) { |
+ controller->frame_->Hide(); |
+ return; |
+ } |
+ |
+ controller->candidate_window_->UpdateCandidates(lookup_table); |
+ controller->frame_->Show(); |
+} |
+ |
+void CandidateWindowController::OnCandidateCommitted(int index, |
+ int button, |
+ int flags) { |
+ NotifyCandidateClicked(ime_status_connection_, index, button, flags); |
+} |
+ |
+} // namespace chromeos |
+ |
+int main(int argc, char** argv) { |
+ // Initialize gtk stuff. |
+ g_thread_init(NULL); |
+ g_type_init(); |
+ gtk_init(&argc, &argv); |
+ |
+ // Initialize Chrome stuff. |
+ base::AtExitManager exit_manager; |
+ base::EnableTerminationOnHeapCorruption(); |
+ app::RegisterPathProvider(); |
+ ResourceBundle::InitSharedInstance(L"en-US"); |
+ |
+ // Load libcros. |
+ chrome::RegisterPathProvider(); // for libcros.so. |
+ CHECK(chromeos::CrosLibrary::EnsureLoaded()) |
+ << "Failed to load libcros"; |
+ |
+ // Create the main message loop. |
+ MessageLoop main_message_loop(MessageLoop::TYPE_UI); |
+ |
+ // Create the candidate window controller. |
+ chromeos::CandidateWindowController controller; |
+ controller.Init(); |
+ |
+ // Start the main loop. |
+ views::AcceleratorHandler accelerator_handler; |
+ MessageLoopForUI::current()->Run(&accelerator_handler); |
+ |
+ return 0; |
+} |