普通文本  |  1419行  |  52.39 KB

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/input_method/candidate_window.h"

#include <algorithm>
#include <string>
#include <vector>

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/observer_list.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "third_party/cros/chromeos_input_method_ui.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font.h"
#include "views/controls/label.h"
#include "views/controls/textfield/textfield.h"
#include "views/events/event.h"
#include "views/layout/fill_layout.h"
#include "views/layout/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);
const SkColor kAnnotationColor = SkColorSetRGB(0x88, 0x88, 0x88);

// We'll use a bigger font size, so Chinese characters are more readable
// in the candidate window.
#if defined(CROS_FONTS_USING_BCI)
const int kFontSizeDelta = 1;
#else
const int kFontSizeDelta = 2;
#endif

// 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;
// The maximum width of candidate labels in the vertical candidate
// window. We use this value to prevent the candidate window from being
// too wide when one of candidates are long.
const int kMaxCandidateLabelWidth = 500;

// 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);
    }
    if (size.width() > kMaxCandidateLabelWidth) {
      size.set_width(kMaxCandidateLabelWidth);
    }
    return size;
  }
};

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

// Creates shortcut text from the given index and the orientation.
std::wstring CreateShortcutText(int index,
    chromeos::InputMethodLookupTable::Orientation orientation) {
  // 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'?';
  // -1 to exclude the null character at the end.
  if (index < static_cast<int>(arraysize(kShortcutCharacters) - 1)) {
    shortcut_character = kShortcutCharacters[index];
  }

  std::wstring shortcut_text;
  if (orientation == chromeos::InputMethodLookupTable::kVertical) {
    shortcut_text = base::StringPrintf(L"%lc", shortcut_character);
  } else {
    shortcut_text = base::StringPrintf(L"%lc.", shortcut_character);
  }

  return shortcut_text;
}

// Creates the shortcut label, and returns it (never returns NULL).
// The label text is not set in this function.
views::Label* CreateShortcutLabel(
    chromeos::InputMethodLookupTable::Orientation orientation) {
  // Create the shortcut label. The label will be owned by
  // |wrapped_shortcut_label|, hence it's deleted when
  // |wrapped_shortcut_label| is deleted.
  views::Label* shortcut_label = new views::Label;

  if (orientation == chromeos::InputMethodLookupTable::kVertical) {
    shortcut_label->SetFont(
        shortcut_label->font().DeriveFont(kFontSizeDelta, gfx::Font::BOLD));
  } else {
    shortcut_label->SetFont(
        shortcut_label->font().DeriveFont(kFontSizeDelta));
  }
  // TODO(satorux): Maybe we need to use language specific fonts for
  // candidate_label, like Chinese font for Chinese input method?
  shortcut_label->SetColor(kShortcutColor);

  return shortcut_label;
}

// Wraps the shortcut label, then decorates wrapped shortcut label
// and returns it (never returns NULL).
// The label text is not set in this function.
views::View* CreateWrappedShortcutLabel(views::Label* shortcut_label,
    chromeos::InputMethodLookupTable::Orientation orientation) {
  // Wrap it with padding.
  const gfx::Insets kVerticalShortcutLabelInsets(1, 6, 1, 6);
  const gfx::Insets kHorizontalShortcutLabelInsets(1, 3, 1, 0);
  const gfx::Insets insets =
      (orientation == chromeos::InputMethodLookupTable::kVertical ?
       kVerticalShortcutLabelInsets :
       kHorizontalShortcutLabelInsets);
  views::View* wrapped_shortcut_label =
      WrapWithPadding(shortcut_label, insets);

  // Add decoration based on the orientation.
  if (orientation == chromeos::InputMethodLookupTable::kVertical) {
    // Set the background color.
    wrapped_shortcut_label->set_background(
        views::Background::CreateSolidBackground(
            kShortcutBackgroundColor));
  }

  return wrapped_shortcut_label;
}

// Creates the candidate label, and returns it (never returns NULL).
// The label text is not set in this function.
views::Label* CreateCandidateLabel(
    chromeos::InputMethodLookupTable::Orientation orientation) {
  views::Label* candidate_label = NULL;

  // 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 == chromeos::InputMethodLookupTable::kVertical) {
    candidate_label = new VerticalCandidateLabel;
  } else {
    candidate_label = new views::Label;
  }

  // Change the font size.
  candidate_label->SetFont(
      candidate_label->font().DeriveFont(kFontSizeDelta));
  candidate_label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);

  return candidate_label;
}

// Creates the annotation label, and return it (never returns NULL).
// The label text is not set in this function.
views::Label* CreateAnnotationLabel(
    chromeos::InputMethodLookupTable::Orientation orientation) {
  // Create the annotation label.
  views::Label* annotation_label = new views::Label;

  // Change the font size and color.
  annotation_label->SetFont(
      annotation_label->font().DeriveFont(kFontSizeDelta));
  annotation_label->SetColor(kAnnotationColor);
  annotation_label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);

  return annotation_label;
}

// Computes shortcut column width.
int ComputeShortcutColumnWidth(
    const chromeos::InputMethodLookupTable& lookup_table) {
  int shortcut_column_width = 0;
  // Create the shortcut label. The label will be owned by
  // |wrapped_shortcut_label|, hence it's deleted when
  // |wrapped_shortcut_label| is deleted.
  views::Label* shortcut_label = CreateShortcutLabel(lookup_table.orientation);
  scoped_ptr<views::View> wrapped_shortcut_label(
      CreateWrappedShortcutLabel(shortcut_label, lookup_table.orientation));

  // Compute the max width in shortcut labels.
  // We'll create temporary shortcut labels, and choose the largest width.
  for (int i = 0; i < lookup_table.page_size; ++i) {
    shortcut_label->SetText(
        CreateShortcutText(i, lookup_table.orientation));
    shortcut_column_width =
        std::max(shortcut_column_width,
                 wrapped_shortcut_label->GetPreferredSize().width());
  }

  return shortcut_column_width;
}

// Computes the page index. For instance, if the page size is 9, and the
// cursor is pointing to 13th candidate, the page index will be 1 (2nd
// page, as the index is zero-origin). Returns -1 on error.
int ComputePageIndex(const chromeos::InputMethodLookupTable& lookup_table) {
  if (lookup_table.page_size > 0)
    return lookup_table.cursor_absolute_index / lookup_table.page_size;
  return -1;
}

// Computes candidate column width.
int ComputeCandidateColumnWidth(
    const chromeos::InputMethodLookupTable& lookup_table) {
  int candidate_column_width = 0;
  scoped_ptr<views::Label> candidate_label(
      CreateCandidateLabel(lookup_table.orientation));

  // Compute the start index of |lookup_table_|.
  const int current_page_index = ComputePageIndex(lookup_table);
  if (current_page_index < 0)
    return 0;
  const size_t start_from = current_page_index * lookup_table.page_size;

  // Compute the max width in candidate labels.
  // We'll create temporary candidate labels, and choose the largest width.
  for (size_t i = 0; i + start_from < lookup_table.candidates.size(); ++i) {
    const size_t index = start_from + i;

    candidate_label->SetText(
        UTF8ToWide(lookup_table.candidates[index]));
    candidate_column_width =
        std::max(candidate_column_width,
                 candidate_label->GetPreferredSize().width());
  }

  return candidate_column_width;
}

// Computes annotation column width.
int ComputeAnnotationColumnWidth(
    const chromeos::InputMethodLookupTable& lookup_table) {
  int annotation_column_width = 0;
  scoped_ptr<views::Label> annotation_label(
      CreateAnnotationLabel(lookup_table.orientation));

  // Compute the start index of |lookup_table_|.
  const int current_page_index = ComputePageIndex(lookup_table);
  if (current_page_index < 0)
    return 0;
  const size_t start_from = current_page_index * lookup_table.page_size;

  // Compute max width in annotation labels.
  // We'll create temporary annotation labels, and choose the largest width.
  for (size_t i = 0; i + start_from < lookup_table.annotations.size(); ++i) {
    const size_t index = start_from + i;

    annotation_label->SetText(
        UTF8ToWide(lookup_table.annotations[index]));
    annotation_column_width =
        std::max(annotation_column_width,
                 annotation_label->GetPreferredSize().width());
  }

  return annotation_column_width;
}

}  // namespace

namespace chromeos {

class CandidateView;

// CandidateWindowView is the main container of the candidate window UI.
class CandidateWindowView : public views::View {
 public:
  // 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_input_method_ui.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 lookup table.
  void HideLookupTable();

  // Hides the auxiliary text.
  void HideAuxiliaryText();

  // Shows the auxiliary text.
  void ShowAuxiliaryText();

  // Updates the auxiliary text.
  void UpdateAuxiliaryText(const std::string& utf8_text);

  // Returns true if we should update candidate views in the window.  For
  // instance, if we are going to show the same candidates as before, we
  // don't have to update candidate views. This happens when the user just
  // moves the cursor in the same page in the candidate window.
  bool ShouldUpdateCandidateViews(
      const InputMethodLookupTable& old_table,
      const InputMethodLookupTable& new_table);

  // Updates candidates of the candidate window from |lookup_table|.
  // Candidates are arranged per |orientation|.
  void UpdateCandidates(const InputMethodLookupTable& lookup_table);

  // Resizes and moves the parent frame. The two actions should be
  // performed consecutively as resizing may require the candidate window
  // to move. For instance, we may need to move the candidate window from
  // below the cursor to above the cursor, if the candidate window becomes
  // too big to be shown near the bottom of the screen.  This function
  // needs to be called when the visible contents of the candidate window
  // are modified.
  void ResizeAndMoveParentFrame();

  // Resizes the parent frame per the current contents size.
  //
  // The function is rarely used solely. See comments at
  // ResizeAndMoveParentFrame().
  void ResizeParentFrame();

  // Moves the candidate window per the current cursor location, and the
  // horizontal offset.
  //
  // The function is rarely used solely. See comments at
  // ResizeAndMoveParentFrame().
  void MoveParentFrame();

  // Returns the horizontal offset used for placing the vertical candidate
  // window so that the first candidate is aligned with the the text being
  // converted like:
  //
  //      XXX           <- The user is converting XXX
  //   +-----+
  //   |1 XXX|
  //   |2 YYY|
  //   |3 ZZZ|
  //
  // Returns 0 if no candidate is present.
  int GetHorizontalOffset();

  // A function to be called when one of the |candidate_views_| receives a mouse
  // press event.
  void OnMousePressed();
  // A function to be called when one of the |candidate_views_| receives a mouse
  // release event.
  void OnMouseReleased();

  void set_cursor_location(const gfx::Rect& cursor_location) {
    cursor_location_ = cursor_location;
  }

  const gfx::Rect& cursor_location() const { return cursor_location_; }

 protected:
  // Override View::VisibilityChanged()
  virtual void VisibilityChanged(View* starting_from, bool is_visible) OVERRIDE;

  // Override View::OnBoundsChanged()
  virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;

 private:
  // Initializes the candidate views if needed.
  void MaybeInitializeCandidateViews(
      const InputMethodLookupTable& lookup_table);

  // 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();

  // The lookup table (candidates).
  InputMethodLookupTable 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 the auxiliary text is shown, if the
  // orientation is vertical. Usually the auxiliary text is used for
  // showing candidate number information like 2/19.
  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
  // orientation is horizontal. If the auxiliary text is not provided, we
  // show nothing.  For instance, we show pinyin text like "zhong'guo".
  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 header label is shown in the header area.
  views::Label* header_label_;
  // The footer label is shown in the footer area.
  views::Label* footer_label_;

  // Current columns width in |candidate_area_|.
  int previous_shortcut_column_width_;
  int previous_candidate_column_width_;
  int previous_annotation_column_width_;

  // The last cursor location.
  gfx::Rect cursor_location_;

  // true if a mouse button is pressed, and is not yet released.
  bool mouse_is_pressed_;
};

// CandidateRow renderes a row of a candidate.
class CandidateView : public views::View {
 public:
  CandidateView(CandidateWindowView* parent_candidate_window,
                int index_in_page,
                InputMethodLookupTable::Orientation orientation);
  virtual ~CandidateView() {}
  // Initializes the candidate view with the given column widths.
  // A width of 0 means that the column is resizable.
  void Init(int shortcut_column_width,
            int candidate_column_width,
            int annotation_column_width);

  // Sets candidate text to the given text.
  void SetCandidateText(const std::wstring& text);

  // Sets shortcut text to the given text.
  void SetShortcutText(const std::wstring& text);

  // Sets annotation text to the given text.
  void SetAnnotationText(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();

  // Enables or disables the candidate row based on |enabled|. Changes the
  // appearance to make it look like unclickable area.
  void SetRowEnabled(bool enabled);

  // Returns the relative position of the candidate label.
  gfx::Point GetCandidateLabelPosition() const;

 private:
  // Overridden from View:
  virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE;
  virtual bool OnMouseDragged(const views::MouseEvent& event) OVERRIDE;
  virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE;
  virtual void OnMouseCaptureLost() OVERRIDE;

  // Zero-origin index in the current page.
  int index_in_page_;

  // The orientation of the candidate view.
  InputMethodLookupTable::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_;
  // The annotation label renders annotations.
  views::Label* annotation_label_;
};

// The implementation of CandidateWindowController.
// CandidateWindowController controls the CandidateWindow.
class CandidateWindowController::Impl : public CandidateWindowView::Observer {
 public:
  Impl();
  virtual ~Impl();

  // Initializes the candidate window. Returns true on success.
  bool Init();

 private:
  // CandidateWindowView::Observer implementation.
  virtual void OnCandidateCommitted(int index,
                                    int button,
                                    int flags);

  // Creates the candidate window view.
  void CreateView();

  // The function is called when |HideAuxiliaryText| signal is received in
  // libcros. |input_method_library| is a void pointer to this object.
  static void OnHideAuxiliaryText(void* input_method_library);

  // The function is called when |HideLookupTable| signal is received in
  // libcros. |input_method_library| is a void pointer to this object.
  static void OnHideLookupTable(void* input_method_library);

  // The function is called when |SetCursorLocation| signal is received
  // in libcros. |input_method_library| is a void pointer to this object.
  static void OnSetCursorLocation(void* input_method_library,
                                  int x,
                                  int y,
                                  int width,
                                  int height);

  // The function is called when |UpdateAuxiliaryText| signal is received
  // in libcros. |input_method_library| is a void pointer to this object.
  static void OnUpdateAuxiliaryText(void* input_method_library,
                                    const std::string& utf8_text,
                                    bool visible);

  // The function is called when |UpdateLookupTable| signal is received
  // in libcros. |input_method_library| is a void pointer to this object.
  static void OnUpdateLookupTable(void* input_method_library,
                                  const InputMethodLookupTable& lookup_table);

  // This function is called by libcros when ibus connects or disconnects.
  // |input_method_library| is a void pointer to this object.
  static void OnConnectionChange(void* input_method_library, bool connected);

  // The connection is used for communicating with input method UI logic
  // in libcros.
  InputMethodUiStatusConnection* ui_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,
    InputMethodLookupTable::Orientation orientation)
    : index_in_page_(index_in_page),
      orientation_(orientation),
      parent_candidate_window_(parent_candidate_window),
      shortcut_label_(NULL),
      candidate_label_(NULL),
      annotation_label_(NULL) {
}

void CandidateView::Init(int shortcut_column_width,
                         int candidate_column_width,
                         int annotation_column_width) {
  views::GridLayout* layout = new views::GridLayout(this);
  SetLayoutManager(layout);  // |this| owns |layout|.

  // Create Labels.
  shortcut_label_ = CreateShortcutLabel(orientation_);
  views::View* wrapped_shortcut_label =
      CreateWrappedShortcutLabel(shortcut_label_, orientation_);
  candidate_label_ = CreateCandidateLabel(orientation_);
  annotation_label_ = CreateAnnotationLabel(orientation_);

  // Initialize the column set with three columns.
  views::ColumnSet* column_set = layout->AddColumnSet(0);

  // If orientation is vertical, each column width is fixed.
  // Otherwise the width is resizable.
  const views::GridLayout::SizeType column_type =
      orientation_ == InputMethodLookupTable::kVertical ?
          views::GridLayout::FIXED : views::GridLayout::USE_PREF;

  const int padding_column_width =
      orientation_ == InputMethodLookupTable::kVertical ? 4 : 6;

  // Set shortcut column type and width.
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
                        0, column_type, shortcut_column_width, 0);
  column_set->AddPaddingColumn(0, padding_column_width);

  // Set candidate column type and width.
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
                        0, column_type, candidate_column_width, 0);
  column_set->AddPaddingColumn(0, padding_column_width);

  // Set annotation column type and width.
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
                        0, column_type, annotation_column_width, 0);
  column_set->AddPaddingColumn(0, padding_column_width);

  // Add the shortcut label, the candidate label, and annotation label.
  layout->StartRow(0, 0);
  // |wrapped_shortcut_label|, |candidate_label_|, and |annotation_label_|
  // will be owned by |this|.
  layout->AddView(wrapped_shortcut_label);
  layout->AddView(candidate_label_);
  layout->AddView(annotation_label_);
}

void CandidateView::SetCandidateText(const std::wstring& text) {
  candidate_label_->SetText(text);
}

void CandidateView::SetShortcutText(const std::wstring& text) {
  shortcut_label_->SetText(text);
}

void CandidateView::SetAnnotationText(const std::wstring& text) {
  annotation_label_->SetText(text);
}

void CandidateView::Select() {
  set_background(
      views::Background::CreateSolidBackground(kSelectedRowBackgroundColor));
  set_border(views::Border::CreateSolidBorder(1, kSelectedRowFrameColor));
  // Need to call SchedulePaint() for background and border color changes.
  SchedulePaint();
}

void CandidateView::Unselect() {
  set_background(NULL);
  set_border(NULL);
  SchedulePaint();  // See comments at Select().
}

void CandidateView::SetRowEnabled(bool enabled) {
  shortcut_label_->SetColor(
      enabled ? kShortcutColor : kDisabledShortcutColor);
}

gfx::Point CandidateView::GetCandidateLabelPosition() const {
  return candidate_label_->GetMirroredPosition();
}

bool CandidateView::OnMousePressed(const views::MouseEvent& event) {
  parent_candidate_window_->OnMousePressed();
  // 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) {
  // Commit the current candidate.
  parent_candidate_window_->CommitCandidate();
  OnMouseCaptureLost();
}

void CandidateView::OnMouseCaptureLost() {
  parent_candidate_window_->OnMouseReleased();
}

CandidateWindowView::CandidateWindowView(
    views::Widget* parent_frame)
    : selected_candidate_index_in_page_(0),
      parent_frame_(parent_frame),
      candidate_area_(NULL),
      footer_area_(NULL),
      header_area_(NULL),
      header_label_(NULL),
      footer_label_(NULL),
      previous_shortcut_column_width_(0),
      previous_candidate_column_width_(0),
      previous_annotation_column_width_(0),
      mouse_is_pressed_(false) {
}

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::HideLookupTable() {
  if (!mouse_is_pressed_) {
    parent_frame_->Hide();
    return;
  }

  // We should not hide the |frame_| when a mouse is pressed, so we don't run
  // into issues below.
  //
  // First, in the following scenario, it seems that the Views popup window does
  // not release mouse/keyboard grab even after it gets hidden.
  //
  // 1. create a popup window by views::Widget::CreateWidget() with the
  //    accept_events flag set to true on the CreateParams.
  // 2. press a mouse button on the window.
  // 3. before releasing the mouse button, Hide() the window.
  // 4. release the button.
  //
  // And if we embed IME candidate window into Chrome, the window sometimes
  // receives an extra 'hide-lookup-table' event before mouse button is
  // released:
  //
  // 1. the candidate window is clicked.
  // 2. The mouse click handler in this file, OnMousePressed() in CandidateView,
  //    is called, and the handler consumes the event by returning true.
  // 3. HOWEVER, if the candidate window is embedded into Chrome, the event is
  //    also sent to Chrome! (problem #1)
  // 4. im-ibus.so in Chrome sends 'focus-out' event to ibus-daemon.
  // 5. ibus-daemon sends 'hide-lookup-table' event to the candidate window.
  // 6. the window is hidden, but the window does not release mouse/keyboard
  //    grab! (problem #2)
  // 7. mouse button is released.
  // 8. now all mouse/keyboard events are consumed by the hidden popup, and are
  //    not sent to Chrome.
  //
  // TODO(yusukes): investigate why the click event is sent to both candidate
  // window and Chrome. http://crosbug.com/11423
  // TODO(yusukes): investigate if we could fix Views so it always releases grab
  // when a popup window gets hidden. http://crosbug.com/11422
  //
  LOG(WARNING) << "Can't hide the table since a mouse button is not released.";
}

void CandidateWindowView::OnMousePressed() {
  mouse_is_pressed_ = true;
}

void CandidateWindowView::OnMouseReleased() {
  mouse_is_pressed_ = false;
}

void CandidateWindowView::HideAuxiliaryText() {
  views::View* target_area = (
      lookup_table_.orientation == InputMethodLookupTable::kHorizontal ?
      header_area_ : footer_area_);
  views::View* target_place_holder = (
      lookup_table_.orientation == InputMethodLookupTable::kHorizontal ?
      header_area_place_holder_.get() :
      footer_area_place_holder_.get());
  // Put the place holder to the target display area.
  target_area->RemoveAllChildViews(false);  // Don't delete child views.
  target_area->AddChildView(target_place_holder);
}

void CandidateWindowView::ShowAuxiliaryText() {
  views::View* target_area = (
      lookup_table_.orientation == InputMethodLookupTable::kHorizontal ?
      header_area_ : footer_area_);
  views::View* target_contents = (
      lookup_table_.orientation == InputMethodLookupTable::kHorizontal ?
      header_area_contents_.get() :
      footer_area_contents_.get());

  if (target_contents->parent() != target_area) {
    // If contents not in display area, put it in.
    target_area->RemoveAllChildViews(false);  // Don't delete child views.
    target_area->AddChildView(target_contents);
  }
}

void CandidateWindowView::UpdateAuxiliaryText(const std::string& utf8_text) {
  views::Label* target_label = (
      lookup_table_.orientation == InputMethodLookupTable::kHorizontal ?
      header_label_ : footer_label_);
  target_label->SetText(UTF8ToWide(utf8_text));
}

bool CandidateWindowView::ShouldUpdateCandidateViews(
    const InputMethodLookupTable& old_table,
    const InputMethodLookupTable& new_table) {
  // Check if most table contents are identical.
  if (old_table.page_size == new_table.page_size &&
      old_table.orientation == new_table.orientation &&
      old_table.candidates == new_table.candidates &&
      old_table.labels == new_table.labels &&
      old_table.annotations == new_table.annotations &&
      // Check if the page indexes are identical.
      ComputePageIndex(old_table) == ComputePageIndex(new_table)) {
    // If all of the conditions are met, we don't have to update candidate
    // views.
    return false;
  }
  return true;
}

void CandidateWindowView::UpdateCandidates(
    const InputMethodLookupTable& new_lookup_table) {
  const bool should_update = ShouldUpdateCandidateViews(lookup_table_,
                                                        new_lookup_table);
  // Updating the candidate views is expensive. We'll skip this if possible.
  if (should_update) {
    // Initialize candidate views if necessary.
    MaybeInitializeCandidateViews(new_lookup_table);

    // Compute the index of the current page.
    const int current_page_index = ComputePageIndex(new_lookup_table);
    if (current_page_index < 0) {
      LOG(ERROR) << "Invalid lookup_table: " << new_lookup_table.ToString();
      return;
    }

    // Update the candidates in the current page.
    const size_t start_from = current_page_index * new_lookup_table.page_size;

    // In some cases, engines send empty shortcut labels. For instance,
    // ibus-mozc sends empty labels when they show suggestions. In this
    // case, we should not show shortcut labels.
    const bool no_shortcut_mode =
        (start_from < new_lookup_table.labels.size() &&
         new_lookup_table.labels[start_from] == "");
    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;
      CandidateView* candidate_view = candidate_views_[index_in_page];
      // Set the shortcut text.
      if (no_shortcut_mode) {
        candidate_view->SetShortcutText(L"");
      } else {
        // At this moment, we don't use labels sent from engines for UX
        // reasons. First, we want to show shortcut labels in empty rows
        // (ex. show 6, 7, 8, ... in empty rows when the number of
        // candidates is 5). Second, we want to add a period after each
        // shortcut label when the candidate window is horizontal.
        candidate_view->SetShortcutText(
            CreateShortcutText(i, new_lookup_table.orientation));
      }
      // Set the candidate text.
      if (candidate_index < new_lookup_table.candidates.size() &&
          candidate_index < new_lookup_table.annotations.size()) {
        candidate_view->SetCandidateText(
            UTF8ToWide(new_lookup_table.candidates[candidate_index]));
        candidate_view->SetAnnotationText(
            UTF8ToWide(new_lookup_table.annotations[candidate_index]));
        candidate_view->SetRowEnabled(true);
      } else {
        // Disable the empty row.
        candidate_view->SetCandidateText(L"");
        candidate_view->SetAnnotationText(L"");
        candidate_view->SetRowEnabled(false);
      }
    }
  }
  // Update the current lookup table. We'll use lookup_table_ from here.
  // Note that SelectCandidateAt() uses lookup_table_.
  lookup_table_ = new_lookup_table;

  // Select the current candidate in the page.
  const int current_candidate_in_page =
      lookup_table_.cursor_absolute_index % lookup_table_.page_size;
  SelectCandidateAt(current_candidate_in_page);
}

void CandidateWindowView::MaybeInitializeCandidateViews(
    const InputMethodLookupTable& lookup_table) {
  const InputMethodLookupTable::Orientation orientation =
      lookup_table.orientation;
  const int page_size = lookup_table.page_size;

  // Current column width.
  int shortcut_column_width = 0;
  int candidate_column_width = 0;
  int annotation_column_width = 0;

  // If orientation is horizontal, don't need to compute width,
  // because each label is left aligned.
  if (orientation == InputMethodLookupTable::kVertical) {
    shortcut_column_width = ComputeShortcutColumnWidth(lookup_table);
    candidate_column_width = ComputeCandidateColumnWidth(lookup_table);
    annotation_column_width = ComputeAnnotationColumnWidth(lookup_table);
  }

  // If the requested number of views matches the number of current views, and
  // previous and current column width are same, just reuse these.
  //
  // Note that the early exit logic is not only useful for improving
  // performance, but also necessary for the horizontal candidate window
  // to be redrawn properly. If we get rid of the logic, the horizontal
  // candidate window won't get redrawn properly for some reason when
  // there is no size change. You can test this by removing "return" here
  // and type "ni" with Pinyin input method.
  if (static_cast<int>(candidate_views_.size()) == page_size &&
      lookup_table_.orientation == orientation &&
      previous_shortcut_column_width_ == shortcut_column_width &&
      previous_candidate_column_width_ == candidate_column_width &&
      previous_annotation_column_width_ == annotation_column_width) {
    return;
  }

  // Update the previous column widths.
  previous_shortcut_column_width_ = shortcut_column_width;
  previous_candidate_column_width_ = candidate_column_width;
  previous_annotation_column_width_ = annotation_column_width;

  // 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 == InputMethodLookupTable::kVertical) {
    column_set->AddColumn(views::GridLayout::FILL,
                          views::GridLayout::FILL,
                          0, views::GridLayout::USE_PREF, 0, 0);
  } else {
    for (int i = 0; i < page_size; ++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 == InputMethodLookupTable::kHorizontal) {
    layout->StartRow(0, 0);
  }

  for (int i = 0; i < page_size; ++i) {
    CandidateView* candidate_row = new CandidateView(this, i, orientation);
    candidate_row->Init(shortcut_column_width,
                        candidate_column_width,
                        annotation_column_width);
    candidate_views_.push_back(candidate_row);
    if (orientation == InputMethodLookupTable::kVertical) {
      layout->StartRow(0, 0);
    }
    // |candidate_row| will be owned by |candidate_area_|.
    layout->AddView(candidate_row);
  }

  // Compute views size in |layout|.
  // If we don't call this function, GetHorizontalOffset() often
  // returns invalid value (returns 0), then candidate window
  // moves right from the correct position in MoveParentFrame().
  // TODO(nhiroki): Figure out why it returns invalid value.
  // It seems that the x-position of the candidate labels is not set.
  layout->Layout(candidate_area_);
}

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.

  // |header_label_| will be owned by |header_area_contents_|.
  header_label_ = new views::Label;
  header_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(header_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) {
  const int current_page_index = ComputePageIndex(lookup_table_);
  if (current_page_index < 0) {
    LOG(ERROR) << "Invalid lookup_table: " << lookup_table_.ToString();
    return;
  }

  const int cursor_absolute_index =
      lookup_table_.page_size * current_page_index + index_in_page;
  // Ignore click on out of range views.
  if (cursor_absolute_index < 0 ||
      cursor_absolute_index >=
      static_cast<int>(lookup_table_.candidates.size())) {
    return;
  }

  // Unselect the currently selected candidate.
  candidate_views_[selected_candidate_index_in_page_]->Unselect();
  // Remember the currently selected candidate index in the current page.
  selected_candidate_index_in_page_ = index_in_page;

  // Select the candidate specified by index_in_page.
  candidate_views_[index_in_page]->Select();

  // Update the cursor indexes in the model.
  lookup_table_.cursor_absolute_index = cursor_absolute_index;
}

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::ResizeAndMoveParentFrame() {
  ResizeParentFrame();
  MoveParentFrame();
}

void CandidateWindowView::ResizeParentFrame() {
  // Resize the parent frame, with the current candidate window size.
  gfx::Size size = GetPreferredSize();
  gfx::Rect bounds = parent_frame_->GetClientAreaScreenBounds();
  // SetBounds() is not cheap. Only call this when the size is changed.
  if (bounds.size() != size) {
    bounds.set_size(size);
    parent_frame_->SetBounds(bounds);
  }
}

void CandidateWindowView::MoveParentFrame() {
  const int x = cursor_location_.x();
  const int y = cursor_location_.y();
  const int height = cursor_location_.height();
  const int horizontal_offset = GetHorizontalOffset();

  gfx::Rect frame_bounds = parent_frame_->GetClientAreaScreenBounds();
  gfx::Rect screen_bounds = views::Screen::GetMonitorWorkAreaNearestWindow(
      parent_frame_->GetNativeView());

  // The default position.
  frame_bounds.set_x(x + horizontal_offset);
  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());
  }

  // Move the window per the cursor location.
  parent_frame_->SetBounds(frame_bounds);
}

int CandidateWindowView::GetHorizontalOffset() {
  // Compute the horizontal offset if the lookup table is vertical.
  if (!candidate_views_.empty() &&
      lookup_table_.orientation == InputMethodLookupTable::kVertical) {
    return - candidate_views_[0]->GetCandidateLabelPosition().x();
  }
  return 0;
}

void CandidateWindowView::VisibilityChanged(View* starting_from,
                                            bool is_visible) {
  if (is_visible) {
    // If the visibility of candidate window is changed,
    // we should move the frame to the right position.
    MoveParentFrame();
  }
}

void CandidateWindowView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
  // If the bounds(size) of candidate window is changed,
  // we should move the frame to the right position.
  View::OnBoundsChanged(previous_bounds);
  MoveParentFrame();
}

bool CandidateWindowController::Impl::Init() {
  // Initialize the input method UI status connection.
  InputMethodUiStatusMonitorFunctions functions;
  functions.hide_auxiliary_text =
      &CandidateWindowController::Impl::OnHideAuxiliaryText;
  functions.hide_lookup_table =
      &CandidateWindowController::Impl::OnHideLookupTable;
  functions.set_cursor_location =
      &CandidateWindowController::Impl::OnSetCursorLocation;
  functions.update_auxiliary_text =
      &CandidateWindowController::Impl::OnUpdateAuxiliaryText;
  functions.update_lookup_table =
      &CandidateWindowController::Impl::OnUpdateLookupTable;
  ui_status_connection_ = MonitorInputMethodUiStatus(functions, this);
  if (!ui_status_connection_) {
    LOG(ERROR) << "MonitorInputMethodUiStatus() failed.";
    return false;
  }
  MonitorInputMethodConnection(
      ui_status_connection_,
      &CandidateWindowController::Impl::OnConnectionChange);

  // Create the candidate window view.
  CreateView();

  return true;
}

void CandidateWindowController::Impl::CreateView() {
  // Create a non-decorated frame.
  frame_.reset(views::Widget::CreateWidget(
      views::Widget::CreateParams(views::Widget::CreateParams::TYPE_POPUP)));
  // 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::Impl::Impl()
    : ui_status_connection_(NULL),
      frame_(NULL) {
}

CandidateWindowController::Impl::~Impl() {
  candidate_window_->RemoveObserver(this);
  chromeos::DisconnectInputMethodUiStatus(ui_status_connection_);
}

void CandidateWindowController::Impl::OnHideAuxiliaryText(
    void* input_method_library) {
  CandidateWindowController::Impl* controller =
      static_cast<CandidateWindowController::Impl*>(input_method_library);

  controller->candidate_window_->HideAuxiliaryText();
  controller->candidate_window_->ResizeParentFrame();
}

void CandidateWindowController::Impl::OnHideLookupTable(
    void* input_method_library) {
  CandidateWindowController::Impl* controller =
      static_cast<CandidateWindowController::Impl*>(input_method_library);

  controller->candidate_window_->HideLookupTable();
}

void CandidateWindowController::Impl::OnSetCursorLocation(
    void* input_method_library,
    int x,
    int y,
    int width,
    int height) {
  CandidateWindowController::Impl* controller =
      static_cast<CandidateWindowController::Impl*>(input_method_library);

  // A workaround for http://crosbug.com/6460. We should ignore very short Y
  // move to prevent the window from shaking up and down.
  const int kKeepPositionThreshold = 2;  // px
  const gfx::Rect& last_location =
      controller->candidate_window_->cursor_location();
  const int delta_y = abs(last_location.y() - y);
  if ((last_location.x() == x) && (delta_y <= kKeepPositionThreshold)) {
    DLOG(INFO) << "Ignored set_cursor_location signal to prevent window shake";
    return;
  }

  // Remember the cursor location.
  controller->candidate_window_->set_cursor_location(
      gfx::Rect(x, y, width, height));
  // Move the window per the cursor location.
  controller->candidate_window_->MoveParentFrame();
}

void CandidateWindowController::Impl::OnUpdateAuxiliaryText(
    void* input_method_library,
    const std::string& utf8_text,
    bool visible) {
  CandidateWindowController::Impl* controller =
      static_cast<CandidateWindowController::Impl*>(input_method_library);
  // 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();
  controller->candidate_window_->ResizeParentFrame();
}

void CandidateWindowController::Impl::OnUpdateLookupTable(
    void* input_method_library,
    const InputMethodLookupTable& lookup_table) {
  CandidateWindowController::Impl* controller =
      static_cast<CandidateWindowController::Impl*>(input_method_library);

  // If it's not visible, hide the window and return.
  if (!lookup_table.visible) {
    controller->candidate_window_->HideLookupTable();
    return;
  }

  controller->candidate_window_->UpdateCandidates(lookup_table);
  controller->candidate_window_->ResizeParentFrame();
  controller->frame_->Show();
}

void CandidateWindowController::Impl::OnCandidateCommitted(int index,
                                                           int button,
                                                           int flags) {
  NotifyCandidateClicked(ui_status_connection_, index, button, flags);
}

void CandidateWindowController::Impl::OnConnectionChange(
    void* input_method_library,
    bool connected) {
  if (!connected) {
    CandidateWindowController::Impl* controller =
        static_cast<CandidateWindowController::Impl*>(input_method_library);
    controller->candidate_window_->HideLookupTable();
  }
}

CandidateWindowController::CandidateWindowController()
    : impl_(new CandidateWindowController::Impl) {
}

CandidateWindowController::~CandidateWindowController() {
  delete impl_;
}

bool CandidateWindowController::Init() {
  return impl_->Init();
}

}  // namespace chromeos