OLD | NEW |
---|---|
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 |
OLD | NEW |