Home | History | Annotate | Download | only in input_method
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/input_method/candidate_window.h"
      6 
      7 #include <algorithm>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/logging.h"
     12 #include "base/memory/scoped_ptr.h"
     13 #include "base/observer_list.h"
     14 #include "base/string_util.h"
     15 #include "base/stringprintf.h"
     16 #include "base/utf_string_conversions.h"
     17 #include "third_party/cros/chromeos_input_method_ui.h"
     18 #include "ui/gfx/canvas.h"
     19 #include "ui/gfx/font.h"
     20 #include "views/controls/label.h"
     21 #include "views/controls/textfield/textfield.h"
     22 #include "views/events/event.h"
     23 #include "views/layout/fill_layout.h"
     24 #include "views/layout/grid_layout.h"
     25 #include "views/screen.h"
     26 #include "views/widget/root_view.h"
     27 #include "views/widget/widget.h"
     28 #include "views/widget/widget_gtk.h"
     29 #include "views/window/non_client_view.h"
     30 #include "views/window/window.h"
     31 #include "views/window/window_delegate.h"
     32 
     33 namespace {
     34 
     35 // Colors used in the candidate window UI.
     36 const SkColor kFrameColor = SkColorSetRGB(0x96, 0x96, 0x96);
     37 const SkColor kShortcutBackgroundColor = SkColorSetARGB(0x10, 0x3, 0x4, 0xf);
     38 const SkColor kSelectedRowBackgroundColor = SkColorSetRGB(0xd1, 0xea, 0xff);
     39 const SkColor kDefaultBackgroundColor = SkColorSetRGB(0xff, 0xff, 0xff);
     40 const SkColor kSelectedRowFrameColor = SkColorSetRGB(0x7f, 0xac, 0xdd);
     41 const SkColor kFooterTopColor = SkColorSetRGB(0xff, 0xff, 0xff);
     42 const SkColor kFooterBottomColor = SkColorSetRGB(0xee, 0xee, 0xee);
     43 const SkColor kShortcutColor = SkColorSetRGB(0x61, 0x61, 0x61);
     44 const SkColor kDisabledShortcutColor = SkColorSetRGB(0xcc, 0xcc, 0xcc);
     45 const SkColor kAnnotationColor = SkColorSetRGB(0x88, 0x88, 0x88);
     46 
     47 // We'll use a bigger font size, so Chinese characters are more readable
     48 // in the candidate window.
     49 #if defined(CROS_FONTS_USING_BCI)
     50 const int kFontSizeDelta = 1;
     51 #else
     52 const int kFontSizeDelta = 2;
     53 #endif
     54 
     55 // The minimum width of candidate labels in the vertical candidate
     56 // window. We use this value to prevent the candidate window from being
     57 // too narrow when all candidates are short.
     58 const int kMinCandidateLabelWidth = 100;
     59 // The maximum width of candidate labels in the vertical candidate
     60 // window. We use this value to prevent the candidate window from being
     61 // too wide when one of candidates are long.
     62 const int kMaxCandidateLabelWidth = 500;
     63 
     64 // VerticalCandidateLabel is used for rendering candidate text in
     65 // the vertical candidate window.
     66 class VerticalCandidateLabel : public views::Label {
     67   virtual ~VerticalCandidateLabel() {}
     68 
     69   // Returns the preferred size, but guarantees that the width has at
     70   // least kMinCandidateLabelWidth pixels.
     71   virtual gfx::Size GetPreferredSize() {
     72     gfx::Size size = Label::GetPreferredSize();
     73     // Hack. +2 is needed to prevent labels from getting elided like
     74     // "abc..." in some cases. TODO(satorux): Figure out why it's
     75     // necessary.
     76     size.set_width(size.width() + 2);
     77     if (size.width() < kMinCandidateLabelWidth) {
     78       size.set_width(kMinCandidateLabelWidth);
     79     }
     80     if (size.width() > kMaxCandidateLabelWidth) {
     81       size.set_width(kMaxCandidateLabelWidth);
     82     }
     83     return size;
     84   }
     85 };
     86 
     87 // Wraps the given view with some padding, and returns it.
     88 views::View* WrapWithPadding(views::View* view, const gfx::Insets& insets) {
     89   views::View* wrapper = new views::View;
     90   // Use GridLayout to give some insets inside.
     91   views::GridLayout* layout = new views::GridLayout(wrapper);
     92   wrapper->SetLayoutManager(layout);  // |wrapper| owns |layout|.
     93   layout->SetInsets(insets);
     94 
     95   views::ColumnSet* column_set = layout->AddColumnSet(0);
     96   column_set->AddColumn(
     97       views::GridLayout::FILL, views::GridLayout::FILL,
     98       1, views::GridLayout::USE_PREF, 0, 0);
     99   layout->StartRow(0, 0);
    100 
    101   // Add the view contents.
    102   layout->AddView(view);  // |view| is owned by |wraper|, not |layout|.
    103   return wrapper;
    104 }
    105 
    106 // Creates shortcut text from the given index and the orientation.
    107 std::wstring CreateShortcutText(int index,
    108     chromeos::InputMethodLookupTable::Orientation orientation) {
    109   // Choose the character used for the shortcut label.
    110   const wchar_t kShortcutCharacters[] = L"1234567890ABCDEF";
    111   // The default character should not be used but just in case.
    112   wchar_t shortcut_character = L'?';
    113   // -1 to exclude the null character at the end.
    114   if (index < static_cast<int>(arraysize(kShortcutCharacters) - 1)) {
    115     shortcut_character = kShortcutCharacters[index];
    116   }
    117 
    118   std::wstring shortcut_text;
    119   if (orientation == chromeos::InputMethodLookupTable::kVertical) {
    120     shortcut_text = base::StringPrintf(L"%lc", shortcut_character);
    121   } else {
    122     shortcut_text = base::StringPrintf(L"%lc.", shortcut_character);
    123   }
    124 
    125   return shortcut_text;
    126 }
    127 
    128 // Creates the shortcut label, and returns it (never returns NULL).
    129 // The label text is not set in this function.
    130 views::Label* CreateShortcutLabel(
    131     chromeos::InputMethodLookupTable::Orientation orientation) {
    132   // Create the shortcut label. The label will be owned by
    133   // |wrapped_shortcut_label|, hence it's deleted when
    134   // |wrapped_shortcut_label| is deleted.
    135   views::Label* shortcut_label = new views::Label;
    136 
    137   if (orientation == chromeos::InputMethodLookupTable::kVertical) {
    138     shortcut_label->SetFont(
    139         shortcut_label->font().DeriveFont(kFontSizeDelta, gfx::Font::BOLD));
    140   } else {
    141     shortcut_label->SetFont(
    142         shortcut_label->font().DeriveFont(kFontSizeDelta));
    143   }
    144   // TODO(satorux): Maybe we need to use language specific fonts for
    145   // candidate_label, like Chinese font for Chinese input method?
    146   shortcut_label->SetColor(kShortcutColor);
    147 
    148   return shortcut_label;
    149 }
    150 
    151 // Wraps the shortcut label, then decorates wrapped shortcut label
    152 // and returns it (never returns NULL).
    153 // The label text is not set in this function.
    154 views::View* CreateWrappedShortcutLabel(views::Label* shortcut_label,
    155     chromeos::InputMethodLookupTable::Orientation orientation) {
    156   // Wrap it with padding.
    157   const gfx::Insets kVerticalShortcutLabelInsets(1, 6, 1, 6);
    158   const gfx::Insets kHorizontalShortcutLabelInsets(1, 3, 1, 0);
    159   const gfx::Insets insets =
    160       (orientation == chromeos::InputMethodLookupTable::kVertical ?
    161        kVerticalShortcutLabelInsets :
    162        kHorizontalShortcutLabelInsets);
    163   views::View* wrapped_shortcut_label =
    164       WrapWithPadding(shortcut_label, insets);
    165 
    166   // Add decoration based on the orientation.
    167   if (orientation == chromeos::InputMethodLookupTable::kVertical) {
    168     // Set the background color.
    169     wrapped_shortcut_label->set_background(
    170         views::Background::CreateSolidBackground(
    171             kShortcutBackgroundColor));
    172   }
    173 
    174   return wrapped_shortcut_label;
    175 }
    176 
    177 // Creates the candidate label, and returns it (never returns NULL).
    178 // The label text is not set in this function.
    179 views::Label* CreateCandidateLabel(
    180     chromeos::InputMethodLookupTable::Orientation orientation) {
    181   views::Label* candidate_label = NULL;
    182 
    183   // Create the candidate label. The label will be added to |this| as a
    184   // child view, hence it's deleted when |this| is deleted.
    185   if (orientation == chromeos::InputMethodLookupTable::kVertical) {
    186     candidate_label = new VerticalCandidateLabel;
    187   } else {
    188     candidate_label = new views::Label;
    189   }
    190 
    191   // Change the font size.
    192   candidate_label->SetFont(
    193       candidate_label->font().DeriveFont(kFontSizeDelta));
    194   candidate_label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    195 
    196   return candidate_label;
    197 }
    198 
    199 // Creates the annotation label, and return it (never returns NULL).
    200 // The label text is not set in this function.
    201 views::Label* CreateAnnotationLabel(
    202     chromeos::InputMethodLookupTable::Orientation orientation) {
    203   // Create the annotation label.
    204   views::Label* annotation_label = new views::Label;
    205 
    206   // Change the font size and color.
    207   annotation_label->SetFont(
    208       annotation_label->font().DeriveFont(kFontSizeDelta));
    209   annotation_label->SetColor(kAnnotationColor);
    210   annotation_label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    211 
    212   return annotation_label;
    213 }
    214 
    215 // Computes shortcut column width.
    216 int ComputeShortcutColumnWidth(
    217     const chromeos::InputMethodLookupTable& lookup_table) {
    218   int shortcut_column_width = 0;
    219   // Create the shortcut label. The label will be owned by
    220   // |wrapped_shortcut_label|, hence it's deleted when
    221   // |wrapped_shortcut_label| is deleted.
    222   views::Label* shortcut_label = CreateShortcutLabel(lookup_table.orientation);
    223   scoped_ptr<views::View> wrapped_shortcut_label(
    224       CreateWrappedShortcutLabel(shortcut_label, lookup_table.orientation));
    225 
    226   // Compute the max width in shortcut labels.
    227   // We'll create temporary shortcut labels, and choose the largest width.
    228   for (int i = 0; i < lookup_table.page_size; ++i) {
    229     shortcut_label->SetText(
    230         CreateShortcutText(i, lookup_table.orientation));
    231     shortcut_column_width =
    232         std::max(shortcut_column_width,
    233                  wrapped_shortcut_label->GetPreferredSize().width());
    234   }
    235 
    236   return shortcut_column_width;
    237 }
    238 
    239 // Computes the page index. For instance, if the page size is 9, and the
    240 // cursor is pointing to 13th candidate, the page index will be 1 (2nd
    241 // page, as the index is zero-origin). Returns -1 on error.
    242 int ComputePageIndex(const chromeos::InputMethodLookupTable& lookup_table) {
    243   if (lookup_table.page_size > 0)
    244     return lookup_table.cursor_absolute_index / lookup_table.page_size;
    245   return -1;
    246 }
    247 
    248 // Computes candidate column width.
    249 int ComputeCandidateColumnWidth(
    250     const chromeos::InputMethodLookupTable& lookup_table) {
    251   int candidate_column_width = 0;
    252   scoped_ptr<views::Label> candidate_label(
    253       CreateCandidateLabel(lookup_table.orientation));
    254 
    255   // Compute the start index of |lookup_table_|.
    256   const int current_page_index = ComputePageIndex(lookup_table);
    257   if (current_page_index < 0)
    258     return 0;
    259   const size_t start_from = current_page_index * lookup_table.page_size;
    260 
    261   // Compute the max width in candidate labels.
    262   // We'll create temporary candidate labels, and choose the largest width.
    263   for (size_t i = 0; i + start_from < lookup_table.candidates.size(); ++i) {
    264     const size_t index = start_from + i;
    265 
    266     candidate_label->SetText(
    267         UTF8ToWide(lookup_table.candidates[index]));
    268     candidate_column_width =
    269         std::max(candidate_column_width,
    270                  candidate_label->GetPreferredSize().width());
    271   }
    272 
    273   return candidate_column_width;
    274 }
    275 
    276 // Computes annotation column width.
    277 int ComputeAnnotationColumnWidth(
    278     const chromeos::InputMethodLookupTable& lookup_table) {
    279   int annotation_column_width = 0;
    280   scoped_ptr<views::Label> annotation_label(
    281       CreateAnnotationLabel(lookup_table.orientation));
    282 
    283   // Compute the start index of |lookup_table_|.
    284   const int current_page_index = ComputePageIndex(lookup_table);
    285   if (current_page_index < 0)
    286     return 0;
    287   const size_t start_from = current_page_index * lookup_table.page_size;
    288 
    289   // Compute max width in annotation labels.
    290   // We'll create temporary annotation labels, and choose the largest width.
    291   for (size_t i = 0; i + start_from < lookup_table.annotations.size(); ++i) {
    292     const size_t index = start_from + i;
    293 
    294     annotation_label->SetText(
    295         UTF8ToWide(lookup_table.annotations[index]));
    296     annotation_column_width =
    297         std::max(annotation_column_width,
    298                  annotation_label->GetPreferredSize().width());
    299   }
    300 
    301   return annotation_column_width;
    302 }
    303 
    304 }  // namespace
    305 
    306 namespace chromeos {
    307 
    308 class CandidateView;
    309 
    310 // CandidateWindowView is the main container of the candidate window UI.
    311 class CandidateWindowView : public views::View {
    312  public:
    313   // The object can be monitored by the observer.
    314   class Observer {
    315    public:
    316     virtual ~Observer() {}
    317     // The function is called when a candidate is committed.
    318     // See comments at NotifyCandidateClicke() in chromeos_input_method_ui.h for
    319     // details about the parameters.
    320     virtual void OnCandidateCommitted(int index, int button, int flag) = 0;
    321   };
    322 
    323   explicit CandidateWindowView(views::Widget* parent_frame);
    324   virtual ~CandidateWindowView() {}
    325   void Init();
    326 
    327   // Adds the given observer. The ownership is not transferred.
    328   void AddObserver(Observer* observer) {
    329     observers_.AddObserver(observer);
    330   }
    331 
    332   // Removes the given observer.
    333   void RemoveObserver(Observer* observer) {
    334     observers_.RemoveObserver(observer);
    335   }
    336 
    337   // Selects the candidate specified by the index in the current page
    338   // (zero-origin).  Changes the appearance of the selected candidate,
    339   // updates the information in the candidate window as needed.
    340   void SelectCandidateAt(int index_in_page);
    341 
    342   // The function is called when a candidate is being dragged. From the
    343   // given point, locates the candidate under the mouse cursor, and
    344   // selects it.
    345   void OnCandidateDragged(const gfx::Point& point);
    346 
    347   // Commits the candidate currently being selected.
    348   void CommitCandidate();
    349 
    350   // Hides the lookup table.
    351   void HideLookupTable();
    352 
    353   // Hides the auxiliary text.
    354   void HideAuxiliaryText();
    355 
    356   // Shows the auxiliary text.
    357   void ShowAuxiliaryText();
    358 
    359   // Updates the auxiliary text.
    360   void UpdateAuxiliaryText(const std::string& utf8_text);
    361 
    362   // Returns true if we should update candidate views in the window.  For
    363   // instance, if we are going to show the same candidates as before, we
    364   // don't have to update candidate views. This happens when the user just
    365   // moves the cursor in the same page in the candidate window.
    366   bool ShouldUpdateCandidateViews(
    367       const InputMethodLookupTable& old_table,
    368       const InputMethodLookupTable& new_table);
    369 
    370   // Updates candidates of the candidate window from |lookup_table|.
    371   // Candidates are arranged per |orientation|.
    372   void UpdateCandidates(const InputMethodLookupTable& lookup_table);
    373 
    374   // Resizes and moves the parent frame. The two actions should be
    375   // performed consecutively as resizing may require the candidate window
    376   // to move. For instance, we may need to move the candidate window from
    377   // below the cursor to above the cursor, if the candidate window becomes
    378   // too big to be shown near the bottom of the screen.  This function
    379   // needs to be called when the visible contents of the candidate window
    380   // are modified.
    381   void ResizeAndMoveParentFrame();
    382 
    383   // Resizes the parent frame per the current contents size.
    384   //
    385   // The function is rarely used solely. See comments at
    386   // ResizeAndMoveParentFrame().
    387   void ResizeParentFrame();
    388 
    389   // Moves the candidate window per the current cursor location, and the
    390   // horizontal offset.
    391   //
    392   // The function is rarely used solely. See comments at
    393   // ResizeAndMoveParentFrame().
    394   void MoveParentFrame();
    395 
    396   // Returns the horizontal offset used for placing the vertical candidate
    397   // window so that the first candidate is aligned with the the text being
    398   // converted like:
    399   //
    400   //      XXX           <- The user is converting XXX
    401   //   +-----+
    402   //   |1 XXX|
    403   //   |2 YYY|
    404   //   |3 ZZZ|
    405   //
    406   // Returns 0 if no candidate is present.
    407   int GetHorizontalOffset();
    408 
    409   // A function to be called when one of the |candidate_views_| receives a mouse
    410   // press event.
    411   void OnMousePressed();
    412   // A function to be called when one of the |candidate_views_| receives a mouse
    413   // release event.
    414   void OnMouseReleased();
    415 
    416   void set_cursor_location(const gfx::Rect& cursor_location) {
    417     cursor_location_ = cursor_location;
    418   }
    419 
    420   const gfx::Rect& cursor_location() const { return cursor_location_; }
    421 
    422  protected:
    423   // Override View::VisibilityChanged()
    424   virtual void VisibilityChanged(View* starting_from, bool is_visible) OVERRIDE;
    425 
    426   // Override View::OnBoundsChanged()
    427   virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
    428 
    429  private:
    430   // Initializes the candidate views if needed.
    431   void MaybeInitializeCandidateViews(
    432       const InputMethodLookupTable& lookup_table);
    433 
    434   // Creates the footer area, where we show status information.
    435   // For instance, we show a cursor position like 2/19.
    436   views::View* CreateFooterArea();
    437 
    438   // Creates the header area, where we show auxiliary text.
    439   views::View* CreateHeaderArea();
    440 
    441   // The lookup table (candidates).
    442   InputMethodLookupTable lookup_table_;
    443 
    444   // The index in the current page of the candidate currently being selected.
    445   int selected_candidate_index_in_page_;
    446 
    447   // The observers of the object.
    448   ObserverList<Observer> observers_;
    449 
    450   // The parent frame.
    451   views::Widget* parent_frame_;
    452 
    453   // Views created in the class will be part of tree of |this|, so these
    454   // child views will be deleted when |this| is deleted.
    455 
    456   // The candidate area is where candidates are rendered.
    457   views::View* candidate_area_;
    458   // The footer area is where the auxiliary text is shown, if the
    459   // orientation is vertical. Usually the auxiliary text is used for
    460   // showing candidate number information like 2/19.
    461   views::View* footer_area_;
    462   // We use this when we show something in the footer area.
    463   scoped_ptr<views::View> footer_area_contents_;
    464   // We use this when we show nothing in the footer area.
    465   scoped_ptr<views::View> footer_area_place_holder_;
    466   // The header area is where the auxiliary text is shown, if the
    467   // orientation is horizontal. If the auxiliary text is not provided, we
    468   // show nothing.  For instance, we show pinyin text like "zhong'guo".
    469   views::View* header_area_;
    470   // We use this when we show something in the header area.
    471   scoped_ptr<views::View> header_area_contents_;
    472   // We use this when we show nothing in the header area.
    473   scoped_ptr<views::View> header_area_place_holder_;
    474   // The candidate views are used for rendering candidates.
    475   std::vector<CandidateView*> candidate_views_;
    476   // The header label is shown in the header area.
    477   views::Label* header_label_;
    478   // The footer label is shown in the footer area.
    479   views::Label* footer_label_;
    480 
    481   // Current columns width in |candidate_area_|.
    482   int previous_shortcut_column_width_;
    483   int previous_candidate_column_width_;
    484   int previous_annotation_column_width_;
    485 
    486   // The last cursor location.
    487   gfx::Rect cursor_location_;
    488 
    489   // true if a mouse button is pressed, and is not yet released.
    490   bool mouse_is_pressed_;
    491 };
    492 
    493 // CandidateRow renderes a row of a candidate.
    494 class CandidateView : public views::View {
    495  public:
    496   CandidateView(CandidateWindowView* parent_candidate_window,
    497                 int index_in_page,
    498                 InputMethodLookupTable::Orientation orientation);
    499   virtual ~CandidateView() {}
    500   // Initializes the candidate view with the given column widths.
    501   // A width of 0 means that the column is resizable.
    502   void Init(int shortcut_column_width,
    503             int candidate_column_width,
    504             int annotation_column_width);
    505 
    506   // Sets candidate text to the given text.
    507   void SetCandidateText(const std::wstring& text);
    508 
    509   // Sets shortcut text to the given text.
    510   void SetShortcutText(const std::wstring& text);
    511 
    512   // Sets annotation text to the given text.
    513   void SetAnnotationText(const std::wstring& text);
    514 
    515   // Selects the candidate row. Changes the appearance to make it look
    516   // like a selected candidate.
    517   void Select();
    518 
    519   // Unselects the candidate row. Changes the appearance to make it look
    520   // like an unselected candidate.
    521   void Unselect();
    522 
    523   // Enables or disables the candidate row based on |enabled|. Changes the
    524   // appearance to make it look like unclickable area.
    525   void SetRowEnabled(bool enabled);
    526 
    527   // Returns the relative position of the candidate label.
    528   gfx::Point GetCandidateLabelPosition() const;
    529 
    530  private:
    531   // Overridden from View:
    532   virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE;
    533   virtual bool OnMouseDragged(const views::MouseEvent& event) OVERRIDE;
    534   virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE;
    535   virtual void OnMouseCaptureLost() OVERRIDE;
    536 
    537   // Zero-origin index in the current page.
    538   int index_in_page_;
    539 
    540   // The orientation of the candidate view.
    541   InputMethodLookupTable::Orientation orientation_;
    542 
    543   // The parent candidate window that contains this view.
    544   CandidateWindowView* parent_candidate_window_;
    545 
    546   // Views created in the class will be part of tree of |this|, so these
    547   // child views will be deleted when |this| is deleted.
    548 
    549   // The shortcut label renders shortcut numbers like 1, 2, and 3.
    550   views::Label* shortcut_label_;
    551   // The candidate label renders candidates.
    552   views::Label* candidate_label_;
    553   // The annotation label renders annotations.
    554   views::Label* annotation_label_;
    555 };
    556 
    557 // The implementation of CandidateWindowController.
    558 // CandidateWindowController controls the CandidateWindow.
    559 class CandidateWindowController::Impl : public CandidateWindowView::Observer {
    560  public:
    561   Impl();
    562   virtual ~Impl();
    563 
    564   // Initializes the candidate window. Returns true on success.
    565   bool Init();
    566 
    567  private:
    568   // CandidateWindowView::Observer implementation.
    569   virtual void OnCandidateCommitted(int index,
    570                                     int button,
    571                                     int flags);
    572 
    573   // Creates the candidate window view.
    574   void CreateView();
    575 
    576   // The function is called when |HideAuxiliaryText| signal is received in
    577   // libcros. |input_method_library| is a void pointer to this object.
    578   static void OnHideAuxiliaryText(void* input_method_library);
    579 
    580   // The function is called when |HideLookupTable| signal is received in
    581   // libcros. |input_method_library| is a void pointer to this object.
    582   static void OnHideLookupTable(void* input_method_library);
    583 
    584   // The function is called when |SetCursorLocation| signal is received
    585   // in libcros. |input_method_library| is a void pointer to this object.
    586   static void OnSetCursorLocation(void* input_method_library,
    587                                   int x,
    588                                   int y,
    589                                   int width,
    590                                   int height);
    591 
    592   // The function is called when |UpdateAuxiliaryText| signal is received
    593   // in libcros. |input_method_library| is a void pointer to this object.
    594   static void OnUpdateAuxiliaryText(void* input_method_library,
    595                                     const std::string& utf8_text,
    596                                     bool visible);
    597 
    598   // The function is called when |UpdateLookupTable| signal is received
    599   // in libcros. |input_method_library| is a void pointer to this object.
    600   static void OnUpdateLookupTable(void* input_method_library,
    601                                   const InputMethodLookupTable& lookup_table);
    602 
    603   // This function is called by libcros when ibus connects or disconnects.
    604   // |input_method_library| is a void pointer to this object.
    605   static void OnConnectionChange(void* input_method_library, bool connected);
    606 
    607   // The connection is used for communicating with input method UI logic
    608   // in libcros.
    609   InputMethodUiStatusConnection* ui_status_connection_;
    610 
    611   // The candidate window view.
    612   CandidateWindowView* candidate_window_;
    613 
    614   // This is the outer frame of the candidate window view. The frame will
    615   // own |candidate_window_|.
    616   scoped_ptr<views::Widget> frame_;
    617 };
    618 
    619 CandidateView::CandidateView(
    620     CandidateWindowView* parent_candidate_window,
    621     int index_in_page,
    622     InputMethodLookupTable::Orientation orientation)
    623     : index_in_page_(index_in_page),
    624       orientation_(orientation),
    625       parent_candidate_window_(parent_candidate_window),
    626       shortcut_label_(NULL),
    627       candidate_label_(NULL),
    628       annotation_label_(NULL) {
    629 }
    630 
    631 void CandidateView::Init(int shortcut_column_width,
    632                          int candidate_column_width,
    633                          int annotation_column_width) {
    634   views::GridLayout* layout = new views::GridLayout(this);
    635   SetLayoutManager(layout);  // |this| owns |layout|.
    636 
    637   // Create Labels.
    638   shortcut_label_ = CreateShortcutLabel(orientation_);
    639   views::View* wrapped_shortcut_label =
    640       CreateWrappedShortcutLabel(shortcut_label_, orientation_);
    641   candidate_label_ = CreateCandidateLabel(orientation_);
    642   annotation_label_ = CreateAnnotationLabel(orientation_);
    643 
    644   // Initialize the column set with three columns.
    645   views::ColumnSet* column_set = layout->AddColumnSet(0);
    646 
    647   // If orientation is vertical, each column width is fixed.
    648   // Otherwise the width is resizable.
    649   const views::GridLayout::SizeType column_type =
    650       orientation_ == InputMethodLookupTable::kVertical ?
    651           views::GridLayout::FIXED : views::GridLayout::USE_PREF;
    652 
    653   const int padding_column_width =
    654       orientation_ == InputMethodLookupTable::kVertical ? 4 : 6;
    655 
    656   // Set shortcut column type and width.
    657   column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
    658                         0, column_type, shortcut_column_width, 0);
    659   column_set->AddPaddingColumn(0, padding_column_width);
    660 
    661   // Set candidate column type and width.
    662   column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
    663                         0, column_type, candidate_column_width, 0);
    664   column_set->AddPaddingColumn(0, padding_column_width);
    665 
    666   // Set annotation column type and width.
    667   column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
    668                         0, column_type, annotation_column_width, 0);
    669   column_set->AddPaddingColumn(0, padding_column_width);
    670 
    671   // Add the shortcut label, the candidate label, and annotation label.
    672   layout->StartRow(0, 0);
    673   // |wrapped_shortcut_label|, |candidate_label_|, and |annotation_label_|
    674   // will be owned by |this|.
    675   layout->AddView(wrapped_shortcut_label);
    676   layout->AddView(candidate_label_);
    677   layout->AddView(annotation_label_);
    678 }
    679 
    680 void CandidateView::SetCandidateText(const std::wstring& text) {
    681   candidate_label_->SetText(text);
    682 }
    683 
    684 void CandidateView::SetShortcutText(const std::wstring& text) {
    685   shortcut_label_->SetText(text);
    686 }
    687 
    688 void CandidateView::SetAnnotationText(const std::wstring& text) {
    689   annotation_label_->SetText(text);
    690 }
    691 
    692 void CandidateView::Select() {
    693   set_background(
    694       views::Background::CreateSolidBackground(kSelectedRowBackgroundColor));
    695   set_border(views::Border::CreateSolidBorder(1, kSelectedRowFrameColor));
    696   // Need to call SchedulePaint() for background and border color changes.
    697   SchedulePaint();
    698 }
    699 
    700 void CandidateView::Unselect() {
    701   set_background(NULL);
    702   set_border(NULL);
    703   SchedulePaint();  // See comments at Select().
    704 }
    705 
    706 void CandidateView::SetRowEnabled(bool enabled) {
    707   shortcut_label_->SetColor(
    708       enabled ? kShortcutColor : kDisabledShortcutColor);
    709 }
    710 
    711 gfx::Point CandidateView::GetCandidateLabelPosition() const {
    712   return candidate_label_->GetMirroredPosition();
    713 }
    714 
    715 bool CandidateView::OnMousePressed(const views::MouseEvent& event) {
    716   parent_candidate_window_->OnMousePressed();
    717   // Select the candidate. We'll commit the candidate when the mouse
    718   // button is released.
    719   parent_candidate_window_->SelectCandidateAt(index_in_page_);
    720   // Request MouseDraggged and MouseReleased events.
    721   return true;
    722 }
    723 
    724 bool CandidateView::OnMouseDragged(const views::MouseEvent& event) {
    725   gfx::Point location_in_candidate_window = event.location();
    726   views::View::ConvertPointToView(this, parent_candidate_window_,
    727                                   &location_in_candidate_window);
    728   // Notify the candidate window that a candidate is now being dragged.
    729   parent_candidate_window_->OnCandidateDragged(location_in_candidate_window);
    730   // Request MouseReleased event.
    731   return true;
    732 }
    733 
    734 void CandidateView::OnMouseReleased(const views::MouseEvent& event) {
    735   // Commit the current candidate.
    736   parent_candidate_window_->CommitCandidate();
    737   OnMouseCaptureLost();
    738 }
    739 
    740 void CandidateView::OnMouseCaptureLost() {
    741   parent_candidate_window_->OnMouseReleased();
    742 }
    743 
    744 CandidateWindowView::CandidateWindowView(
    745     views::Widget* parent_frame)
    746     : selected_candidate_index_in_page_(0),
    747       parent_frame_(parent_frame),
    748       candidate_area_(NULL),
    749       footer_area_(NULL),
    750       header_area_(NULL),
    751       header_label_(NULL),
    752       footer_label_(NULL),
    753       previous_shortcut_column_width_(0),
    754       previous_candidate_column_width_(0),
    755       previous_annotation_column_width_(0),
    756       mouse_is_pressed_(false) {
    757 }
    758 
    759 void CandidateWindowView::Init() {
    760   // Set the background and the border of the view.
    761   set_background(
    762       views::Background::CreateSolidBackground(kDefaultBackgroundColor));
    763   set_border(views::Border::CreateSolidBorder(1, kFrameColor));
    764 
    765   // Create the header area.
    766   header_area_ = CreateHeaderArea();
    767   // Create the candidate area.
    768   candidate_area_ = new views::View;
    769   // Create the footer area.
    770   footer_area_ = CreateFooterArea();
    771 
    772   // Set the window layout of the view
    773   views::GridLayout* layout = new views::GridLayout(this);
    774   SetLayoutManager(layout);  // |this| owns layout|.
    775   views::ColumnSet* column_set = layout->AddColumnSet(0);
    776   column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
    777                         0, views::GridLayout::USE_PREF, 0, 0);
    778 
    779   // Add the header area.
    780   layout->StartRow(0, 0);
    781   layout->AddView(header_area_);  // |header_area_| is owned by |this|.
    782 
    783   // Add the candidate area.
    784   layout->StartRow(0, 0);
    785   layout->AddView(candidate_area_);  // |candidate_area_| is owned by |this|.
    786 
    787   // Add the footer area.
    788   layout->StartRow(0, 0);
    789   layout->AddView(footer_area_);  // |footer_area_| is owned by |this|.
    790 }
    791 
    792 void CandidateWindowView::HideLookupTable() {
    793   if (!mouse_is_pressed_) {
    794     parent_frame_->Hide();
    795     return;
    796   }
    797 
    798   // We should not hide the |frame_| when a mouse is pressed, so we don't run
    799   // into issues below.
    800   //
    801   // First, in the following scenario, it seems that the Views popup window does
    802   // not release mouse/keyboard grab even after it gets hidden.
    803   //
    804   // 1. create a popup window by views::Widget::CreateWidget() with the
    805   //    accept_events flag set to true on the CreateParams.
    806   // 2. press a mouse button on the window.
    807   // 3. before releasing the mouse button, Hide() the window.
    808   // 4. release the button.
    809   //
    810   // And if we embed IME candidate window into Chrome, the window sometimes
    811   // receives an extra 'hide-lookup-table' event before mouse button is
    812   // released:
    813   //
    814   // 1. the candidate window is clicked.
    815   // 2. The mouse click handler in this file, OnMousePressed() in CandidateView,
    816   //    is called, and the handler consumes the event by returning true.
    817   // 3. HOWEVER, if the candidate window is embedded into Chrome, the event is
    818   //    also sent to Chrome! (problem #1)
    819   // 4. im-ibus.so in Chrome sends 'focus-out' event to ibus-daemon.
    820   // 5. ibus-daemon sends 'hide-lookup-table' event to the candidate window.
    821   // 6. the window is hidden, but the window does not release mouse/keyboard
    822   //    grab! (problem #2)
    823   // 7. mouse button is released.
    824   // 8. now all mouse/keyboard events are consumed by the hidden popup, and are
    825   //    not sent to Chrome.
    826   //
    827   // TODO(yusukes): investigate why the click event is sent to both candidate
    828   // window and Chrome. http://crosbug.com/11423
    829   // TODO(yusukes): investigate if we could fix Views so it always releases grab
    830   // when a popup window gets hidden. http://crosbug.com/11422
    831   //
    832   LOG(WARNING) << "Can't hide the table since a mouse button is not released.";
    833 }
    834 
    835 void CandidateWindowView::OnMousePressed() {
    836   mouse_is_pressed_ = true;
    837 }
    838 
    839 void CandidateWindowView::OnMouseReleased() {
    840   mouse_is_pressed_ = false;
    841 }
    842 
    843 void CandidateWindowView::HideAuxiliaryText() {
    844   views::View* target_area = (
    845       lookup_table_.orientation == InputMethodLookupTable::kHorizontal ?
    846       header_area_ : footer_area_);
    847   views::View* target_place_holder = (
    848       lookup_table_.orientation == InputMethodLookupTable::kHorizontal ?
    849       header_area_place_holder_.get() :
    850       footer_area_place_holder_.get());
    851   // Put the place holder to the target display area.
    852   target_area->RemoveAllChildViews(false);  // Don't delete child views.
    853   target_area->AddChildView(target_place_holder);
    854 }
    855 
    856 void CandidateWindowView::ShowAuxiliaryText() {
    857   views::View* target_area = (
    858       lookup_table_.orientation == InputMethodLookupTable::kHorizontal ?
    859       header_area_ : footer_area_);
    860   views::View* target_contents = (
    861       lookup_table_.orientation == InputMethodLookupTable::kHorizontal ?
    862       header_area_contents_.get() :
    863       footer_area_contents_.get());
    864 
    865   if (target_contents->parent() != target_area) {
    866     // If contents not in display area, put it in.
    867     target_area->RemoveAllChildViews(false);  // Don't delete child views.
    868     target_area->AddChildView(target_contents);
    869   }
    870 }
    871 
    872 void CandidateWindowView::UpdateAuxiliaryText(const std::string& utf8_text) {
    873   views::Label* target_label = (
    874       lookup_table_.orientation == InputMethodLookupTable::kHorizontal ?
    875       header_label_ : footer_label_);
    876   target_label->SetText(UTF8ToWide(utf8_text));
    877 }
    878 
    879 bool CandidateWindowView::ShouldUpdateCandidateViews(
    880     const InputMethodLookupTable& old_table,
    881     const InputMethodLookupTable& new_table) {
    882   // Check if most table contents are identical.
    883   if (old_table.page_size == new_table.page_size &&
    884       old_table.orientation == new_table.orientation &&
    885       old_table.candidates == new_table.candidates &&
    886       old_table.labels == new_table.labels &&
    887       old_table.annotations == new_table.annotations &&
    888       // Check if the page indexes are identical.
    889       ComputePageIndex(old_table) == ComputePageIndex(new_table)) {
    890     // If all of the conditions are met, we don't have to update candidate
    891     // views.
    892     return false;
    893   }
    894   return true;
    895 }
    896 
    897 void CandidateWindowView::UpdateCandidates(
    898     const InputMethodLookupTable& new_lookup_table) {
    899   const bool should_update = ShouldUpdateCandidateViews(lookup_table_,
    900                                                         new_lookup_table);
    901   // Updating the candidate views is expensive. We'll skip this if possible.
    902   if (should_update) {
    903     // Initialize candidate views if necessary.
    904     MaybeInitializeCandidateViews(new_lookup_table);
    905 
    906     // Compute the index of the current page.
    907     const int current_page_index = ComputePageIndex(new_lookup_table);
    908     if (current_page_index < 0) {
    909       LOG(ERROR) << "Invalid lookup_table: " << new_lookup_table.ToString();
    910       return;
    911     }
    912 
    913     // Update the candidates in the current page.
    914     const size_t start_from = current_page_index * new_lookup_table.page_size;
    915 
    916     // In some cases, engines send empty shortcut labels. For instance,
    917     // ibus-mozc sends empty labels when they show suggestions. In this
    918     // case, we should not show shortcut labels.
    919     const bool no_shortcut_mode =
    920         (start_from < new_lookup_table.labels.size() &&
    921          new_lookup_table.labels[start_from] == "");
    922     for (size_t i = 0; i < candidate_views_.size(); ++i) {
    923       const size_t index_in_page = i;
    924       const size_t candidate_index = start_from + index_in_page;
    925       CandidateView* candidate_view = candidate_views_[index_in_page];
    926       // Set the shortcut text.
    927       if (no_shortcut_mode) {
    928         candidate_view->SetShortcutText(L"");
    929       } else {
    930         // At this moment, we don't use labels sent from engines for UX
    931         // reasons. First, we want to show shortcut labels in empty rows
    932         // (ex. show 6, 7, 8, ... in empty rows when the number of
    933         // candidates is 5). Second, we want to add a period after each
    934         // shortcut label when the candidate window is horizontal.
    935         candidate_view->SetShortcutText(
    936             CreateShortcutText(i, new_lookup_table.orientation));
    937       }
    938       // Set the candidate text.
    939       if (candidate_index < new_lookup_table.candidates.size() &&
    940           candidate_index < new_lookup_table.annotations.size()) {
    941         candidate_view->SetCandidateText(
    942             UTF8ToWide(new_lookup_table.candidates[candidate_index]));
    943         candidate_view->SetAnnotationText(
    944             UTF8ToWide(new_lookup_table.annotations[candidate_index]));
    945         candidate_view->SetRowEnabled(true);
    946       } else {
    947         // Disable the empty row.
    948         candidate_view->SetCandidateText(L"");
    949         candidate_view->SetAnnotationText(L"");
    950         candidate_view->SetRowEnabled(false);
    951       }
    952     }
    953   }
    954   // Update the current lookup table. We'll use lookup_table_ from here.
    955   // Note that SelectCandidateAt() uses lookup_table_.
    956   lookup_table_ = new_lookup_table;
    957 
    958   // Select the current candidate in the page.
    959   const int current_candidate_in_page =
    960       lookup_table_.cursor_absolute_index % lookup_table_.page_size;
    961   SelectCandidateAt(current_candidate_in_page);
    962 }
    963 
    964 void CandidateWindowView::MaybeInitializeCandidateViews(
    965     const InputMethodLookupTable& lookup_table) {
    966   const InputMethodLookupTable::Orientation orientation =
    967       lookup_table.orientation;
    968   const int page_size = lookup_table.page_size;
    969 
    970   // Current column width.
    971   int shortcut_column_width = 0;
    972   int candidate_column_width = 0;
    973   int annotation_column_width = 0;
    974 
    975   // If orientation is horizontal, don't need to compute width,
    976   // because each label is left aligned.
    977   if (orientation == InputMethodLookupTable::kVertical) {
    978     shortcut_column_width = ComputeShortcutColumnWidth(lookup_table);
    979     candidate_column_width = ComputeCandidateColumnWidth(lookup_table);
    980     annotation_column_width = ComputeAnnotationColumnWidth(lookup_table);
    981   }
    982 
    983   // If the requested number of views matches the number of current views, and
    984   // previous and current column width are same, just reuse these.
    985   //
    986   // Note that the early exit logic is not only useful for improving
    987   // performance, but also necessary for the horizontal candidate window
    988   // to be redrawn properly. If we get rid of the logic, the horizontal
    989   // candidate window won't get redrawn properly for some reason when
    990   // there is no size change. You can test this by removing "return" here
    991   // and type "ni" with Pinyin input method.
    992   if (static_cast<int>(candidate_views_.size()) == page_size &&
    993       lookup_table_.orientation == orientation &&
    994       previous_shortcut_column_width_ == shortcut_column_width &&
    995       previous_candidate_column_width_ == candidate_column_width &&
    996       previous_annotation_column_width_ == annotation_column_width) {
    997     return;
    998   }
    999 
   1000   // Update the previous column widths.
   1001   previous_shortcut_column_width_ = shortcut_column_width;
   1002   previous_candidate_column_width_ = candidate_column_width;
   1003   previous_annotation_column_width_ = annotation_column_width;
   1004 
   1005   // Clear the existing candidate_views if any.
   1006   for (size_t i = 0; i < candidate_views_.size(); ++i) {
   1007     candidate_area_->RemoveChildView(candidate_views_[i]);
   1008   }
   1009   candidate_views_.clear();
   1010 
   1011   views::GridLayout* layout = new views::GridLayout(candidate_area_);
   1012   // |candidate_area_| owns |layout|.
   1013   candidate_area_->SetLayoutManager(layout);
   1014   // Initialize the column set.
   1015   views::ColumnSet* column_set = layout->AddColumnSet(0);
   1016   if (orientation == InputMethodLookupTable::kVertical) {
   1017     column_set->AddColumn(views::GridLayout::FILL,
   1018                           views::GridLayout::FILL,
   1019                           0, views::GridLayout::USE_PREF, 0, 0);
   1020   } else {
   1021     for (int i = 0; i < page_size; ++i) {
   1022       column_set->AddColumn(views::GridLayout::FILL,
   1023                             views::GridLayout::FILL,
   1024                             0, views::GridLayout::USE_PREF, 0, 0);
   1025     }
   1026   }
   1027 
   1028   // Set insets so the border of the selected candidate is drawn inside of
   1029   // the border of the main candidate window, but we don't have the inset
   1030   // at the top and the bottom as we have the borders of the header and
   1031   // footer areas.
   1032   const gfx::Insets kCandidateAreaInsets(0, 1, 0, 1);
   1033   layout->SetInsets(kCandidateAreaInsets.top(),
   1034                     kCandidateAreaInsets.left(),
   1035                     kCandidateAreaInsets.bottom(),
   1036                     kCandidateAreaInsets.right());
   1037 
   1038   // Add views to the candidate area.
   1039   if (orientation == InputMethodLookupTable::kHorizontal) {
   1040     layout->StartRow(0, 0);
   1041   }
   1042 
   1043   for (int i = 0; i < page_size; ++i) {
   1044     CandidateView* candidate_row = new CandidateView(this, i, orientation);
   1045     candidate_row->Init(shortcut_column_width,
   1046                         candidate_column_width,
   1047                         annotation_column_width);
   1048     candidate_views_.push_back(candidate_row);
   1049     if (orientation == InputMethodLookupTable::kVertical) {
   1050       layout->StartRow(0, 0);
   1051     }
   1052     // |candidate_row| will be owned by |candidate_area_|.
   1053     layout->AddView(candidate_row);
   1054   }
   1055 
   1056   // Compute views size in |layout|.
   1057   // If we don't call this function, GetHorizontalOffset() often
   1058   // returns invalid value (returns 0), then candidate window
   1059   // moves right from the correct position in MoveParentFrame().
   1060   // TODO(nhiroki): Figure out why it returns invalid value.
   1061   // It seems that the x-position of the candidate labels is not set.
   1062   layout->Layout(candidate_area_);
   1063 }
   1064 
   1065 views::View* CandidateWindowView::CreateHeaderArea() {
   1066   // |header_area_place_holder_| will not be owned by another view.
   1067   // This will be deleted by scoped_ptr.
   1068   //
   1069   // This is because we swap the contents of |header_area_| between
   1070   // |header_area_place_holder_| (to show nothing) and
   1071   // |header_area_contents_| (to show something). In other words,
   1072   // |header_area_| only contains one of the two views hence cannot own
   1073   // the two views at the same time.
   1074   header_area_place_holder_.reset(new views::View);
   1075   header_area_place_holder_->set_parent_owned(false);  // Won't be owened.
   1076 
   1077   // |header_label_| will be owned by |header_area_contents_|.
   1078   header_label_ = new views::Label;
   1079   header_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
   1080 
   1081   const gfx::Insets kHeaderInsets(2, 2, 2, 4);
   1082   // |header_area_contents_| will not be owned by another view.
   1083   // See a comment at |header_area_place_holder_| for why.
   1084   header_area_contents_.reset(
   1085       WrapWithPadding(header_label_, kHeaderInsets));
   1086   header_area_contents_->set_parent_owned(false);  // Won't be owened.
   1087   header_area_contents_->set_border(
   1088       views::Border::CreateSolidBorder(1, kFrameColor));
   1089   header_area_contents_->set_background(
   1090       views::Background::CreateVerticalGradientBackground(
   1091           kFooterTopColor,
   1092           kFooterBottomColor));
   1093 
   1094   views::View* header_area = new views::View;
   1095   header_area->SetLayoutManager(new views::FillLayout);
   1096   // Initialize the header area with the place holder (i.e. show nothing).
   1097   header_area->AddChildView(header_area_place_holder_.get());
   1098   return header_area;
   1099 }
   1100 
   1101 views::View* CandidateWindowView::CreateFooterArea() {
   1102   // |footer_area_place_holder_| will not be owned by another view.
   1103   // See also the comment about |header_area_place_holder_| in
   1104   // CreateHeaderArea().
   1105   footer_area_place_holder_.reset(new views::View);
   1106   footer_area_place_holder_->set_parent_owned(false);  // Won't be owened.
   1107 
   1108   footer_label_ = new views::Label();
   1109   footer_label_->SetHorizontalAlignment(views::Label::ALIGN_RIGHT);
   1110 
   1111   const gfx::Insets kFooterInsets(2, 2, 2, 4);
   1112   footer_area_contents_.reset(
   1113       WrapWithPadding(footer_label_, kFooterInsets));
   1114   footer_area_contents_->set_parent_owned(false);  // Won't be owened.
   1115   footer_area_contents_->set_border(
   1116       views::Border::CreateSolidBorder(1, kFrameColor));
   1117   footer_area_contents_->set_background(
   1118       views::Background::CreateVerticalGradientBackground(
   1119           kFooterTopColor,
   1120           kFooterBottomColor));
   1121 
   1122   views::View* footer_area = new views::View;
   1123   footer_area->SetLayoutManager(new views::FillLayout);
   1124   // Initialize the footer area with the place holder (i.e. show nothing).
   1125   footer_area->AddChildView(footer_area_place_holder_.get());
   1126   return footer_area;
   1127 }
   1128 
   1129 void CandidateWindowView::SelectCandidateAt(int index_in_page) {
   1130   const int current_page_index = ComputePageIndex(lookup_table_);
   1131   if (current_page_index < 0) {
   1132     LOG(ERROR) << "Invalid lookup_table: " << lookup_table_.ToString();
   1133     return;
   1134   }
   1135 
   1136   const int cursor_absolute_index =
   1137       lookup_table_.page_size * current_page_index + index_in_page;
   1138   // Ignore click on out of range views.
   1139   if (cursor_absolute_index < 0 ||
   1140       cursor_absolute_index >=
   1141       static_cast<int>(lookup_table_.candidates.size())) {
   1142     return;
   1143   }
   1144 
   1145   // Unselect the currently selected candidate.
   1146   candidate_views_[selected_candidate_index_in_page_]->Unselect();
   1147   // Remember the currently selected candidate index in the current page.
   1148   selected_candidate_index_in_page_ = index_in_page;
   1149 
   1150   // Select the candidate specified by index_in_page.
   1151   candidate_views_[index_in_page]->Select();
   1152 
   1153   // Update the cursor indexes in the model.
   1154   lookup_table_.cursor_absolute_index = cursor_absolute_index;
   1155 }
   1156 
   1157 void CandidateWindowView::OnCandidateDragged(
   1158     const gfx::Point& location) {
   1159   for (size_t i = 0; i < candidate_views_.size(); ++i) {
   1160     gfx::Point converted_location = location;
   1161     views::View::ConvertPointToView(this, candidate_views_[i],
   1162                                     &converted_location);
   1163     if (candidate_views_[i]->HitTest(converted_location)) {
   1164       SelectCandidateAt(i);
   1165       break;
   1166     }
   1167   }
   1168 }
   1169 
   1170 void CandidateWindowView::CommitCandidate() {
   1171   // For now, we don't distinguish left and right clicks.
   1172   const int button = 1;  // Left button.
   1173   const int key_modifilers = 0;
   1174   FOR_EACH_OBSERVER(Observer, observers_,
   1175                     OnCandidateCommitted(selected_candidate_index_in_page_,
   1176                                          button,
   1177                                          key_modifilers));
   1178 }
   1179 
   1180 void CandidateWindowView::ResizeAndMoveParentFrame() {
   1181   ResizeParentFrame();
   1182   MoveParentFrame();
   1183 }
   1184 
   1185 void CandidateWindowView::ResizeParentFrame() {
   1186   // Resize the parent frame, with the current candidate window size.
   1187   gfx::Size size = GetPreferredSize();
   1188   gfx::Rect bounds = parent_frame_->GetClientAreaScreenBounds();
   1189   // SetBounds() is not cheap. Only call this when the size is changed.
   1190   if (bounds.size() != size) {
   1191     bounds.set_size(size);
   1192     parent_frame_->SetBounds(bounds);
   1193   }
   1194 }
   1195 
   1196 void CandidateWindowView::MoveParentFrame() {
   1197   const int x = cursor_location_.x();
   1198   const int y = cursor_location_.y();
   1199   const int height = cursor_location_.height();
   1200   const int horizontal_offset = GetHorizontalOffset();
   1201 
   1202   gfx::Rect frame_bounds = parent_frame_->GetClientAreaScreenBounds();
   1203   gfx::Rect screen_bounds = views::Screen::GetMonitorWorkAreaNearestWindow(
   1204       parent_frame_->GetNativeView());
   1205 
   1206   // The default position.
   1207   frame_bounds.set_x(x + horizontal_offset);
   1208   frame_bounds.set_y(y + height);
   1209 
   1210   // Handle overflow at the left and the top.
   1211   frame_bounds.set_x(std::max(frame_bounds.x(), screen_bounds.x()));
   1212   frame_bounds.set_y(std::max(frame_bounds.y(), screen_bounds.y()));
   1213 
   1214   // Handle overflow at the right.
   1215   const int right_overflow = frame_bounds.right() - screen_bounds.right();
   1216   if (right_overflow > 0) {
   1217     frame_bounds.set_x(frame_bounds.x() - right_overflow);
   1218   }
   1219 
   1220   // Handle overflow at the bottom.
   1221   const int bottom_overflow = frame_bounds.bottom() - screen_bounds.bottom();
   1222   if (bottom_overflow > 0) {
   1223     frame_bounds.set_y(frame_bounds.y() - height - frame_bounds.height());
   1224   }
   1225 
   1226   // Move the window per the cursor location.
   1227   parent_frame_->SetBounds(frame_bounds);
   1228 }
   1229 
   1230 int CandidateWindowView::GetHorizontalOffset() {
   1231   // Compute the horizontal offset if the lookup table is vertical.
   1232   if (!candidate_views_.empty() &&
   1233       lookup_table_.orientation == InputMethodLookupTable::kVertical) {
   1234     return - candidate_views_[0]->GetCandidateLabelPosition().x();
   1235   }
   1236   return 0;
   1237 }
   1238 
   1239 void CandidateWindowView::VisibilityChanged(View* starting_from,
   1240                                             bool is_visible) {
   1241   if (is_visible) {
   1242     // If the visibility of candidate window is changed,
   1243     // we should move the frame to the right position.
   1244     MoveParentFrame();
   1245   }
   1246 }
   1247 
   1248 void CandidateWindowView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
   1249   // If the bounds(size) of candidate window is changed,
   1250   // we should move the frame to the right position.
   1251   View::OnBoundsChanged(previous_bounds);
   1252   MoveParentFrame();
   1253 }
   1254 
   1255 bool CandidateWindowController::Impl::Init() {
   1256   // Initialize the input method UI status connection.
   1257   InputMethodUiStatusMonitorFunctions functions;
   1258   functions.hide_auxiliary_text =
   1259       &CandidateWindowController::Impl::OnHideAuxiliaryText;
   1260   functions.hide_lookup_table =
   1261       &CandidateWindowController::Impl::OnHideLookupTable;
   1262   functions.set_cursor_location =
   1263       &CandidateWindowController::Impl::OnSetCursorLocation;
   1264   functions.update_auxiliary_text =
   1265       &CandidateWindowController::Impl::OnUpdateAuxiliaryText;
   1266   functions.update_lookup_table =
   1267       &CandidateWindowController::Impl::OnUpdateLookupTable;
   1268   ui_status_connection_ = MonitorInputMethodUiStatus(functions, this);
   1269   if (!ui_status_connection_) {
   1270     LOG(ERROR) << "MonitorInputMethodUiStatus() failed.";
   1271     return false;
   1272   }
   1273   MonitorInputMethodConnection(
   1274       ui_status_connection_,
   1275       &CandidateWindowController::Impl::OnConnectionChange);
   1276 
   1277   // Create the candidate window view.
   1278   CreateView();
   1279 
   1280   return true;
   1281 }
   1282 
   1283 void CandidateWindowController::Impl::CreateView() {
   1284   // Create a non-decorated frame.
   1285   frame_.reset(views::Widget::CreateWidget(
   1286       views::Widget::CreateParams(views::Widget::CreateParams::TYPE_POPUP)));
   1287   // The size is initially zero.
   1288   frame_->Init(NULL, gfx::Rect(0, 0));
   1289 
   1290   // Create the candidate window.
   1291   candidate_window_ = new CandidateWindowView(frame_.get());
   1292   candidate_window_->Init();
   1293   candidate_window_->AddObserver(this);
   1294 
   1295   // Put the candidate window view on the frame.  The frame is resized
   1296   // later when the candidate window is shown.
   1297   views::RootView* root_view = frame_->GetRootView();
   1298   // |root_view| owns the |candidate_window_|, thus |frame_| effectively
   1299   // owns |candidate_window_|.
   1300   root_view->SetContentsView(candidate_window_);
   1301 }
   1302 
   1303 CandidateWindowController::Impl::Impl()
   1304     : ui_status_connection_(NULL),
   1305       frame_(NULL) {
   1306 }
   1307 
   1308 CandidateWindowController::Impl::~Impl() {
   1309   candidate_window_->RemoveObserver(this);
   1310   chromeos::DisconnectInputMethodUiStatus(ui_status_connection_);
   1311 }
   1312 
   1313 void CandidateWindowController::Impl::OnHideAuxiliaryText(
   1314     void* input_method_library) {
   1315   CandidateWindowController::Impl* controller =
   1316       static_cast<CandidateWindowController::Impl*>(input_method_library);
   1317 
   1318   controller->candidate_window_->HideAuxiliaryText();
   1319   controller->candidate_window_->ResizeParentFrame();
   1320 }
   1321 
   1322 void CandidateWindowController::Impl::OnHideLookupTable(
   1323     void* input_method_library) {
   1324   CandidateWindowController::Impl* controller =
   1325       static_cast<CandidateWindowController::Impl*>(input_method_library);
   1326 
   1327   controller->candidate_window_->HideLookupTable();
   1328 }
   1329 
   1330 void CandidateWindowController::Impl::OnSetCursorLocation(
   1331     void* input_method_library,
   1332     int x,
   1333     int y,
   1334     int width,
   1335     int height) {
   1336   CandidateWindowController::Impl* controller =
   1337       static_cast<CandidateWindowController::Impl*>(input_method_library);
   1338 
   1339   // A workaround for http://crosbug.com/6460. We should ignore very short Y
   1340   // move to prevent the window from shaking up and down.
   1341   const int kKeepPositionThreshold = 2;  // px
   1342   const gfx::Rect& last_location =
   1343       controller->candidate_window_->cursor_location();
   1344   const int delta_y = abs(last_location.y() - y);
   1345   if ((last_location.x() == x) && (delta_y <= kKeepPositionThreshold)) {
   1346     DLOG(INFO) << "Ignored set_cursor_location signal to prevent window shake";
   1347     return;
   1348   }
   1349 
   1350   // Remember the cursor location.
   1351   controller->candidate_window_->set_cursor_location(
   1352       gfx::Rect(x, y, width, height));
   1353   // Move the window per the cursor location.
   1354   controller->candidate_window_->MoveParentFrame();
   1355 }
   1356 
   1357 void CandidateWindowController::Impl::OnUpdateAuxiliaryText(
   1358     void* input_method_library,
   1359     const std::string& utf8_text,
   1360     bool visible) {
   1361   CandidateWindowController::Impl* controller =
   1362       static_cast<CandidateWindowController::Impl*>(input_method_library);
   1363   // If it's not visible, hide the auxiliary text and return.
   1364   if (!visible) {
   1365     controller->candidate_window_->HideAuxiliaryText();
   1366     return;
   1367   }
   1368   controller->candidate_window_->UpdateAuxiliaryText(utf8_text);
   1369   controller->candidate_window_->ShowAuxiliaryText();
   1370   controller->candidate_window_->ResizeParentFrame();
   1371 }
   1372 
   1373 void CandidateWindowController::Impl::OnUpdateLookupTable(
   1374     void* input_method_library,
   1375     const InputMethodLookupTable& lookup_table) {
   1376   CandidateWindowController::Impl* controller =
   1377       static_cast<CandidateWindowController::Impl*>(input_method_library);
   1378 
   1379   // If it's not visible, hide the window and return.
   1380   if (!lookup_table.visible) {
   1381     controller->candidate_window_->HideLookupTable();
   1382     return;
   1383   }
   1384 
   1385   controller->candidate_window_->UpdateCandidates(lookup_table);
   1386   controller->candidate_window_->ResizeParentFrame();
   1387   controller->frame_->Show();
   1388 }
   1389 
   1390 void CandidateWindowController::Impl::OnCandidateCommitted(int index,
   1391                                                            int button,
   1392                                                            int flags) {
   1393   NotifyCandidateClicked(ui_status_connection_, index, button, flags);
   1394 }
   1395 
   1396 void CandidateWindowController::Impl::OnConnectionChange(
   1397     void* input_method_library,
   1398     bool connected) {
   1399   if (!connected) {
   1400     CandidateWindowController::Impl* controller =
   1401         static_cast<CandidateWindowController::Impl*>(input_method_library);
   1402     controller->candidate_window_->HideLookupTable();
   1403   }
   1404 }
   1405 
   1406 CandidateWindowController::CandidateWindowController()
   1407     : impl_(new CandidateWindowController::Impl) {
   1408 }
   1409 
   1410 CandidateWindowController::~CandidateWindowController() {
   1411   delete impl_;
   1412 }
   1413 
   1414 bool CandidateWindowController::Init() {
   1415   return impl_->Init();
   1416 }
   1417 
   1418 }  // namespace chromeos
   1419