| 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;
|
| +}
|
|
|