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
33namespace {
34
35// Colors used in the candidate window UI.
36const SkColor kFrameColor = SkColorSetRGB(0x96, 0x96, 0x96);
37const SkColor kShortcutBackgroundColor = SkColorSetARGB(0x10, 0x3, 0x4, 0xf);
38const SkColor kSelectedRowBackgroundColor = SkColorSetRGB(0xd1, 0xea, 0xff);
39const SkColor kDefaultBackgroundColor = SkColorSetRGB(0xff, 0xff, 0xff);
40const SkColor kSelectedRowFrameColor = SkColorSetRGB(0x7f, 0xac, 0xdd);
41const SkColor kFooterTopColor = SkColorSetRGB(0xff, 0xff, 0xff);
42const SkColor kFooterBottomColor = SkColorSetRGB(0xee, 0xee, 0xee);
43const SkColor kShortcutColor = SkColorSetRGB(0x61, 0x61, 0x61);
44const SkColor kDisabledShortcutColor = SkColorSetRGB(0xcc, 0xcc, 0xcc);
45const 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)
50const int kFontSizeDelta = 1;
51#else
52const 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.
58const 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.
62const int kMaxCandidateLabelWidth = 500;
63
64// VerticalCandidateLabel is used for rendering candidate text in
65// the vertical candidate window.
66class 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.
88views::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.
107std::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.
130views::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.
154views::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.
179views::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.
201views::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.
216int 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.
242int 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.
249int 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.
277int 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
306namespace chromeos {
307
308class CandidateView;
309
310// CandidateWindowView is the main container of the candidate window UI.
311class 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.
494class 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.
559class 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
619CandidateView::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
631void 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
680void CandidateView::SetCandidateText(const std::wstring& text) {
681  candidate_label_->SetText(text);
682}
683
684void CandidateView::SetShortcutText(const std::wstring& text) {
685  shortcut_label_->SetText(text);
686}
687
688void CandidateView::SetAnnotationText(const std::wstring& text) {
689  annotation_label_->SetText(text);
690}
691
692void 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
700void CandidateView::Unselect() {
701  set_background(NULL);
702  set_border(NULL);
703  SchedulePaint();  // See comments at Select().
704}
705
706void CandidateView::SetRowEnabled(bool enabled) {
707  shortcut_label_->SetColor(
708      enabled ? kShortcutColor : kDisabledShortcutColor);
709}
710
711gfx::Point CandidateView::GetCandidateLabelPosition() const {
712  return candidate_label_->GetMirroredPosition();
713}
714
715bool 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
724bool 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
734void CandidateView::OnMouseReleased(const views::MouseEvent& event) {
735  // Commit the current candidate.
736  parent_candidate_window_->CommitCandidate();
737  OnMouseCaptureLost();
738}
739
740void CandidateView::OnMouseCaptureLost() {
741  parent_candidate_window_->OnMouseReleased();
742}
743
744CandidateWindowView::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
759void 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
792void 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
835void CandidateWindowView::OnMousePressed() {
836  mouse_is_pressed_ = true;
837}
838
839void CandidateWindowView::OnMouseReleased() {
840  mouse_is_pressed_ = false;
841}
842
843void 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
856void 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
872void 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
879bool 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
897void 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
964void 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
1065views::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
1101views::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
1129void 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
1157void 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
1170void 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
1180void CandidateWindowView::ResizeAndMoveParentFrame() {
1181  ResizeParentFrame();
1182  MoveParentFrame();
1183}
1184
1185void 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
1196void 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
1230int 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
1239void 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
1248void 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
1255bool 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
1283void 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
1303CandidateWindowController::Impl::Impl()
1304    : ui_status_connection_(NULL),
1305      frame_(NULL) {
1306}
1307
1308CandidateWindowController::Impl::~Impl() {
1309  candidate_window_->RemoveObserver(this);
1310  chromeos::DisconnectInputMethodUiStatus(ui_status_connection_);
1311}
1312
1313void 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
1322void 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
1330void 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
1357void 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
1373void 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
1390void CandidateWindowController::Impl::OnCandidateCommitted(int index,
1391                                                           int button,
1392                                                           int flags) {
1393  NotifyCandidateClicked(ui_status_connection_, index, button, flags);
1394}
1395
1396void 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
1406CandidateWindowController::CandidateWindowController()
1407    : impl_(new CandidateWindowController::Impl) {
1408}
1409
1410CandidateWindowController::~CandidateWindowController() {
1411  delete impl_;
1412}
1413
1414bool CandidateWindowController::Init() {
1415  return impl_->Init();
1416}
1417
1418}  // namespace chromeos
1419