Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(124)

Side by Side Diff: chrome/browser/chromeos/input_method/candidate_window_view.cc

Issue 121163003: Cleanup CandidateWindowView. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix Created 6 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 #include "chrome/browser/chromeos/input_method/candidate_window_view.h" 4 #include "chrome/browser/chromeos/input_method/candidate_window_view.h"
5 5
6 #include <string> 6 #include <string>
7 7
8 #include "ash/shell.h"
9 #include "base/strings/utf_string_conversions.h" 8 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/chromeos/input_method/candidate_view.h" 9 #include "chrome/browser/chromeos/input_method/candidate_view.h"
11 #include "chrome/browser/chromeos/input_method/candidate_window_constants.h" 10 #include "chrome/browser/chromeos/input_method/candidate_window_constants.h"
12 #include "chrome/browser/chromeos/input_method/hidable_area.h"
13 #include "chromeos/ime/candidate_window.h" 11 #include "chromeos/ime/candidate_window.h"
14 #include "ui/gfx/color_utils.h" 12 #include "ui/gfx/color_utils.h"
13 #include "ui/gfx/screen.h"
15 #include "ui/native_theme/native_theme.h" 14 #include "ui/native_theme/native_theme.h"
16 #include "ui/views/background.h" 15 #include "ui/views/background.h"
17 #include "ui/views/border.h" 16 #include "ui/views/border.h"
17 #include "ui/views/bubble/bubble_frame_view.h"
18 #include "ui/views/controls/label.h" 18 #include "ui/views/controls/label.h"
19 #include "ui/views/layout/grid_layout.h" 19 #include "ui/views/corewm/window_animations.h"
20 #include "ui/views/widget/widget.h" 20 #include "ui/views/layout/box_layout.h"
21 #include "ui/views/layout/fill_layout.h"
21 22
22 namespace chromeos { 23 namespace chromeos {
23 namespace input_method { 24 namespace input_method {
24 25
25 namespace { 26 namespace {
26 // VerticalCandidateLabel is used for rendering candidate text in 27
27 // the vertical candidate window. 28 class CandidateWindowBorder : public views::BubbleBorder {
28 class VerticalCandidateLabel : public views::Label {
29 public: 29 public:
30 VerticalCandidateLabel() {} 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 virtual ~CandidateWindowBorder() {}
39
40 void set_offset(int offset) { offset_ = offset; }
31 41
32 private: 42 private:
33 virtual ~VerticalCandidateLabel() {} 43 // Overridden from views::BubbleBorder:
44 virtual 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()));
34 51
35 // Returns the preferred size, but guarantees that the width has at 52 // It cannot use the normal logic of arrow offset for horizontal offscreen,
36 // least kMinCandidateLabelWidth pixels. 53 // because the arrow must be in the content's edge. But CandidateWindow has
37 virtual gfx::Size GetPreferredSize() OVERRIDE { 54 // to be visible even when |anchor_rect| is out of the screen.
38 gfx::Size size = Label::GetPreferredSize(); 55 gfx::Rect work_area = gfx::Screen::GetNativeScreen()->
39 // Hack. +2 is needed to prevent labels from getting elided like 56 GetDisplayNearestWindow(parent_).work_area();
40 // "abc..." in some cases. TODO(satorux): Figure out why it's 57 if (bounds.right() > work_area.right())
41 // necessary. 58 bounds.set_x(work_area.right() - bounds.width());
42 size.set_width(size.width() + 2); 59 if (bounds.x() < work_area.x())
43 if (size.width() < kMinCandidateLabelWidth) { 60 bounds.set_x(work_area.x());
44 size.set_width(kMinCandidateLabelWidth); 61
45 } 62 return bounds;
46 if (size.width() > kMaxCandidateLabelWidth) {
47 size.set_width(kMaxCandidateLabelWidth);
48 }
49 return size;
50 } 63 }
51 64
52 DISALLOW_COPY_AND_ASSIGN(VerticalCandidateLabel); 65 virtual gfx::Insets GetInsets() const OVERRIDE {
53 }; 66 return gfx::Insets();
54
55 // Wraps the given view with some padding, and returns it.
56 views::View* WrapWithPadding(views::View* view, const gfx::Insets& insets) {
57 views::View* wrapper = new views::View;
58 // Use GridLayout to give some insets inside.
59 views::GridLayout* layout = new views::GridLayout(wrapper);
60 wrapper->SetLayoutManager(layout); // |wrapper| owns |layout|.
61 layout->SetInsets(insets);
62
63 views::ColumnSet* column_set = layout->AddColumnSet(0);
64 column_set->AddColumn(
65 views::GridLayout::FILL, views::GridLayout::FILL,
66 1, views::GridLayout::USE_PREF, 0, 0);
67 layout->StartRow(0, 0);
68
69 // Add the view contents.
70 layout->AddView(view); // |view| is owned by |wraper|, not |layout|.
71 return wrapper;
72 }
73
74 // Creates shortcut text from the given index and the orientation.
75 base::string16 CreateShortcutText(size_t index,
76 const CandidateWindow& candidate_window) {
77 if (index >= candidate_window.candidates().size())
78 return base::string16();
79 std::string shortcut_text = candidate_window.candidates()[index].label;
80 if (!shortcut_text.empty() &&
81 candidate_window.orientation() != CandidateWindow::VERTICAL)
82 shortcut_text += '.';
83 return base::UTF8ToUTF16(shortcut_text);
84 }
85
86 // Creates the shortcut label, and returns it (never returns NULL).
87 // The label text is not set in this function.
88 views::Label* CreateShortcutLabel(
89 CandidateWindow::Orientation orientation, const ui::NativeTheme& theme) {
90 // Create the shortcut label. The label will be owned by
91 // |wrapped_shortcut_label|, hence it's deleted when
92 // |wrapped_shortcut_label| is deleted.
93 views::Label* shortcut_label = new views::Label;
94
95 if (orientation == CandidateWindow::VERTICAL) {
96 shortcut_label->SetFont(
97 shortcut_label->font().DeriveFont(kFontSizeDelta, gfx::Font::BOLD));
98 } else {
99 shortcut_label->SetFont(
100 shortcut_label->font().DeriveFont(kFontSizeDelta));
101 }
102 // TODO(satorux): Maybe we need to use language specific fonts for
103 // candidate_label, like Chinese font for Chinese input method?
104 shortcut_label->SetEnabledColor(theme.GetSystemColor(
105 ui::NativeTheme::kColorId_LabelEnabledColor));
106 shortcut_label->SetDisabledColor(theme.GetSystemColor(
107 ui::NativeTheme::kColorId_LabelDisabledColor));
108
109 return shortcut_label;
110 }
111
112 // Wraps the shortcut label, then decorates wrapped shortcut label
113 // and returns it (never returns NULL).
114 // The label text is not set in this function.
115 views::View* CreateWrappedShortcutLabel(
116 views::Label* shortcut_label,
117 CandidateWindow::Orientation orientation,
118 const ui::NativeTheme& theme) {
119 // Wrap it with padding.
120 const gfx::Insets kVerticalShortcutLabelInsets(1, 6, 1, 6);
121 const gfx::Insets kHorizontalShortcutLabelInsets(1, 3, 1, 0);
122 const gfx::Insets insets =
123 (orientation == CandidateWindow::VERTICAL ?
124 kVerticalShortcutLabelInsets :
125 kHorizontalShortcutLabelInsets);
126 views::View* wrapped_shortcut_label =
127 WrapWithPadding(shortcut_label, insets);
128
129 // Add decoration based on the orientation.
130 if (orientation == CandidateWindow::VERTICAL) {
131 // Set the background color.
132 SkColor blackish = color_utils::AlphaBlend(
133 SK_ColorBLACK,
134 theme.GetSystemColor(ui::NativeTheme::kColorId_WindowBackground),
135 0x40);
136 SkColor transparent_blakish = color_utils::AlphaBlend(
137 SK_ColorTRANSPARENT, blackish, 0xE0);
138 wrapped_shortcut_label->set_background(
139 views::Background::CreateSolidBackground(transparent_blakish));
140 shortcut_label->SetBackgroundColor(
141 wrapped_shortcut_label->background()->get_color());
142 } 67 }
143 68
144 return wrapped_shortcut_label; 69 gfx::NativeView parent_;
145 } 70 int offset_;
146 71
147 // Creates the candidate label, and returns it (never returns NULL). 72 DISALLOW_COPY_AND_ASSIGN(CandidateWindowBorder);
148 // The label text is not set in this function. 73 };
149 views::Label* CreateCandidateLabel(
150 CandidateWindow::Orientation orientation) {
151 views::Label* candidate_label = NULL;
152
153 // Create the candidate label. The label will be added to |this| as a
154 // child view, hence it's deleted when |this| is deleted.
155 if (orientation == CandidateWindow::VERTICAL) {
156 candidate_label = new VerticalCandidateLabel;
157 } else {
158 candidate_label = new views::Label;
159 }
160
161 // Change the font size.
162 candidate_label->SetFont(
163 candidate_label->font().DeriveFont(kFontSizeDelta));
164 candidate_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
165
166 return candidate_label;
167 }
168
169 // Creates the annotation label, and return it (never returns NULL).
170 // The label text is not set in this function.
171 views::Label* CreateAnnotationLabel(
172 CandidateWindow::Orientation orientation, const ui::NativeTheme& theme) {
173 // Create the annotation label.
174 views::Label* annotation_label = new views::Label;
175
176 // Change the font size and color.
177 annotation_label->SetFont(
178 annotation_label->font().DeriveFont(kFontSizeDelta));
179 annotation_label->SetEnabledColor(theme.GetSystemColor(
180 ui::NativeTheme::kColorId_LabelDisabledColor));
181 annotation_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
182
183 return annotation_label;
184 }
185
186 // Computes shortcut column size.
187 gfx::Size ComputeShortcutColumnSize(
188 const CandidateWindow& candidate_window,
189 const ui::NativeTheme& theme) {
190 int shortcut_column_width = 0;
191 int shortcut_column_height = 0;
192 // Create the shortcut label. The label will be owned by
193 // |wrapped_shortcut_label|, hence it's deleted when
194 // |wrapped_shortcut_label| is deleted.
195 views::Label* shortcut_label = CreateShortcutLabel(
196 candidate_window.orientation(), theme);
197 scoped_ptr<views::View> wrapped_shortcut_label(
198 CreateWrappedShortcutLabel(shortcut_label,
199 candidate_window.orientation(),
200 theme));
201
202 // Compute the max width and height in shortcut labels.
203 // We'll create temporary shortcut labels, and choose the largest width and
204 // height.
205 for (size_t i = 0; i < candidate_window.page_size(); ++i) {
206 shortcut_label->SetText(CreateShortcutText(i, candidate_window));
207 gfx::Size text_size = wrapped_shortcut_label->GetPreferredSize();
208 shortcut_column_width = std::max(shortcut_column_width, text_size.width());
209 shortcut_column_height = std::max(shortcut_column_height,
210 text_size.height());
211 }
212
213 return gfx::Size(shortcut_column_width, shortcut_column_height);
214 }
215 74
216 // Computes the page index. For instance, if the page size is 9, and the 75 // Computes the page index. For instance, if the page size is 9, and the
217 // cursor is pointing to 13th candidate, the page index will be 1 (2nd 76 // cursor is pointing to 13th candidate, the page index will be 1 (2nd
218 // page, as the index is zero-origin). Returns -1 on error. 77 // page, as the index is zero-origin). Returns -1 on error.
219 int ComputePageIndex(const CandidateWindow& candidate_window) { 78 int ComputePageIndex(const CandidateWindow& candidate_window) {
220 if (candidate_window.page_size() > 0) 79 if (candidate_window.page_size() > 0)
221 return candidate_window.cursor_position() / candidate_window.page_size(); 80 return candidate_window.cursor_position() / candidate_window.page_size();
222 return -1; 81 return -1;
223 } 82 }
224 83
225 // Computes candidate column size.
226 gfx::Size ComputeCandidateColumnSize(
227 const CandidateWindow& candidate_window) {
228 int candidate_column_width = 0;
229 int candidate_column_height = 0;
230 scoped_ptr<views::Label> candidate_label(
231 CreateCandidateLabel(candidate_window.orientation()));
232
233 // Compute the start index of |candidate_window_|.
234 const int current_page_index = ComputePageIndex(candidate_window);
235 if (current_page_index < 0)
236 return gfx::Size(0, 0);
237 const size_t start_from = current_page_index * candidate_window.page_size();
238
239 // Compute the max width and height in candidate labels.
240 // We'll create temporary candidate labels, and choose the largest width and
241 // height.
242 for (size_t i = 0;
243 i + start_from < candidate_window.candidates().size();
244 ++i) {
245 const size_t index = start_from + i;
246
247 candidate_label->SetText(
248 base::UTF8ToUTF16(candidate_window.candidates()[index].value));
249 gfx::Size text_size = candidate_label->GetPreferredSize();
250 candidate_column_width = std::max(candidate_column_width,
251 text_size.width());
252 candidate_column_height = std::max(candidate_column_height,
253 text_size.height());
254 }
255
256 return gfx::Size(candidate_column_width, candidate_column_height);
257 }
258
259 // Computes annotation column size.
260 gfx::Size ComputeAnnotationColumnSize(
261 const CandidateWindow& candidate_window, const ui::NativeTheme& theme) {
262 int annotation_column_width = 0;
263 int annotation_column_height = 0;
264 scoped_ptr<views::Label> annotation_label(
265 CreateAnnotationLabel(candidate_window.orientation(), theme));
266
267 // Compute the start index of |candidate_window_|.
268 const int current_page_index = ComputePageIndex(candidate_window);
269 if (current_page_index < 0)
270 return gfx::Size(0, 0);
271 const size_t start_from = current_page_index * candidate_window.page_size();
272
273 // Compute max width and height in annotation labels.
274 // We'll create temporary annotation labels, and choose the largest width and
275 // height.
276 for (size_t i = 0;
277 i + start_from < candidate_window.candidates().size();
278 ++i) {
279 const size_t index = start_from + i;
280
281 annotation_label->SetText(
282 base::UTF8ToUTF16(candidate_window.candidates()[index].annotation));
283 gfx::Size text_size = annotation_label->GetPreferredSize();
284 annotation_column_width = std::max(annotation_column_width,
285 text_size.width());
286 annotation_column_height = std::max(annotation_column_height,
287 text_size.height());
288 }
289
290 return gfx::Size(annotation_column_width, annotation_column_height);
291 }
292
293 } // namespace 84 } // namespace
294 85
295 // InformationTextArea is a HidableArea having a single Label in it. 86 class InformationTextArea : public views::View {
296 class InformationTextArea : public HidableArea {
297 public: 87 public:
88 // InformationTextArea's border is drawn as a separator, it should appear
89 // at either top or bottom.
90 enum BorderPosition {
91 TOP,
92 BOTTOM
93 };
94
298 // Specify the alignment and initialize the control. 95 // Specify the alignment and initialize the control.
299 InformationTextArea(gfx::HorizontalAlignment align, int minWidth) 96 InformationTextArea(gfx::HorizontalAlignment align, int min_width)
300 : minWidth_(minWidth) { 97 : min_width_(min_width) {
301 label_ = new views::Label; 98 label_ = new views::Label;
302 label_->SetHorizontalAlignment(align); 99 label_->SetHorizontalAlignment(align);
100 label_->set_border(views::Border::CreateEmptyBorder(2, 2, 2, 4));
303 101
304 const gfx::Insets kInsets(2, 2, 2, 4); 102 SetLayoutManager(new views::FillLayout());
305 views::View* contents = WrapWithPadding(label_, kInsets); 103 AddChildView(label_);
306 SetContents(contents); 104 set_background(views::Background::CreateSolidBackground(
307 contents->set_border(views::Border::CreateSolidBorder(
308 1,
309 GetNativeTheme()->GetSystemColor(
310 ui::NativeTheme::kColorId_MenuBorderColor)));
311 contents->set_background(views::Background::CreateSolidBackground(
312 color_utils::AlphaBlend(SK_ColorBLACK, 105 color_utils::AlphaBlend(SK_ColorBLACK,
313 GetNativeTheme()->GetSystemColor( 106 GetNativeTheme()->GetSystemColor(
314 ui::NativeTheme::kColorId_WindowBackground), 107 ui::NativeTheme::kColorId_WindowBackground),
315 0x10))); 108 0x10)));
316 label_->SetBackgroundColor(contents->background()->get_color());
317 } 109 }
318 110
319 // Set the displayed text. 111 // Sets the text alignment.
112 void SetAlignment(gfx::HorizontalAlignment alignment) {
113 label_->SetHorizontalAlignment(alignment);
114 }
115
116 // Sets the displayed text.
320 void SetText(const std::string& utf8_text) { 117 void SetText(const std::string& utf8_text) {
321 label_->SetText(base::UTF8ToUTF16(utf8_text)); 118 label_->SetText(base::UTF8ToUTF16(utf8_text));
322 } 119 }
323 120
121 // Sets the border thickness for top/bottom.
122 void SetBorder(BorderPosition position) {
123 set_border(views::Border::CreateSolidSidedBorder(
124 (position == TOP) ? 1 : 0, 0, (position == BOTTOM) ? 1 : 0, 0,
125 GetNativeTheme()->GetSystemColor(
126 ui::NativeTheme::kColorId_MenuBorderColor)));
127 }
128
324 protected: 129 protected:
325 virtual gfx::Size GetPreferredSize() OVERRIDE { 130 virtual gfx::Size GetPreferredSize() OVERRIDE {
326 gfx::Size size = HidableArea::GetPreferredSize(); 131 gfx::Size size = views::View::GetPreferredSize();
327 // Hack. +2 is needed as the same reason as in VerticalCandidateLabel 132 size.SetToMax(gfx::Size(min_width_, 0));
328 size.set_width(size.width() + 2);
329 if (size.width() < minWidth_) {
330 size.set_width(minWidth_);
331 }
332 return size; 133 return size;
333 } 134 }
334 135
335 private: 136 private:
336 views::Label* label_; 137 views::Label* label_;
337 int minWidth_; 138 int min_width_;
338 139
339 DISALLOW_COPY_AND_ASSIGN(InformationTextArea); 140 DISALLOW_COPY_AND_ASSIGN(InformationTextArea);
340 }; 141 };
341 142
342 CandidateView::CandidateView( 143 CandidateWindowView::CandidateWindowView(gfx::NativeView parent)
343 CandidateWindowView* parent_candidate_window,
344 int index_in_page,
345 CandidateWindow::Orientation orientation)
346 : index_in_page_(index_in_page),
347 orientation_(orientation),
348 parent_candidate_window_(parent_candidate_window),
349 shortcut_label_(NULL),
350 candidate_label_(NULL),
351 annotation_label_(NULL),
352 infolist_icon_(NULL),
353 infolist_icon_enabled_(false) {
354 }
355
356 void CandidateView::Init(int shortcut_column_width,
357 int candidate_column_width,
358 int annotation_column_width,
359 int column_height) {
360 views::GridLayout* layout = new views::GridLayout(this);
361 SetLayoutManager(layout); // |this| owns |layout|.
362
363 // Create Labels.
364 const ui::NativeTheme& theme = *GetNativeTheme();
365 shortcut_label_ = CreateShortcutLabel(orientation_, theme);
366 views::View* wrapped_shortcut_label =
367 CreateWrappedShortcutLabel(shortcut_label_, orientation_, theme);
368 candidate_label_ = CreateCandidateLabel(orientation_);
369 annotation_label_ = CreateAnnotationLabel(orientation_, theme);
370
371 // Initialize the column set with three columns.
372 views::ColumnSet* column_set = layout->AddColumnSet(0);
373
374 // If orientation is vertical, each column width is fixed.
375 // Otherwise the width is resizable.
376 const views::GridLayout::SizeType column_type =
377 orientation_ == CandidateWindow::VERTICAL ?
378 views::GridLayout::FIXED : views::GridLayout::USE_PREF;
379
380 const int padding_column_width =
381 orientation_ == CandidateWindow::VERTICAL ? 4 : 6;
382
383 // Set shortcut column type and width.
384 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
385 0, column_type, shortcut_column_width, 0);
386 column_set->AddPaddingColumn(0, padding_column_width);
387
388 // Set candidate column type and width.
389 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
390 1, views::GridLayout::USE_PREF, 0,
391 orientation_ == CandidateWindow::VERTICAL ?
392 candidate_column_width : 0);
393 column_set->AddPaddingColumn(0, padding_column_width);
394
395 // Set annotation column type and width.
396 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
397 0, column_type, annotation_column_width, 0);
398
399 if (orientation_ == CandidateWindow::VERTICAL) {
400 column_set->AddPaddingColumn(0, 1);
401 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
402 views::GridLayout::FIXED, kInfolistIndicatorIconWidth,
403 0);
404 column_set->AddPaddingColumn(0, 2);
405 } else {
406 column_set->AddPaddingColumn(0, padding_column_width);
407 }
408
409 // Add the shortcut label, the candidate label, and annotation label.
410 layout->StartRow(0, 0);
411 // |wrapped_shortcut_label|, |candidate_label_|, and |annotation_label_|
412 // will be owned by |this|.
413 layout->AddView(wrapped_shortcut_label,
414 1, // Column span.
415 1, // Row span.
416 views::GridLayout::FILL, // Horizontal alignment.
417 views::GridLayout::FILL, // Vertical alignment.
418 -1, // Preferred width, not specified.
419 column_height); // Preferred height.
420 layout->AddView(candidate_label_,
421 1, // Column span.
422 1, // Row span.
423 views::GridLayout::FILL, // Horizontal alignment.
424 views::GridLayout::FILL, // Vertical alignment.
425 -1, // Preferred width, not specified.
426 column_height); // Preferred height.
427 layout->AddView(annotation_label_,
428 1, // Column span.
429 1, // Row span.
430 views::GridLayout::FILL, // Horizontal alignment.
431 views::GridLayout::FILL, // Vertical alignemnt.
432 -1, // Preferred width, not specified.
433 column_height); // Preferred height.
434 if (orientation_ == CandidateWindow::VERTICAL) {
435 infolist_icon_ = new views::View;
436 views::View* infolist_icon_wrapper = new views::View;
437 views::GridLayout* infolist_icon_layout =
438 new views::GridLayout(infolist_icon_wrapper);
439 // |infolist_icon_layout| is owned by |infolist_icon_wrapper|.
440 infolist_icon_wrapper->SetLayoutManager(infolist_icon_layout);
441 infolist_icon_layout->AddColumnSet(0)->AddColumn(
442 views::GridLayout::FILL, views::GridLayout::FILL,
443 0, views::GridLayout::FIXED, kInfolistIndicatorIconWidth, 0);
444 infolist_icon_layout->AddPaddingRow(0, kInfolistIndicatorIconPadding);
445 infolist_icon_layout->StartRow(1.0, 0); // infolist_icon_ is resizable.
446 // |infolist_icon_| is owned by |infolist_icon_wrapper|.
447 infolist_icon_layout->AddView(infolist_icon_);
448 infolist_icon_layout->AddPaddingRow(0, kInfolistIndicatorIconPadding);
449 // |infolist_icon_wrapper| is owned by |this|.
450 layout->AddView(infolist_icon_wrapper);
451 }
452 UpdateLabelBackgroundColors();
453 }
454
455 void CandidateView::SetCandidateText(const base::string16& text) {
456 candidate_label_->SetText(text);
457 }
458
459 void CandidateView::SetShortcutText(const base::string16& text) {
460 shortcut_label_->SetText(text);
461 }
462
463 void CandidateView::SetAnnotationText(const base::string16& text) {
464 annotation_label_->SetText(text);
465 }
466
467 void CandidateView::SetInfolistIcon(bool enable) {
468 if (!infolist_icon_ || (infolist_icon_enabled_ == enable))
469 return;
470 infolist_icon_enabled_ = enable;
471 infolist_icon_->set_background(
472 enable ?
473 views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
474 ui::NativeTheme::kColorId_FocusedBorderColor)) :
475 NULL);
476 UpdateLabelBackgroundColors();
477 SchedulePaint();
478 }
479
480 void CandidateView::Select() {
481 set_background(
482 views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
483 ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused)));
484 set_border(views::Border::CreateSolidBorder(
485 1, GetNativeTheme()->GetSystemColor(
486 ui::NativeTheme::kColorId_FocusedBorderColor)));
487 UpdateLabelBackgroundColors();
488 // Need to call SchedulePaint() for background and border color changes.
489 SchedulePaint();
490 }
491
492 void CandidateView::Unselect() {
493 set_background(NULL);
494 set_border(NULL);
495 UpdateLabelBackgroundColors();
496 SchedulePaint(); // See comments at Select().
497 }
498
499 void CandidateView::SetRowEnabled(bool enabled) {
500 shortcut_label_->SetEnabled(enabled);
501 }
502
503 gfx::Point CandidateView::GetCandidateLabelPosition() const {
504 return candidate_label_->GetMirroredPosition();
505 }
506
507 bool CandidateView::OnMousePressed(const ui::MouseEvent& event) {
508 // TODO(kinaba): On Windows and MacOS, candidate windows typically commits a
509 // candidate at OnMouseReleased event. We have chosen OnMousePressed here for
510 // working around several obstacle rising from views implementation over GTK.
511 // See: http://crosbug.com/11423#c11. Since we have moved from GTK to Aura,
512 // the reasoning should have became obsolete. We might want to reconsider
513 // implementing mouse-up selection.
514 SelectCandidateAt(event.location());
515 return false;
516 }
517
518 void CandidateView::OnGestureEvent(ui::GestureEvent* event) {
519 if (event->type() == ui::ET_GESTURE_TAP) {
520 SelectCandidateAt(event->location());
521 event->SetHandled();
522 return;
523 }
524 View::OnGestureEvent(event);
525 }
526
527 void CandidateView::SelectCandidateAt(const gfx::Point& location) {
528 gfx::Point location_in_candidate_window = location;
529 views::View::ConvertPointToTarget(this, parent_candidate_window_,
530 &location_in_candidate_window);
531 parent_candidate_window_->OnCandidatePressed(location_in_candidate_window);
532 parent_candidate_window_->CommitCandidate();
533 }
534
535 void CandidateView::UpdateLabelBackgroundColors() {
536 SkColor color = background() ?
537 background()->get_color() :
538 GetNativeTheme()->GetSystemColor(
539 ui::NativeTheme::kColorId_WindowBackground);
540 if (orientation_ != CandidateWindow::VERTICAL)
541 shortcut_label_->SetBackgroundColor(color);
542 candidate_label_->SetBackgroundColor(color);
543 annotation_label_->SetBackgroundColor(color);
544 }
545
546 CandidateWindowView::CandidateWindowView(views::Widget* parent_frame)
547 : selected_candidate_index_in_page_(-1), 144 : selected_candidate_index_in_page_(-1),
548 parent_frame_(parent_frame),
549 preedit_area_(NULL),
550 header_area_(NULL),
551 candidate_area_(NULL),
552 footer_area_(NULL),
553 previous_shortcut_column_size_(0, 0),
554 previous_candidate_column_size_(0, 0),
555 previous_annotation_column_size_(0, 0),
556 should_show_at_composition_head_(false), 145 should_show_at_composition_head_(false),
557 should_show_upper_side_(false), 146 should_show_upper_side_(false),
558 was_candidate_window_open_(false) { 147 was_candidate_window_open_(false) {
148 set_parent_window(parent);
149 set_margins(gfx::Insets());
150
151 // Set the background and the border of the view.
152 ui::NativeTheme* theme = GetNativeTheme();
153 set_background(
154 views::Background::CreateSolidBackground(theme->GetSystemColor(
155 ui::NativeTheme::kColorId_WindowBackground)));
156 set_border(views::Border::CreateSolidBorder(
157 1, theme->GetSystemColor(ui::NativeTheme::kColorId_MenuBorderColor)));
158
159 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
160 auxiliary_text_ = new InformationTextArea(gfx::ALIGN_RIGHT, 0);
161 preedit_ = new InformationTextArea(gfx::ALIGN_LEFT, kMinPreeditAreaWidth);
162 candidate_area_ = new views::View;
163 auxiliary_text_->SetVisible(false);
164 preedit_->SetVisible(false);
165 candidate_area_->SetVisible(false);
166 preedit_->SetBorder(InformationTextArea::BOTTOM);
167 if (candidate_window_.orientation() == CandidateWindow::VERTICAL) {
168 AddChildView(preedit_);
169 AddChildView(candidate_area_);
170 AddChildView(auxiliary_text_);
171 auxiliary_text_->SetBorder(InformationTextArea::TOP);
172 candidate_area_->SetLayoutManager(new views::BoxLayout(
173 views::BoxLayout::kVertical, 0, 0, 0));
174 } else {
175 AddChildView(preedit_);
176 AddChildView(auxiliary_text_);
177 AddChildView(candidate_area_);
178 auxiliary_text_->SetAlignment(gfx::ALIGN_LEFT);
179 auxiliary_text_->SetBorder(InformationTextArea::BOTTOM);
180 candidate_area_->SetLayoutManager(new views::BoxLayout(
181 views::BoxLayout::kHorizontal, 0, 0, 0));
182 }
559 } 183 }
560 184
561 CandidateWindowView::~CandidateWindowView() { 185 CandidateWindowView::~CandidateWindowView() {
562 } 186 }
563 187
564 void CandidateWindowView::Init() { 188 views::Widget* CandidateWindowView::InitWidget() {
565 // Set the background and the border of the view. 189 views::Widget* widget = BubbleDelegateView::CreateBubble(this);
566 set_background(
567 views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(
568 ui::NativeTheme::kColorId_WindowBackground)));
569 set_border(views::Border::CreateSolidBorder(
570 1, GetNativeTheme()->GetSystemColor(
571 ui::NativeTheme::kColorId_MenuBorderColor)));
572 190
573 // Create areas. 191 views::corewm::SetWindowVisibilityAnimationType(
574 preedit_area_ = new InformationTextArea(gfx::ALIGN_LEFT, 192 widget->GetNativeView(),
575 kMinPreeditAreaWidth); 193 views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
576 header_area_ = new InformationTextArea(gfx::ALIGN_LEFT, 0);
577 candidate_area_ = new HidableArea;
578 candidate_area_->SetContents(new views::View);
579 footer_area_ = new InformationTextArea(gfx::ALIGN_RIGHT, 0);
580 194
581 // Set the window layout of the view 195 GetBubbleFrameView()->SetBubbleBorder(
582 views::GridLayout* layout = new views::GridLayout(this); 196 new CandidateWindowBorder(parent_window()));
583 SetLayoutManager(layout); // |this| owns |layout|. 197 return widget;
584 views::ColumnSet* column_set = layout->AddColumnSet(0);
585 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
586 0, views::GridLayout::USE_PREF, 0, 0);
587
588 // Add the preedit area
589 layout->StartRow(0, 0);
590 layout->AddView(preedit_area_); // |preedit_area_| is owned by |this|.
591
592 // Add the header area.
593 layout->StartRow(0, 0);
594 layout->AddView(header_area_); // |header_area_| is owned by |this|.
595
596 // Add the candidate area.
597 layout->StartRow(0, 0);
598 layout->AddView(candidate_area_); // |candidate_area_| is owned by |this|.
599
600 // Add the footer area.
601 layout->StartRow(0, 0);
602 layout->AddView(footer_area_); // |footer_area_| is owned by |this|.
603 } 198 }
604 199
605 void CandidateWindowView::HideAll() { 200 void CandidateWindowView::UpdateVisibility() {
606 parent_frame_->Hide(); 201 if (candidate_area_->visible() || auxiliary_text_->visible() ||
607 NotifyIfCandidateWindowOpenedOrClosed(); 202 preedit_->visible()) {
608 } 203 SizeToContents();
609
610 void CandidateWindowView::UpdateParentArea() {
611 if (candidate_area_->IsShown() ||
612 header_area_->IsShown() ||
613 footer_area_->IsShown() ||
614 preedit_area_->IsShown()) {
615 ResizeAndMoveParentFrame();
616 parent_frame_->Show();
617 } else { 204 } else {
618 parent_frame_->Hide(); 205 GetWidget()->Close();
619 } 206 }
620 NotifyIfCandidateWindowOpenedOrClosed();
621 } 207 }
622 208
623 void CandidateWindowView::HideLookupTable() { 209 void CandidateWindowView::HideLookupTable() {
624 candidate_area_->Hide(); 210 candidate_area_->SetVisible(false);
625 UpdateParentArea(); 211 UpdateVisibility();
626 } 212 }
627 213
628 void CandidateWindowView::HideAuxiliaryText() { 214 void CandidateWindowView::HideAuxiliaryText() {
629 header_area_->Hide(); 215 auxiliary_text_->SetVisible(false);
630 footer_area_->Hide(); 216 UpdateVisibility();
631 UpdateParentArea();
632 } 217 }
633 218
634 void CandidateWindowView::ShowAuxiliaryText() { 219 void CandidateWindowView::ShowAuxiliaryText() {
635 // If candidate_area is not shown, shows auxiliary text at header_area. 220 auxiliary_text_->SetVisible(true);
636 // We expect both header_area_ and footer_area_ contain same value. 221 UpdateVisibility();
637 if (!candidate_area_->IsShown()) {
638 header_area_->Show();
639 footer_area_->Hide();
640 } else {
641 // If candidate_area is shown, shows auxiliary text with orientation.
642 if (candidate_window_.orientation() == CandidateWindow::HORIZONTAL) {
643 header_area_->Show();
644 footer_area_->Hide();
645 } else {
646 footer_area_->Show();
647 header_area_->Hide();
648 }
649 }
650 UpdateParentArea();
651 } 222 }
652 223
653 void CandidateWindowView::UpdateAuxiliaryText(const std::string& utf8_text) { 224 void CandidateWindowView::UpdateAuxiliaryText(const std::string& utf8_text) {
654 header_area_->SetText(utf8_text); 225 auxiliary_text_->SetText(utf8_text);
655 footer_area_->SetText(utf8_text);
656 ShowAuxiliaryText(); 226 ShowAuxiliaryText();
657 } 227 }
658 228
659 void CandidateWindowView::HidePreeditText() { 229 void CandidateWindowView::HidePreeditText() {
660 preedit_area_->Hide(); 230 preedit_->SetVisible(false);
661 UpdateParentArea(); 231 UpdateVisibility();
662 } 232 }
663 233
664 void CandidateWindowView::ShowPreeditText() { 234 void CandidateWindowView::ShowPreeditText() {
665 preedit_area_->Show(); 235 preedit_->SetVisible(true);
666 UpdateParentArea(); 236 UpdateVisibility();
667 } 237 }
668 238
669 void CandidateWindowView::UpdatePreeditText(const std::string& utf8_text) { 239 void CandidateWindowView::UpdatePreeditText(const std::string& utf8_text) {
670 preedit_area_->SetText(utf8_text); 240 preedit_->SetText(utf8_text);
671 } 241 }
672 242
673 void CandidateWindowView::ShowLookupTable() { 243 void CandidateWindowView::ShowLookupTable() {
674 if (!candidate_area_->IsShown()) 244 candidate_area_->SetVisible(true);
675 should_show_upper_side_ = false; 245 UpdateVisibility();
676 candidate_area_->Show();
677 UpdateParentArea();
678 }
679
680 void CandidateWindowView::NotifyIfCandidateWindowOpenedOrClosed() {
681 bool is_open = IsCandidateWindowOpen();
682 if (!was_candidate_window_open_ && is_open) {
683 FOR_EACH_OBSERVER(Observer, observers_, OnCandidateWindowOpened());
684 } else if (was_candidate_window_open_ && !is_open) {
685 FOR_EACH_OBSERVER(Observer, observers_, OnCandidateWindowClosed());
686 }
687 was_candidate_window_open_ = is_open;
688 }
689
690 bool CandidateWindowView::ShouldUpdateCandidateViews(
691 const CandidateWindow& old_candidate_window,
692 const CandidateWindow& new_candidate_window) {
693 return !old_candidate_window.IsEqual(new_candidate_window);
694 } 246 }
695 247
696 void CandidateWindowView::UpdateCandidates( 248 void CandidateWindowView::UpdateCandidates(
697 const CandidateWindow& new_candidate_window) { 249 const CandidateWindow& new_candidate_window) {
698 const bool should_update = ShouldUpdateCandidateViews(candidate_window_,
699 new_candidate_window);
700 // Updating the candidate views is expensive. We'll skip this if possible. 250 // Updating the candidate views is expensive. We'll skip this if possible.
701 if (should_update) { 251 if (!candidate_window_.IsEqual(new_candidate_window)) {
252 if (candidate_window_.orientation() != new_candidate_window.orientation()) {
253 // If the new layout is vertical, the aux text should appear at the
254 // bottom. If horizontal, it should appear between preedit and candidates.
255 if (new_candidate_window.orientation() == CandidateWindow::VERTICAL) {
256 ReorderChildView(auxiliary_text_, -1);
257 auxiliary_text_->SetAlignment(gfx::ALIGN_RIGHT);
258 auxiliary_text_->SetBorder(InformationTextArea::TOP);
Hiro Komatsu 2013/12/27 07:13:34 Please double check if TOP and BOTTOM are correct
Jun Mukai 2013/12/27 11:01:13 This is correct. When vertical, the aux text will
259 candidate_area_->SetLayoutManager(new views::BoxLayout(
260 views::BoxLayout::kVertical, 0, 0, 0));
261 } else {
262 ReorderChildView(auxiliary_text_, 1);
263 auxiliary_text_->SetAlignment(gfx::ALIGN_LEFT);
264 auxiliary_text_->SetBorder(InformationTextArea::BOTTOM);
265 candidate_area_->SetLayoutManager(new views::BoxLayout(
266 views::BoxLayout::kHorizontal, 0, 0, 0));
267 }
268 }
269
702 // Initialize candidate views if necessary. 270 // Initialize candidate views if necessary.
703 MaybeInitializeCandidateViews(new_candidate_window); 271 MaybeInitializeCandidateViews(new_candidate_window);
704 272
705 should_show_at_composition_head_ 273 should_show_at_composition_head_
706 = new_candidate_window.show_window_at_composition(); 274 = new_candidate_window.show_window_at_composition();
707 // Compute the index of the current page. 275 // Compute the index of the current page.
708 const int current_page_index = ComputePageIndex(new_candidate_window); 276 const int current_page_index = ComputePageIndex(new_candidate_window);
709 if (current_page_index < 0) { 277 if (current_page_index < 0)
710 return; 278 return;
711 }
712 279
713 // Update the candidates in the current page. 280 // Update the candidates in the current page.
714 const size_t start_from = 281 const size_t start_from =
715 current_page_index * new_candidate_window.page_size(); 282 current_page_index * new_candidate_window.page_size();
716 283
717 // In some cases, engines send empty shortcut labels. For instance, 284 int max_shortcut_width = 0;
718 // ibus-mozc sends empty labels when they show suggestions. In this 285 int max_candidate_width = 0;
719 // case, we should not show shortcut labels.
720 bool no_shortcut_mode = true;
721 for (size_t i = 0; i < new_candidate_window.candidates().size(); ++i) {
722 if (!new_candidate_window.candidates()[i].label.empty()) {
723 no_shortcut_mode = false;
724 break;
725 }
726 }
727
728 for (size_t i = 0; i < candidate_views_.size(); ++i) { 286 for (size_t i = 0; i < candidate_views_.size(); ++i) {
729 const size_t index_in_page = i; 287 const size_t index_in_page = i;
730 const size_t candidate_index = start_from + index_in_page; 288 const size_t candidate_index = start_from + index_in_page;
731 CandidateView* candidate_view = candidate_views_[index_in_page]; 289 CandidateView* candidate_view = candidate_views_[index_in_page];
732 // Set the shortcut text.
733 if (no_shortcut_mode) {
734 candidate_view->SetShortcutText(base::string16());
735 } else {
736 // At this moment, we don't use labels sent from engines for UX
737 // reasons. First, we want to show shortcut labels in empty rows
738 // (ex. show 6, 7, 8, ... in empty rows when the number of
739 // candidates is 5). Second, we want to add a period after each
740 // shortcut label when the candidate window is horizontal.
741 candidate_view->SetShortcutText(
742 CreateShortcutText(i, new_candidate_window));
743 }
744 // Set the candidate text. 290 // Set the candidate text.
745 if (candidate_index < new_candidate_window.candidates().size()) { 291 if (candidate_index < new_candidate_window.candidates().size()) {
746 const CandidateWindow::Entry& entry = 292 const CandidateWindow::Entry& entry =
747 new_candidate_window.candidates()[candidate_index]; 293 new_candidate_window.candidates()[candidate_index];
748 candidate_view->SetCandidateText(base::UTF8ToUTF16(entry.value)); 294 candidate_view->SetEntry(entry);
749 candidate_view->SetAnnotationText(base::UTF8ToUTF16(entry.annotation)); 295 candidate_view->SetState(views::Button::STATE_NORMAL);
750 candidate_view->SetRowEnabled(true); 296 candidate_view->SetInfolistIcon(!entry.description_title.empty());
751 candidate_view->SetInfolistIcon(!entry.description_title.empty());
752 } else { 297 } else {
753 // Disable the empty row. 298 // Disable the empty row.
754 candidate_view->SetCandidateText(base::string16()); 299 candidate_view->SetEntry(CandidateWindow::Entry());
755 candidate_view->SetAnnotationText(base::string16()); 300 candidate_view->SetState(views::Button::STATE_DISABLED);
756 candidate_view->SetRowEnabled(false);
757 candidate_view->SetInfolistIcon(false); 301 candidate_view->SetInfolistIcon(false);
758 } 302 }
303 if (new_candidate_window.orientation() == CandidateWindow::VERTICAL) {
304 int shortcut_width = 0;
305 int candidate_width = 0;
306 candidate_views_[i]->GetPreferredWidths(
307 &shortcut_width, &candidate_width);
308 max_shortcut_width = std::max(max_shortcut_width, shortcut_width);
309 max_candidate_width = std::max(max_candidate_width, candidate_width);
310 }
759 } 311 }
312 if (new_candidate_window.orientation() == CandidateWindow::VERTICAL) {
313 for (size_t i = 0; i < candidate_views_.size(); ++i)
314 candidate_views_[i]->SetWidths(max_shortcut_width, max_candidate_width);
315 }
316
317 CandidateWindowBorder* border = static_cast<CandidateWindowBorder*>(
318 GetBubbleFrameView()->bubble_border());
319 if (new_candidate_window.orientation() == CandidateWindow::VERTICAL)
320 border->set_offset(max_shortcut_width);
321 else
322 border->set_offset(0);
760 } 323 }
761 // Update the current candidate window. We'll use candidate_window_ from here. 324 // Update the current candidate window. We'll use candidate_window_ from here.
762 // Note that SelectCandidateAt() uses candidate_window_. 325 // Note that SelectCandidateAt() uses candidate_window_.
763 candidate_window_.CopyFrom(new_candidate_window); 326 candidate_window_.CopyFrom(new_candidate_window);
764 327
765 // Select the current candidate in the page. 328 // Select the current candidate in the page.
766 if (candidate_window_.is_cursor_visible()) { 329 if (candidate_window_.is_cursor_visible()) {
767 if (candidate_window_.page_size()) { 330 if (candidate_window_.page_size()) {
768 const int current_candidate_in_page = 331 const int current_candidate_in_page =
769 candidate_window_.cursor_position() % candidate_window_.page_size(); 332 candidate_window_.cursor_position() % candidate_window_.page_size();
770 SelectCandidateAt(current_candidate_in_page); 333 SelectCandidateAt(current_candidate_in_page);
771 } 334 }
772 } else { 335 } else {
773 // Unselect the currently selected candidate. 336 // Unselect the currently selected candidate.
774 if (0 <= selected_candidate_index_in_page_ && 337 if (0 <= selected_candidate_index_in_page_ &&
775 static_cast<size_t>(selected_candidate_index_in_page_) < 338 static_cast<size_t>(selected_candidate_index_in_page_) <
776 candidate_views_.size()) { 339 candidate_views_.size()) {
777 candidate_views_[selected_candidate_index_in_page_]->Unselect(); 340 candidate_views_[selected_candidate_index_in_page_]->SetState(
341 views::Button::STATE_NORMAL);
778 selected_candidate_index_in_page_ = -1; 342 selected_candidate_index_in_page_ = -1;
779 } 343 }
780 } 344 }
781 } 345 }
782 346
347 void CandidateWindowView::SetCursorBounds(const gfx::Rect& cursor_bounds,
348 const gfx::Rect& composition_head) {
349 if (candidate_window_.show_window_at_composition())
350 SetAnchorRect(composition_head);
351 else
352 SetAnchorRect(cursor_bounds);
353 }
354
783 void CandidateWindowView::MaybeInitializeCandidateViews( 355 void CandidateWindowView::MaybeInitializeCandidateViews(
784 const CandidateWindow& candidate_window) { 356 const CandidateWindow& candidate_window) {
785 const CandidateWindow::Orientation orientation = 357 const CandidateWindow::Orientation orientation =
786 candidate_window.orientation(); 358 candidate_window.orientation();
787 const int page_size = candidate_window.page_size(); 359 const size_t page_size = candidate_window.page_size();
788 views::View* candidate_area_contents = candidate_area_->contents();
789 360
790 // Current column width. 361 // Reset all candidate_views_ when orientation changes.
791 gfx::Size shortcut_column_size(0, 0); 362 if (orientation != candidate_window_.orientation())
792 gfx::Size candidate_column_size(0,0); 363 STLDeleteElements(&candidate_views_);
793 gfx::Size annotation_column_size(0, 0);
794 364
795 // If orientation is horizontal, don't need to compute width, 365 while (page_size < candidate_views_.size()) {
796 // because each label is left aligned. 366 delete candidate_views_.back();
797 if (orientation == CandidateWindow::VERTICAL) { 367 candidate_views_.pop_back();
798 const ui::NativeTheme& theme = *GetNativeTheme();
799 shortcut_column_size = ComputeShortcutColumnSize(candidate_window, theme);
800 candidate_column_size = ComputeCandidateColumnSize(candidate_window);
801 annotation_column_size = ComputeAnnotationColumnSize(candidate_window,
802 theme);
803 } 368 }
804 369 while (page_size > candidate_views_.size()) {
805 // If the requested number of views matches the number of current views, and 370 CandidateView* new_candidate = new CandidateView(this, orientation);
806 // previous and current column width are same, just reuse these. 371 candidate_area_->AddChildView(new_candidate);
807 // 372 candidate_views_.push_back(new_candidate);
808 // Note that the early exit logic is not only useful for improving
809 // performance, but also necessary for the horizontal candidate window
810 // to be redrawn properly. If we get rid of the logic, the horizontal
811 // candidate window won't get redrawn properly for some reason when
812 // there is no size change. You can test this by removing "return" here
813 // and type "ni" with Pinyin input method.
814 if (static_cast<int>(candidate_views_.size()) == page_size &&
815 candidate_window_.orientation() == orientation &&
816 previous_shortcut_column_size_ == shortcut_column_size &&
817 previous_candidate_column_size_ == candidate_column_size &&
818 previous_annotation_column_size_ == annotation_column_size) {
819 return;
820 } 373 }
821
822 // Update the previous column widths.
823 previous_shortcut_column_size_ = shortcut_column_size;
824 previous_candidate_column_size_ = candidate_column_size;
825 previous_annotation_column_size_ = annotation_column_size;
826
827 // Clear the existing candidate_views if any.
828 for (size_t i = 0; i < candidate_views_.size(); ++i) {
829 candidate_area_contents->RemoveChildView(candidate_views_[i]);
830 // Delete the view after getting out the current message loop iteration.
831 base::MessageLoop::current()->DeleteSoon(FROM_HERE, candidate_views_[i]);
832 }
833 candidate_views_.clear();
834 selected_candidate_index_in_page_ = -1; // Invalidates the index.
835
836 views::GridLayout* layout = new views::GridLayout(candidate_area_contents);
837 // |candidate_area_contents| owns |layout|.
838 candidate_area_contents->SetLayoutManager(layout);
839 // Initialize the column set.
840 views::ColumnSet* column_set = layout->AddColumnSet(0);
841 if (orientation == CandidateWindow::VERTICAL) {
842 column_set->AddColumn(views::GridLayout::FILL,
843 views::GridLayout::FILL,
844 1, views::GridLayout::USE_PREF, 0, 0);
845 } else {
846 for (int i = 0; i < page_size; ++i) {
847 column_set->AddColumn(views::GridLayout::FILL,
848 views::GridLayout::FILL,
849 0, views::GridLayout::USE_PREF, 0, 0);
850 }
851 }
852
853 // Set insets so the border of the selected candidate is drawn inside of
854 // the border of the main candidate window, but we don't have the inset
855 // at the top and the bottom as we have the borders of the header and
856 // footer areas.
857 const gfx::Insets kCandidateAreaInsets(0, 1, 0, 1);
858 layout->SetInsets(kCandidateAreaInsets.top(),
859 kCandidateAreaInsets.left(),
860 kCandidateAreaInsets.bottom(),
861 kCandidateAreaInsets.right());
862
863 // Use maximum height for all rows in candidate area.
864 const int kColumnHeight = std::max(shortcut_column_size.height(),
865 std::max(candidate_column_size.height(),
866 annotation_column_size.height()));
867
868 // Add views to the candidate area.
869 if (orientation == CandidateWindow::HORIZONTAL) {
870 layout->StartRow(0, 0);
871 }
872
873 for (int i = 0; i < page_size; ++i) {
874 CandidateView* candidate_row = new CandidateView(this, i, orientation);
875 candidate_row->Init(shortcut_column_size.width(),
876 candidate_column_size.width(),
877 annotation_column_size.width(),
878 kColumnHeight);
879 candidate_views_.push_back(candidate_row);
880 if (orientation == CandidateWindow::VERTICAL) {
881 layout->StartRow(0, 0);
882 }
883 // |candidate_row| will be owned by |candidate_area_contents|.
884 layout->AddView(candidate_row,
885 1, // Column span.
886 1, // Row span.
887 // Horizontal alignment.
888 orientation == CandidateWindow::VERTICAL ?
889 views::GridLayout::FILL : views::GridLayout::CENTER,
890 views::GridLayout::CENTER, // Vertical alignment.
891 -1, // Preferred width, not specified.
892 kColumnHeight); // Preferred height.
893 }
894
895 // Compute views size in |layout|.
896 // If we don't call this function, GetHorizontalOffset() often
897 // returns invalid value (returns 0), then candidate window
898 // moves right from the correct position in ResizeAndMoveParentFrame().
899 // TODO(nhiroki): Figure out why it returns invalid value.
900 // It seems that the x-position of the candidate labels is not set.
901 layout->Layout(candidate_area_contents);
902 }
903
904 bool CandidateWindowView::IsCandidateWindowOpen() const {
905 return !should_show_at_composition_head_ &&
906 candidate_area_->visible() && candidate_area_->IsShown();
907 } 374 }
908 375
909 void CandidateWindowView::SelectCandidateAt(int index_in_page) { 376 void CandidateWindowView::SelectCandidateAt(int index_in_page) {
910 const int current_page_index = ComputePageIndex(candidate_window_); 377 const int current_page_index = ComputePageIndex(candidate_window_);
911 if (current_page_index < 0) { 378 if (current_page_index < 0) {
912 return; 379 return;
913 } 380 }
914 381
915 const int cursor_absolute_index = 382 const int cursor_absolute_index =
916 candidate_window_.page_size() * current_page_index + index_in_page; 383 candidate_window_.page_size() * current_page_index + index_in_page;
917 // Ignore click on out of range views. 384 // Ignore click on out of range views.
918 if (cursor_absolute_index < 0 || 385 if (cursor_absolute_index < 0 ||
919 candidate_window_.candidates().size() <= 386 candidate_window_.candidates().size() <=
920 static_cast<size_t>(cursor_absolute_index)) { 387 static_cast<size_t>(cursor_absolute_index)) {
921 return; 388 return;
922 } 389 }
923 390
924 // Unselect the currently selected candidate.
925 if (0 <= selected_candidate_index_in_page_ &&
926 static_cast<size_t>(selected_candidate_index_in_page_) <
927 candidate_views_.size()) {
928 candidate_views_[selected_candidate_index_in_page_]->Unselect();
929 }
930 // Remember the currently selected candidate index in the current page. 391 // Remember the currently selected candidate index in the current page.
931 selected_candidate_index_in_page_ = index_in_page; 392 selected_candidate_index_in_page_ = index_in_page;
932 393
933 // Select the candidate specified by index_in_page. 394 // Select the candidate specified by index_in_page.
934 candidate_views_[index_in_page]->Select(); 395 candidate_views_[index_in_page]->SetState(views::Button::STATE_PRESSED);
935 396
936 // Update the cursor indexes in the model. 397 // Update the cursor indexes in the model.
937 candidate_window_.set_cursor_position(cursor_absolute_index); 398 candidate_window_.set_cursor_position(cursor_absolute_index);
938 } 399 }
939 400
940 void CandidateWindowView::OnCandidatePressed( 401 void CandidateWindowView::ButtonPressed(views::Button* sender,
941 const gfx::Point& location) { 402 const ui::Event& event) {
942 for (size_t i = 0; i < candidate_views_.size(); ++i) { 403 for (size_t i = 0; i < candidate_views_.size(); ++i) {
943 gfx::Point converted_location = location; 404 if (sender == candidate_views_[i]) {
944 views::View::ConvertPointToTarget(this, candidate_views_[i], 405 FOR_EACH_OBSERVER(Observer, observers_, OnCandidateCommitted(i));
945 &converted_location); 406 return;
946 if (candidate_views_[i]->HitTestPoint(converted_location)) {
947 SelectCandidateAt(i);
948 break;
949 } 407 }
950 } 408 }
951 } 409 }
952 410
953 void CandidateWindowView::CommitCandidate() {
954 if (!(0 <= selected_candidate_index_in_page_ &&
955 static_cast<size_t>(selected_candidate_index_in_page_) <
956 candidate_views_.size())) {
957 return; // Out of range, do nothing.
958 }
959
960 FOR_EACH_OBSERVER(Observer, observers_,
961 OnCandidateCommitted(selected_candidate_index_in_page_));
962 }
963
964 void CandidateWindowView::ResizeAndMoveParentFrame() {
965 // If rendering operation comes from mozc-engine, uses mozc specific bounds,
966 // otherwise candidate window is shown under the cursor.
967 const int x = should_show_at_composition_head_?
968 composition_head_bounds_.x() : cursor_bounds_.x();
969 // To avoid candidate-window overlapping, uses maximum y-position of mozc
970 // specific bounds and cursor bounds, because mozc-engine does not
971 // consider about multi-line composition.
972 const int y = should_show_at_composition_head_?
973 std::max(composition_head_bounds_.y(), cursor_bounds_.y()) :
974 cursor_bounds_.y();
975 const int height = cursor_bounds_.height();
976 const int horizontal_offset = GetHorizontalOffset();
977
978 gfx::Rect old_bounds = parent_frame_->GetClientAreaBoundsInScreen();
979 gfx::Rect screen_bounds = ash::Shell::GetScreen()->GetDisplayMatching(
980 cursor_bounds_).work_area();
981 // The size.
982 gfx::Rect frame_bounds = old_bounds;
983 frame_bounds.set_size(GetPreferredSize());
984
985 // The default position.
986 frame_bounds.set_x(x + horizontal_offset);
987 frame_bounds.set_y(y + height);
988
989 // Handle overflow at the left and the top.
990 frame_bounds.set_x(std::max(frame_bounds.x(), screen_bounds.x()));
991 frame_bounds.set_y(std::max(frame_bounds.y(), screen_bounds.y()));
992
993 // Handle overflow at the right.
994 const int right_overflow = frame_bounds.right() - screen_bounds.right();
995 if (right_overflow > 0) {
996 frame_bounds.set_x(frame_bounds.x() - right_overflow);
997 }
998
999 // Handle overflow at the bottom.
1000 const int bottom_overflow = frame_bounds.bottom() - screen_bounds.bottom();
1001
1002 // To avoid flickering window position, the candidate window should be shown
1003 // on upper side of composition string if it was shown there.
1004 if (should_show_upper_side_ || bottom_overflow > 0) {
1005 frame_bounds.set_y(frame_bounds.y() - height - frame_bounds.height());
1006 should_show_upper_side_ = true;
1007 }
1008
1009 // TODO(nona): check top_overflow here.
1010
1011 // Move the window per the cursor bounds.
1012 // SetBounds() is not cheap. Only call this when it is really changed.
1013 if (frame_bounds != old_bounds)
1014 parent_frame_->SetBounds(frame_bounds);
1015 }
1016
1017 int CandidateWindowView::GetHorizontalOffset() {
1018 // Compute the horizontal offset if the candidate window is vertical.
1019 if (!candidate_views_.empty() &&
1020 candidate_window_.orientation() == CandidateWindow::VERTICAL) {
1021 return - candidate_views_[0]->GetCandidateLabelPosition().x();
1022 }
1023 return 0;
1024 }
1025
1026 void CandidateWindowView::VisibilityChanged(View* starting_from,
1027 bool is_visible) {
1028 if (is_visible) {
1029 // If the visibility of candidate window is changed,
1030 // we should move the frame to the right position.
1031 ResizeAndMoveParentFrame();
1032 }
1033 }
1034
1035 void CandidateWindowView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1036 // If the bounds(size) of candidate window is changed,
1037 // we should move the frame to the right position.
1038 View::OnBoundsChanged(previous_bounds);
1039 ResizeAndMoveParentFrame();
1040 }
1041
1042 } // namespace input_method 411 } // namespace input_method
1043 } // namespace chromeos 412 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698