diff options
-rw-r--r-- | build/all.gyp | 2 | ||||
-rw-r--r-- | chrome/browser/chromeos/text_input/candidate_window.cc | 948 | ||||
-rw-r--r-- | chrome/browser/chromeos/text_input/text_input.gyp | 29 |
3 files changed, 978 insertions, 1 deletions
diff --git a/build/all.gyp b/build/all.gyp index 8a0910b..dbec978 100644 --- a/build/all.gyp +++ b/build/all.gyp @@ -114,7 +114,7 @@ }], ['chromeos==1', { 'dependencies': [ - '../third_party/chromeos_text_input/text_input.gyp:*', + '../chrome/browser/chromeos/text_input/text_input.gyp:*', ], }], ], 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 0000000..c7cc055 --- /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; +} diff --git a/chrome/browser/chromeos/text_input/text_input.gyp b/chrome/browser/chromeos/text_input/text_input.gyp new file mode 100644 index 0000000..ee2ab4d --- /dev/null +++ b/chrome/browser/chromeos/text_input/text_input.gyp @@ -0,0 +1,29 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [ + { + 'target_name': 'candidate_window', + 'type': 'executable', + 'dependencies': [ + '../../../../base/base.gyp:base', + '../../../../build/linux/system.gyp:gtk', + '../../../../build/linux/system.gyp:x11', + '../../../../chrome/chrome.gyp:common_constants', + '../../../../skia/skia.gyp:skia', + '../../../../views/views.gyp:views', + '../cros/cros_api.gyp:cros_api', + ], + 'sources': [ + 'candidate_window.cc', + # For loading libcros. + '../cros/cros_library.cc', + ], + }, + ], +} |