Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ash / ime / candidate_window_view.cc
1 // Copyright 2014 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 "ash/ime/candidate_window_view.h"
6
7 #include <string>
8
9 #include "ash/ime/candidate_view.h"
10 #include "ash/ime/candidate_window_constants.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "ui/gfx/color_utils.h"
13 #include "ui/gfx/screen.h"
14 #include "ui/native_theme/native_theme.h"
15 #include "ui/views/background.h"
16 #include "ui/views/border.h"
17 #include "ui/views/bubble/bubble_frame_view.h"
18 #include "ui/views/controls/label.h"
19 #include "ui/views/layout/box_layout.h"
20 #include "ui/views/layout/fill_layout.h"
21 #include "ui/wm/core/window_animations.h"
22
23 namespace ash {
24 namespace ime {
25
26 namespace {
27
28 class CandidateWindowBorder : public views::BubbleBorder {
29  public:
30   explicit CandidateWindowBorder(gfx::NativeView parent)
31       : views::BubbleBorder(views::BubbleBorder::TOP_CENTER,
32                             views::BubbleBorder::NO_SHADOW,
33                             SK_ColorTRANSPARENT),
34         parent_(parent),
35         offset_(0) {
36     set_paint_arrow(views::BubbleBorder::PAINT_NONE);
37   }
38   ~CandidateWindowBorder() override {}
39
40   void set_offset(int offset) { offset_ = offset; }
41
42  private:
43   // Overridden from views::BubbleBorder:
44   gfx::Rect GetBounds(const gfx::Rect& anchor_rect,
45                       const gfx::Size& content_size) const override {
46     gfx::Rect bounds(content_size);
47     bounds.set_origin(gfx::Point(
48         anchor_rect.x() - offset_,
49         is_arrow_on_top(arrow()) ?
50         anchor_rect.bottom() : anchor_rect.y() - content_size.height()));
51
52     // It cannot use the normal logic of arrow offset for horizontal offscreen,
53     // because the arrow must be in the content's edge. But CandidateWindow has
54     // to be visible even when |anchor_rect| is out of the screen.
55     gfx::Rect work_area = gfx::Screen::GetNativeScreen()->
56         GetDisplayNearestWindow(parent_).work_area();
57     if (bounds.right() > work_area.right())
58       bounds.set_x(work_area.right() - bounds.width());
59     if (bounds.x() < work_area.x())
60       bounds.set_x(work_area.x());
61
62     return bounds;
63   }
64
65   gfx::Insets GetInsets() const override { return gfx::Insets(); }
66
67   gfx::NativeView parent_;
68   int offset_;
69
70   DISALLOW_COPY_AND_ASSIGN(CandidateWindowBorder);
71 };
72
73 // Computes the page index. For instance, if the page size is 9, and the
74 // cursor is pointing to 13th candidate, the page index will be 1 (2nd
75 // page, as the index is zero-origin). Returns -1 on error.
76 int ComputePageIndex(const ui::CandidateWindow& candidate_window) {
77   if (candidate_window.page_size() > 0)
78     return candidate_window.cursor_position() / candidate_window.page_size();
79   return -1;
80 }
81
82 }  // namespace
83
84 class InformationTextArea : public views::View {
85  public:
86   // InformationTextArea's border is drawn as a separator, it should appear
87   // at either top or bottom.
88   enum BorderPosition {
89     TOP,
90     BOTTOM
91   };
92
93   // Specify the alignment and initialize the control.
94   InformationTextArea(gfx::HorizontalAlignment align, int min_width)
95       : min_width_(min_width) {
96     label_ = new views::Label;
97     label_->SetHorizontalAlignment(align);
98     label_->SetBorder(views::Border::CreateEmptyBorder(2, 2, 2, 4));
99
100     SetLayoutManager(new views::FillLayout());
101     AddChildView(label_);
102     set_background(views::Background::CreateSolidBackground(
103         color_utils::AlphaBlend(SK_ColorBLACK,
104                                 GetNativeTheme()->GetSystemColor(
105                                     ui::NativeTheme::kColorId_WindowBackground),
106                                 0x10)));
107   }
108
109   // Sets the text alignment.
110   void SetAlignment(gfx::HorizontalAlignment alignment) {
111     label_->SetHorizontalAlignment(alignment);
112   }
113
114   // Sets the displayed text.
115   void SetText(const base::string16& text) {
116     label_->SetText(text);
117   }
118
119   // Sets the border thickness for top/bottom.
120   void SetBorderFromPosition(BorderPosition position) {
121     SetBorder(views::Border::CreateSolidSidedBorder(
122         (position == TOP) ? 1 : 0,
123         0,
124         (position == BOTTOM) ? 1 : 0,
125         0,
126         GetNativeTheme()->GetSystemColor(
127             ui::NativeTheme::kColorId_MenuBorderColor)));
128   }
129
130  protected:
131   gfx::Size GetPreferredSize() const override {
132     gfx::Size size = views::View::GetPreferredSize();
133     size.SetToMax(gfx::Size(min_width_, 0));
134     return size;
135   }
136
137  private:
138   views::Label* label_;
139   int min_width_;
140
141   DISALLOW_COPY_AND_ASSIGN(InformationTextArea);
142 };
143
144 CandidateWindowView::CandidateWindowView(gfx::NativeView parent)
145     : selected_candidate_index_in_page_(-1),
146       should_show_at_composition_head_(false),
147       should_show_upper_side_(false),
148       was_candidate_window_open_(false) {
149   set_can_activate(false);
150   set_parent_window(parent);
151   set_margins(gfx::Insets());
152
153   // Set the background and the border of the view.
154   ui::NativeTheme* theme = GetNativeTheme();
155   set_background(
156       views::Background::CreateSolidBackground(theme->GetSystemColor(
157           ui::NativeTheme::kColorId_WindowBackground)));
158   SetBorder(views::Border::CreateSolidBorder(
159       1, theme->GetSystemColor(ui::NativeTheme::kColorId_MenuBorderColor)));
160
161   SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
162   auxiliary_text_ = new InformationTextArea(gfx::ALIGN_RIGHT, 0);
163   preedit_ = new InformationTextArea(gfx::ALIGN_LEFT, kMinPreeditAreaWidth);
164   candidate_area_ = new views::View;
165   auxiliary_text_->SetVisible(false);
166   preedit_->SetVisible(false);
167   candidate_area_->SetVisible(false);
168   preedit_->SetBorderFromPosition(InformationTextArea::BOTTOM);
169   if (candidate_window_.orientation() == ui::CandidateWindow::VERTICAL) {
170     AddChildView(preedit_);
171     AddChildView(candidate_area_);
172     AddChildView(auxiliary_text_);
173     auxiliary_text_->SetBorderFromPosition(InformationTextArea::TOP);
174     candidate_area_->SetLayoutManager(new views::BoxLayout(
175         views::BoxLayout::kVertical, 0, 0, 0));
176   } else {
177     AddChildView(preedit_);
178     AddChildView(auxiliary_text_);
179     AddChildView(candidate_area_);
180     auxiliary_text_->SetAlignment(gfx::ALIGN_LEFT);
181     auxiliary_text_->SetBorderFromPosition(InformationTextArea::BOTTOM);
182     candidate_area_->SetLayoutManager(new views::BoxLayout(
183         views::BoxLayout::kHorizontal, 0, 0, 0));
184   }
185 }
186
187 CandidateWindowView::~CandidateWindowView() {
188 }
189
190 views::Widget* CandidateWindowView::InitWidget() {
191   views::Widget* widget = BubbleDelegateView::CreateBubble(this);
192
193   wm::SetWindowVisibilityAnimationType(
194       widget->GetNativeView(),
195       wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
196
197   GetBubbleFrameView()->SetBubbleBorder(scoped_ptr<views::BubbleBorder>(
198       new CandidateWindowBorder(parent_window())));
199   return widget;
200 }
201
202 void CandidateWindowView::UpdateVisibility() {
203   if (candidate_area_->visible() || auxiliary_text_->visible() ||
204       preedit_->visible()) {
205     SizeToContents();
206   } else {
207     GetWidget()->Close();
208   }
209 }
210
211 void CandidateWindowView::HideLookupTable() {
212   candidate_area_->SetVisible(false);
213   auxiliary_text_->SetVisible(false);
214   UpdateVisibility();
215 }
216
217 void CandidateWindowView::HidePreeditText() {
218   preedit_->SetVisible(false);
219   UpdateVisibility();
220 }
221
222 void CandidateWindowView::ShowPreeditText() {
223   preedit_->SetVisible(true);
224   UpdateVisibility();
225 }
226
227 void CandidateWindowView::UpdatePreeditText(const base::string16& text) {
228   preedit_->SetText(text);
229 }
230
231 void CandidateWindowView::ShowLookupTable() {
232   candidate_area_->SetVisible(true);
233   auxiliary_text_->SetVisible(candidate_window_.is_auxiliary_text_visible());
234   UpdateVisibility();
235 }
236
237 void CandidateWindowView::UpdateCandidates(
238     const ui::CandidateWindow& new_candidate_window) {
239   // Updating the candidate views is expensive. We'll skip this if possible.
240   if (!candidate_window_.IsEqual(new_candidate_window)) {
241     if (candidate_window_.orientation() != new_candidate_window.orientation()) {
242       // If the new layout is vertical, the aux text should appear at the
243       // bottom. If horizontal, it should appear between preedit and candidates.
244       if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) {
245         ReorderChildView(auxiliary_text_, -1);
246         auxiliary_text_->SetAlignment(gfx::ALIGN_RIGHT);
247         auxiliary_text_->SetBorderFromPosition(InformationTextArea::TOP);
248         candidate_area_->SetLayoutManager(new views::BoxLayout(
249             views::BoxLayout::kVertical, 0, 0, 0));
250       } else {
251         ReorderChildView(auxiliary_text_, 1);
252         auxiliary_text_->SetAlignment(gfx::ALIGN_LEFT);
253         auxiliary_text_->SetBorderFromPosition(InformationTextArea::BOTTOM);
254         candidate_area_->SetLayoutManager(new views::BoxLayout(
255             views::BoxLayout::kHorizontal, 0, 0, 0));
256       }
257     }
258
259     // Initialize candidate views if necessary.
260     MaybeInitializeCandidateViews(new_candidate_window);
261
262     should_show_at_composition_head_
263         = new_candidate_window.show_window_at_composition();
264     // Compute the index of the current page.
265     const int current_page_index = ComputePageIndex(new_candidate_window);
266     if (current_page_index < 0)
267       return;
268
269     // Update the candidates in the current page.
270     const size_t start_from =
271         current_page_index * new_candidate_window.page_size();
272
273     int max_shortcut_width = 0;
274     int max_candidate_width = 0;
275     for (size_t i = 0; i < candidate_views_.size(); ++i) {
276       const size_t index_in_page = i;
277       const size_t candidate_index = start_from + index_in_page;
278       CandidateView* candidate_view = candidate_views_[index_in_page];
279       // Set the candidate text.
280       if (candidate_index < new_candidate_window.candidates().size()) {
281         const ui::CandidateWindow::Entry& entry =
282             new_candidate_window.candidates()[candidate_index];
283         candidate_view->SetEntry(entry);
284         candidate_view->SetEnabled(true);
285         candidate_view->SetInfolistIcon(!entry.description_title.empty());
286       } else {
287         // Disable the empty row.
288         candidate_view->SetEntry(ui::CandidateWindow::Entry());
289         candidate_view->SetEnabled(false);
290         candidate_view->SetInfolistIcon(false);
291       }
292       if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) {
293         int shortcut_width = 0;
294         int candidate_width = 0;
295         candidate_views_[i]->GetPreferredWidths(
296             &shortcut_width, &candidate_width);
297         max_shortcut_width = std::max(max_shortcut_width, shortcut_width);
298         max_candidate_width = std::max(max_candidate_width, candidate_width);
299       }
300     }
301     if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL) {
302       for (size_t i = 0; i < candidate_views_.size(); ++i)
303         candidate_views_[i]->SetWidths(max_shortcut_width, max_candidate_width);
304     }
305
306     CandidateWindowBorder* border = static_cast<CandidateWindowBorder*>(
307         GetBubbleFrameView()->bubble_border());
308     if (new_candidate_window.orientation() == ui::CandidateWindow::VERTICAL)
309       border->set_offset(max_shortcut_width);
310     else
311       border->set_offset(0);
312   }
313   // Update the current candidate window. We'll use candidate_window_ from here.
314   // Note that SelectCandidateAt() uses candidate_window_.
315   candidate_window_.CopyFrom(new_candidate_window);
316
317   // Select the current candidate in the page.
318   if (candidate_window_.is_cursor_visible()) {
319     if (candidate_window_.page_size()) {
320       const int current_candidate_in_page =
321           candidate_window_.cursor_position() % candidate_window_.page_size();
322       SelectCandidateAt(current_candidate_in_page);
323     }
324   } else {
325     // Unselect the currently selected candidate.
326     if (0 <= selected_candidate_index_in_page_ &&
327         static_cast<size_t>(selected_candidate_index_in_page_) <
328         candidate_views_.size()) {
329       candidate_views_[selected_candidate_index_in_page_]->SetHighlighted(
330           false);
331       selected_candidate_index_in_page_ = -1;
332     }
333   }
334
335   // Updates auxiliary text
336   auxiliary_text_->SetVisible(candidate_window_.is_auxiliary_text_visible());
337   auxiliary_text_->SetText(base::UTF8ToUTF16(
338       candidate_window_.auxiliary_text()));
339 }
340
341 void CandidateWindowView::SetCursorBounds(const gfx::Rect& cursor_bounds,
342                                           const gfx::Rect& composition_head) {
343   if (candidate_window_.show_window_at_composition())
344     SetAnchorRect(composition_head);
345   else
346     SetAnchorRect(cursor_bounds);
347 }
348
349 void CandidateWindowView::MaybeInitializeCandidateViews(
350     const ui::CandidateWindow& candidate_window) {
351   const ui::CandidateWindow::Orientation orientation =
352       candidate_window.orientation();
353   const size_t page_size = candidate_window.page_size();
354
355   // Reset all candidate_views_ when orientation changes.
356   if (orientation != candidate_window_.orientation())
357     STLDeleteElements(&candidate_views_);
358
359   while (page_size < candidate_views_.size()) {
360     delete candidate_views_.back();
361     candidate_views_.pop_back();
362   }
363   while (page_size > candidate_views_.size()) {
364     CandidateView* new_candidate = new CandidateView(this, orientation);
365     candidate_area_->AddChildView(new_candidate);
366     candidate_views_.push_back(new_candidate);
367   }
368 }
369
370 void CandidateWindowView::SelectCandidateAt(int index_in_page) {
371   const int current_page_index = ComputePageIndex(candidate_window_);
372   if (current_page_index < 0) {
373     return;
374   }
375
376   const int cursor_absolute_index =
377       candidate_window_.page_size() * current_page_index + index_in_page;
378   // Ignore click on out of range views.
379   if (cursor_absolute_index < 0 ||
380       candidate_window_.candidates().size() <=
381       static_cast<size_t>(cursor_absolute_index)) {
382     return;
383   }
384
385   // Remember the currently selected candidate index in the current page.
386   selected_candidate_index_in_page_ = index_in_page;
387
388   // Select the candidate specified by index_in_page.
389   candidate_views_[index_in_page]->SetHighlighted(true);
390
391   // Update the cursor indexes in the model.
392   candidate_window_.set_cursor_position(cursor_absolute_index);
393 }
394
395 void CandidateWindowView::ButtonPressed(views::Button* sender,
396                                         const ui::Event& event) {
397   for (size_t i = 0; i < candidate_views_.size(); ++i) {
398     if (sender == candidate_views_[i]) {
399       FOR_EACH_OBSERVER(Observer, observers_, OnCandidateCommitted(i));
400       return;
401     }
402   }
403 }
404
405 }  // namespace ime
406 }  // namespace ash