• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
~VerticalCandidateLabel()67   virtual ~VerticalCandidateLabel() {}
68 
69   // Returns the preferred size, but guarantees that the width has at
70   // least kMinCandidateLabelWidth pixels.
GetPreferredSize()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.
WrapWithPadding(views::View * view,const gfx::Insets & insets)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.
CreateShortcutText(int index,chromeos::InputMethodLookupTable::Orientation 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.
CreateShortcutLabel(chromeos::InputMethodLookupTable::Orientation orientation)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.
CreateWrappedShortcutLabel(views::Label * shortcut_label,chromeos::InputMethodLookupTable::Orientation orientation)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.
CreateCandidateLabel(chromeos::InputMethodLookupTable::Orientation orientation)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.
CreateAnnotationLabel(chromeos::InputMethodLookupTable::Orientation orientation)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.
ComputeShortcutColumnWidth(const chromeos::InputMethodLookupTable & lookup_table)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.
ComputePageIndex(const chromeos::InputMethodLookupTable & lookup_table)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.
ComputeCandidateColumnWidth(const chromeos::InputMethodLookupTable & lookup_table)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.
ComputeAnnotationColumnWidth(const chromeos::InputMethodLookupTable & lookup_table)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:
~Observer()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);
~CandidateWindowView()324   virtual ~CandidateWindowView() {}
325   void Init();
326 
327   // Adds the given observer. The ownership is not transferred.
AddObserver(Observer * observer)328   void AddObserver(Observer* observer) {
329     observers_.AddObserver(observer);
330   }
331 
332   // Removes the given observer.
RemoveObserver(Observer * 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 
set_cursor_location(const gfx::Rect & cursor_location)416   void set_cursor_location(const gfx::Rect& cursor_location) {
417     cursor_location_ = cursor_location;
418   }
419 
cursor_location() const420   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);
~CandidateView()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 
CandidateView(CandidateWindowView * parent_candidate_window,int index_in_page,InputMethodLookupTable::Orientation orientation)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 
Init(int shortcut_column_width,int candidate_column_width,int annotation_column_width)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 
SetCandidateText(const std::wstring & text)680 void CandidateView::SetCandidateText(const std::wstring& text) {
681   candidate_label_->SetText(text);
682 }
683 
SetShortcutText(const std::wstring & text)684 void CandidateView::SetShortcutText(const std::wstring& text) {
685   shortcut_label_->SetText(text);
686 }
687 
SetAnnotationText(const std::wstring & text)688 void CandidateView::SetAnnotationText(const std::wstring& text) {
689   annotation_label_->SetText(text);
690 }
691 
Select()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 
Unselect()700 void CandidateView::Unselect() {
701   set_background(NULL);
702   set_border(NULL);
703   SchedulePaint();  // See comments at Select().
704 }
705 
SetRowEnabled(bool enabled)706 void CandidateView::SetRowEnabled(bool enabled) {
707   shortcut_label_->SetColor(
708       enabled ? kShortcutColor : kDisabledShortcutColor);
709 }
710 
GetCandidateLabelPosition() const711 gfx::Point CandidateView::GetCandidateLabelPosition() const {
712   return candidate_label_->GetMirroredPosition();
713 }
714 
OnMousePressed(const views::MouseEvent & event)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 
OnMouseDragged(const views::MouseEvent & event)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 
OnMouseReleased(const views::MouseEvent & event)734 void CandidateView::OnMouseReleased(const views::MouseEvent& event) {
735   // Commit the current candidate.
736   parent_candidate_window_->CommitCandidate();
737   OnMouseCaptureLost();
738 }
739 
OnMouseCaptureLost()740 void CandidateView::OnMouseCaptureLost() {
741   parent_candidate_window_->OnMouseReleased();
742 }
743 
CandidateWindowView(views::Widget * parent_frame)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 
Init()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 
HideLookupTable()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 
OnMousePressed()835 void CandidateWindowView::OnMousePressed() {
836   mouse_is_pressed_ = true;
837 }
838 
OnMouseReleased()839 void CandidateWindowView::OnMouseReleased() {
840   mouse_is_pressed_ = false;
841 }
842 
HideAuxiliaryText()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 
ShowAuxiliaryText()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 
UpdateAuxiliaryText(const std::string & utf8_text)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 
ShouldUpdateCandidateViews(const InputMethodLookupTable & old_table,const InputMethodLookupTable & new_table)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 
UpdateCandidates(const InputMethodLookupTable & new_lookup_table)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 
MaybeInitializeCandidateViews(const InputMethodLookupTable & lookup_table)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 
CreateHeaderArea()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 
CreateFooterArea()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 
SelectCandidateAt(int index_in_page)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 
OnCandidateDragged(const gfx::Point & location)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 
CommitCandidate()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 
ResizeAndMoveParentFrame()1180 void CandidateWindowView::ResizeAndMoveParentFrame() {
1181   ResizeParentFrame();
1182   MoveParentFrame();
1183 }
1184 
ResizeParentFrame()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 
MoveParentFrame()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 
GetHorizontalOffset()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 
VisibilityChanged(View * starting_from,bool is_visible)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 
OnBoundsChanged(const gfx::Rect & previous_bounds)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 
Init()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 
CreateView()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 
Impl()1303 CandidateWindowController::Impl::Impl()
1304     : ui_status_connection_(NULL),
1305       frame_(NULL) {
1306 }
1307 
~Impl()1308 CandidateWindowController::Impl::~Impl() {
1309   candidate_window_->RemoveObserver(this);
1310   chromeos::DisconnectInputMethodUiStatus(ui_status_connection_);
1311 }
1312 
OnHideAuxiliaryText(void * input_method_library)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 
OnHideLookupTable(void * input_method_library)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 
OnSetCursorLocation(void * input_method_library,int x,int y,int width,int height)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 
OnUpdateAuxiliaryText(void * input_method_library,const std::string & utf8_text,bool visible)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 
OnUpdateLookupTable(void * input_method_library,const InputMethodLookupTable & lookup_table)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 
OnCandidateCommitted(int index,int button,int flags)1390 void CandidateWindowController::Impl::OnCandidateCommitted(int index,
1391                                                            int button,
1392                                                            int flags) {
1393   NotifyCandidateClicked(ui_status_connection_, index, button, flags);
1394 }
1395 
OnConnectionChange(void * input_method_library,bool connected)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 
CandidateWindowController()1406 CandidateWindowController::CandidateWindowController()
1407     : impl_(new CandidateWindowController::Impl) {
1408 }
1409 
~CandidateWindowController()1410 CandidateWindowController::~CandidateWindowController() {
1411   delete impl_;
1412 }
1413 
Init()1414 bool CandidateWindowController::Init() {
1415   return impl_->Init();
1416 }
1417 
1418 }  // namespace chromeos
1419