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 | 4 |
5 #include "ui/views/controls/combobox/combobox.h" | 5 #include "ui/views/controls/combobox/combobox.h" |
6 | 6 |
7 #include "base/logging.h" | 7 #include "base/logging.h" |
8 #include "base/strings/utf_string_conversions.h" | 8 #include "base/strings/utf_string_conversions.h" |
9 #include "grit/ui_resources.h" | 9 #include "grit/ui_resources.h" |
10 #include "ui/base/accessibility/accessible_view_state.h" | 10 #include "ui/base/accessibility/accessible_view_state.h" |
11 #include "ui/base/models/combobox_model.h" | 11 #include "ui/base/models/combobox_model.h" |
12 #include "ui/base/resource/resource_bundle.h" | 12 #include "ui/base/resource/resource_bundle.h" |
13 #include "ui/events/event.h" | 13 #include "ui/events/event.h" |
14 #include "ui/events/keycodes/keyboard_codes.h" | 14 #include "ui/events/keycodes/keyboard_codes.h" |
15 #include "ui/gfx/animation/throb_animation.h" | |
15 #include "ui/gfx/canvas.h" | 16 #include "ui/gfx/canvas.h" |
17 #include "ui/gfx/image/image.h" | |
18 #include "ui/gfx/scoped_canvas.h" | |
16 #include "ui/native_theme/native_theme.h" | 19 #include "ui/native_theme/native_theme.h" |
17 #include "ui/views/color_constants.h" | 20 #include "ui/views/color_constants.h" |
21 #include "ui/views/controls/button/custom_button.h" | |
22 #include "ui/views/controls/button/label_button.h" | |
18 #include "ui/views/controls/combobox/combobox_listener.h" | 23 #include "ui/views/controls/combobox/combobox_listener.h" |
19 #include "ui/views/controls/focusable_border.h" | 24 #include "ui/views/controls/focusable_border.h" |
20 #include "ui/views/controls/menu/menu_runner.h" | 25 #include "ui/views/controls/menu/menu_runner.h" |
21 #include "ui/views/controls/menu/submenu_view.h" | 26 #include "ui/views/controls/menu/submenu_view.h" |
22 #include "ui/views/controls/prefix_selector.h" | 27 #include "ui/views/controls/prefix_selector.h" |
23 #include "ui/views/ime/input_method.h" | 28 #include "ui/views/ime/input_method.h" |
24 #include "ui/views/mouse_constants.h" | 29 #include "ui/views/mouse_constants.h" |
30 #include "ui/views/painter.h" | |
25 #include "ui/views/widget/widget.h" | 31 #include "ui/views/widget/widget.h" |
26 | 32 |
27 namespace views { | 33 namespace views { |
28 | 34 |
29 namespace { | 35 namespace { |
30 | 36 |
31 // Menu border widths | 37 // Menu border widths |
32 const int kMenuBorderWidthLeft = 1; | 38 const int kMenuBorderWidthLeft = 1; |
33 const int kMenuBorderWidthTop = 1; | 39 const int kMenuBorderWidthTop = 1; |
34 const int kMenuBorderWidthRight = 1; | 40 const int kMenuBorderWidthRight = 1; |
35 | 41 |
36 // Limit how small a combobox can be. | 42 // Limit how small a combobox can be. |
37 const int kMinComboboxWidth = 25; | 43 const int kMinComboboxWidth = 25; |
38 | 44 |
39 // Size of the combobox arrow margins | 45 // Size of the combobox arrow margins |
40 const int kDisclosureArrowLeftPadding = 7; | 46 const int kDisclosureArrowLeftPadding = 7; |
41 const int kDisclosureArrowRightPadding = 7; | 47 const int kDisclosureArrowRightPadding = 7; |
48 const int kDisclosureArrowButtonLeftPadding = 11; | |
49 const int kDisclosureArrowButtonRightPadding = 12; | |
42 | 50 |
43 // Define the id of the first item in the menu (since it needs to be > 0) | 51 // Define the id of the first item in the menu (since it needs to be > 0) |
44 const int kFirstMenuItemId = 1000; | 52 const int kFirstMenuItemId = 1000; |
45 | 53 |
46 const SkColor kInvalidTextColor = SK_ColorWHITE; | 54 const SkColor kInvalidTextColor = SK_ColorWHITE; |
47 | 55 |
48 // Used to indicate that no item is currently selected by the user. | 56 // Used to indicate that no item is currently selected by the user. |
49 const int kNoSelection = -1; | 57 const int kNoSelection = -1; |
50 | 58 |
59 const int kBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON); | |
60 const int kHoveredBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_H); | |
61 const int kPressedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_P); | |
62 | |
63 #define MENU_IMAGE_GRID(x) { \ | |
64 x ## _MENU_TOP, x ## _MENU_CENTER, x ## _MENU_BOTTOM, } | |
65 | |
66 const int kMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON); | |
67 const int kHoveredMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_H); | |
68 const int kPressedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_P); | |
69 | |
70 #undef MENU_IMAGE_GRID | |
71 | |
51 // The background to use for invalid comboboxes. | 72 // The background to use for invalid comboboxes. |
52 class InvalidBackground : public Background { | 73 class InvalidBackground : public Background { |
53 public: | 74 public: |
54 InvalidBackground() {} | 75 InvalidBackground() {} |
55 virtual ~InvalidBackground() {} | 76 virtual ~InvalidBackground() {} |
56 | 77 |
57 // Overridden from Background: | 78 // Overridden from Background: |
58 virtual void Paint(gfx::Canvas* canvas, View* view) const OVERRIDE { | 79 virtual void Paint(gfx::Canvas* canvas, View* view) const OVERRIDE { |
59 gfx::Rect bounds(view->GetLocalBounds()); | 80 gfx::Rect bounds(view->GetLocalBounds()); |
60 // Inset by 2 to leave 1 empty pixel between background and border. | 81 // Inset by 2 to leave 1 empty pixel between background and border. |
61 bounds.Inset(2, 2, 2, 2); | 82 bounds.Inset(2, 2, 2, 2); |
62 canvas->FillRect(bounds, kWarningColor); | 83 canvas->FillRect(bounds, kWarningColor); |
63 } | 84 } |
64 | 85 |
65 private: | 86 private: |
66 DISALLOW_COPY_AND_ASSIGN(InvalidBackground); | 87 DISALLOW_COPY_AND_ASSIGN(InvalidBackground); |
67 }; | 88 }; |
68 | 89 |
90 // The transparent button which holds a button state but is not rendered. | |
91 class TransparentButton : public CustomButton { | |
92 public: | |
93 TransparentButton(ButtonListener* listener) | |
94 : CustomButton(listener) { | |
95 SetAnimationDuration(LabelButton::kHoverAnimationDurationMs); | |
96 } | |
97 virtual ~TransparentButton() {} | |
98 | |
99 // CustomButton: | |
100 virtual void StateChanged() OVERRIDE { | |
101 parent()->SchedulePaintInRect(bounds()); | |
102 } | |
103 | |
104 // gfx::AnimationDelegate: | |
105 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE { | |
106 parent()->SchedulePaintInRect(bounds()); | |
107 } | |
108 | |
109 double GetAnimationValue() const { | |
110 return hover_animation_->GetCurrentValue(); | |
111 } | |
112 | |
113 private: | |
114 DISALLOW_COPY_AND_ASSIGN(TransparentButton); | |
115 }; | |
116 | |
69 // Returns the next or previous valid index (depending on |increment|'s value). | 117 // Returns the next or previous valid index (depending on |increment|'s value). |
70 // Skips separator or disabled indices. Returns -1 if there is no valid adjacent | 118 // Skips separator or disabled indices. Returns -1 if there is no valid adjacent |
71 // index. | 119 // index. |
72 int GetAdjacentIndex(ui::ComboboxModel* model, int increment, int index) { | 120 int GetAdjacentIndex(ui::ComboboxModel* model, int increment, int index) { |
73 DCHECK(increment == -1 || increment == 1); | 121 DCHECK(increment == -1 || increment == 1); |
74 | 122 |
75 index += increment; | 123 index += increment; |
76 while (index >= 0 && index < model->GetItemCount()) { | 124 while (index >= 0 && index < model->GetItemCount()) { |
77 if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index)) | 125 if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index)) |
78 return index; | 126 return index; |
79 index += increment; | 127 index += increment; |
80 } | 128 } |
81 return kNoSelection; | 129 return kNoSelection; |
82 } | 130 } |
83 | 131 |
132 // Returns the image resource ids of an array for the body button. | |
133 // | |
134 // TODO(hajimehoshi): This function should return the images for the 'disabled' | |
135 // status. (crbug/270052) | |
136 // | |
137 // TODO(hajimehoshi): Currently, |focused| is ignored. This should return the | |
138 // images for the 'focused' status. (crbug/270052) | |
139 const int* GetBodyButtonImageIds(bool focused, | |
140 Button::ButtonState state, | |
141 size_t* num) { | |
142 DCHECK(num); | |
143 *num = 9; | |
144 switch (state) { | |
145 case Button::STATE_DISABLED: | |
146 return kBodyButtonImages; | |
147 case Button::STATE_NORMAL: | |
148 return kBodyButtonImages; | |
149 case Button::STATE_HOVERED: | |
150 return kHoveredBodyButtonImages; | |
151 case Button::STATE_PRESSED: | |
152 return kPressedBodyButtonImages; | |
153 default: | |
154 NOTREACHED(); | |
155 } | |
156 return NULL; | |
157 } | |
158 | |
159 // Returns the image resource ids of an array for the menu button. | |
160 const int* GetMenuButtonImageIds(bool focused, | |
161 Button::ButtonState state, | |
162 size_t* num) { | |
163 DCHECK(num); | |
164 *num = 3; | |
165 switch (state) { | |
166 case Button::STATE_DISABLED: | |
167 return kMenuButtonImages; | |
168 case Button::STATE_NORMAL: | |
169 return kMenuButtonImages; | |
170 case Button::STATE_HOVERED: | |
171 return kHoveredMenuButtonImages; | |
172 case Button::STATE_PRESSED: | |
173 return kPressedMenuButtonImages; | |
174 default: | |
175 NOTREACHED(); | |
176 } | |
177 return NULL; | |
178 } | |
179 | |
180 // Returns the images for the menu buttons. | |
181 std::vector<const gfx::ImageSkia*> GetMenuButtonImages( | |
182 bool focused, | |
183 Button::ButtonState state) { | |
184 const int* ids; | |
185 size_t num_ids; | |
186 ids = GetMenuButtonImageIds(focused, state, &num_ids); | |
187 std::vector<const gfx::ImageSkia*> images; | |
188 images.reserve(num_ids); | |
189 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
190 for (size_t i = 0; i < num_ids; i++) | |
191 images.push_back(rb.GetImageSkiaNamed(ids[i])); | |
192 return images; | |
193 } | |
194 | |
195 // Paints three images in a column at the given location. The center image is | |
196 // stretched so as to fit the given height. | |
197 void PaintImagesVertically(gfx::Canvas* canvas, | |
198 const gfx::ImageSkia& top_image, | |
199 const gfx::ImageSkia& center_image, | |
200 const gfx::ImageSkia& bottom_image, | |
201 int x, int y, int width, int height) { | |
202 canvas->DrawImageInt(top_image, | |
203 0, 0, top_image.width(), top_image.height(), | |
204 x, y, width, top_image.height(), false); | |
205 y += top_image.height(); | |
206 int center_height = height - top_image.height() - bottom_image.height(); | |
207 canvas->DrawImageInt(center_image, | |
208 0, 0, center_image.width(), center_image.height(), | |
209 x, y, width, center_height, false); | |
210 y += center_height; | |
211 canvas->DrawImageInt(bottom_image, | |
212 0, 0, bottom_image.width(), bottom_image.height(), | |
213 x, y, width, bottom_image.height(), false); | |
214 } | |
215 | |
216 // Paints the arrow button. | |
217 void PaintArrowButton( | |
218 gfx::Canvas* canvas, | |
219 const std::vector<const gfx::ImageSkia*>& arrow_button_images, | |
220 int x, int height) { | |
221 PaintImagesVertically(canvas, | |
222 *arrow_button_images[0], | |
223 *arrow_button_images[1], | |
224 *arrow_button_images[2], | |
225 x, 0, arrow_button_images[0]->width(), height); | |
226 } | |
227 | |
84 } // namespace | 228 } // namespace |
85 | 229 |
86 // static | 230 // static |
87 const char Combobox::kViewClassName[] = "views/Combobox"; | 231 const char Combobox::kViewClassName[] = "views/Combobox"; |
88 | 232 |
89 //////////////////////////////////////////////////////////////////////////////// | 233 //////////////////////////////////////////////////////////////////////////////// |
90 // Combobox, public: | 234 // Combobox, public: |
91 | 235 |
92 Combobox::Combobox(ui::ComboboxModel* model) | 236 Combobox::Combobox(ui::ComboboxModel* model) |
93 : model_(model), | 237 : model_(model), |
238 style_(STYLE_SHOW_DROP_DOWN_ON_CLICK), | |
94 listener_(NULL), | 239 listener_(NULL), |
95 selected_index_(model_->GetDefaultIndex()), | 240 selected_index_(model_->GetDefaultIndex()), |
96 invalid_(false), | 241 invalid_(false), |
97 text_border_(new FocusableBorder()), | 242 text_border_(new FocusableBorder()), |
98 disclosure_arrow_(ui::ResourceBundle::GetSharedInstance().GetImageNamed( | 243 disclosure_arrow_(ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
99 IDR_MENU_DROPARROW).ToImageSkia()), | 244 IDR_MENU_DROPARROW).ToImageSkia()), |
100 dropdown_open_(false) { | 245 dropdown_open_(false), |
246 text_button_(new TransparentButton(this)), | |
247 arrow_button_(new TransparentButton(this)) { | |
101 model_->AddObserver(this); | 248 model_->AddObserver(this); |
102 UpdateFromModel(); | 249 UpdateFromModel(); |
103 set_focusable(true); | 250 set_focusable(true); |
104 // |text_border_| draws focus. | 251 // |text_border_| draws focus. |
105 set_focus_border(NULL); | 252 set_focus_border(NULL); |
106 set_border(text_border_); | 253 set_border(text_border_); |
254 | |
255 // Initialize the button images. | |
256 Button::ButtonState button_states[] = { | |
257 Button::STATE_DISABLED, | |
258 Button::STATE_NORMAL, | |
259 Button::STATE_HOVERED, | |
260 Button::STATE_PRESSED, | |
261 }; | |
262 for (int focused = 0; focused < 2; focused++) { | |
263 for (size_t state_index = 0; state_index < arraysize(button_states); | |
264 state_index++) { | |
265 Button::ButtonState state = button_states[state_index]; | |
266 size_t num; | |
267 const int* ids = GetBodyButtonImageIds(focused, state, &num); | |
268 body_button_painters_[focused][state].reset( | |
269 Painter::CreateImageGridPainter(ids)); | |
270 menu_button_images_[focused][state] = GetMenuButtonImages(focused, state); | |
271 } | |
272 } | |
273 | |
274 text_button_->SetVisible(true); | |
275 arrow_button_->SetVisible(true); | |
276 text_button_->set_focusable(false); | |
277 arrow_button_->set_focusable(false); | |
278 AddChildView(text_button_); | |
279 AddChildView(arrow_button_); | |
107 } | 280 } |
108 | 281 |
109 Combobox::~Combobox() { | 282 Combobox::~Combobox() { |
110 model_->RemoveObserver(this); | 283 model_->RemoveObserver(this); |
111 } | 284 } |
112 | 285 |
113 // static | 286 // static |
114 const gfx::Font& Combobox::GetFont() { | 287 const gfx::Font& Combobox::GetFont() { |
115 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | 288 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
116 return rb.GetFont(ui::ResourceBundle::BaseFont); | 289 return rb.GetFont(ui::ResourceBundle::BaseFont); |
117 } | 290 } |
118 | 291 |
292 void Combobox::SetStyle(Style style) { | |
293 if (style_ == style) | |
294 return; | |
295 | |
296 style_ = style; | |
297 switch (style) { | |
298 case STYLE_SHOW_DROP_DOWN_ON_CLICK: { | |
299 // Recreate a FocusableBorder to reset the insets of the border. | |
300 // set_border with a new border will delete the existing border. | |
301 text_border_ = new FocusableBorder(); | |
302 set_border(text_border_); | |
303 break; | |
304 } | |
305 case STYLE_NOTIFY_ON_CLICK: { | |
306 text_border_->SetInsets(8, 13, 8, 13); | |
sky
2013/12/05 16:46:32
What I'm saying is you should get rid of text_bord
hajimehoshi
2013/12/06 12:27:04
Done. (Added UpdateBorder)
| |
307 break; | |
308 } | |
309 } | |
310 | |
311 PreferredSizeChanged(); | |
312 } | |
313 | |
119 void Combobox::ModelChanged() { | 314 void Combobox::ModelChanged() { |
120 selected_index_ = std::min(0, model_->GetItemCount()); | 315 selected_index_ = std::min(0, model_->GetItemCount()); |
121 UpdateFromModel(); | 316 UpdateFromModel(); |
122 PreferredSizeChanged(); | 317 PreferredSizeChanged(); |
123 } | 318 } |
124 | 319 |
125 void Combobox::SetSelectedIndex(int index) { | 320 void Combobox::SetSelectedIndex(int index) { |
126 selected_index_ = index; | 321 selected_index_ = index; |
127 SchedulePaint(); | 322 SchedulePaint(); |
128 } | 323 } |
(...skipping 26 matching lines...) Expand all Loading... | |
155 } | 350 } |
156 SchedulePaint(); | 351 SchedulePaint(); |
157 } | 352 } |
158 | 353 |
159 ui::TextInputClient* Combobox::GetTextInputClient() { | 354 ui::TextInputClient* Combobox::GetTextInputClient() { |
160 if (!selector_) | 355 if (!selector_) |
161 selector_.reset(new PrefixSelector(this)); | 356 selector_.reset(new PrefixSelector(this)); |
162 return selector_.get(); | 357 return selector_.get(); |
163 } | 358 } |
164 | 359 |
360 void Combobox::Layout() { | |
361 PrefixDelegate::Layout(); | |
362 | |
363 gfx::Insets insets = GetInsets(); | |
364 int height = content_size_.height() + insets.height(); | |
sky
2013/12/05 16:46:32
Shouldn't you use height() here rather than calcul
hajimehoshi
2013/12/06 12:27:04
height() was enough. Done.
| |
365 int text_button_width = 0; | |
366 int arrow_button_width = 0; | |
367 | |
368 switch (style_) { | |
369 case STYLE_SHOW_DROP_DOWN_ON_CLICK: { | |
370 arrow_button_width = width(); | |
371 break; | |
372 } | |
373 case STYLE_NOTIFY_ON_CLICK: { | |
374 arrow_button_width = GetDisclosureArrowLeftPadding() + | |
375 disclosure_arrow_->width() + GetDisclosureArrowRightPadding(); | |
376 text_button_width = width() - arrow_button_width; | |
377 break; | |
378 } | |
379 } | |
380 | |
381 int arrow_button_x = text_button_width; | |
382 text_button_->SetBounds(0, 0, text_button_width, height); | |
sky
2013/12/05 16:46:32
std::max(0, text_button_width)
hajimehoshi
2013/12/06 12:27:04
Done.
| |
383 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height); | |
384 } | |
165 | 385 |
166 bool Combobox::IsItemChecked(int id) const { | 386 bool Combobox::IsItemChecked(int id) const { |
167 return false; | 387 return false; |
168 } | 388 } |
169 | 389 |
170 bool Combobox::IsCommandEnabled(int id) const { | 390 bool Combobox::IsCommandEnabled(int id) const { |
171 return model()->IsItemEnabledAt(MenuCommandToIndex(id)); | 391 return model()->IsItemEnabledAt(MenuCommandToIndex(id)); |
172 } | 392 } |
173 | 393 |
174 void Combobox::ExecuteCommand(int id) { | 394 void Combobox::ExecuteCommand(int id) { |
(...skipping 25 matching lines...) Expand all Loading... | |
200 // Combobox, View overrides: | 420 // Combobox, View overrides: |
201 | 421 |
202 gfx::Size Combobox::GetPreferredSize() { | 422 gfx::Size Combobox::GetPreferredSize() { |
203 if (content_size_.IsEmpty()) | 423 if (content_size_.IsEmpty()) |
204 UpdateFromModel(); | 424 UpdateFromModel(); |
205 | 425 |
206 // The preferred size will drive the local bounds which in turn is used to set | 426 // The preferred size will drive the local bounds which in turn is used to set |
207 // the minimum width for the dropdown list. | 427 // the minimum width for the dropdown list. |
208 gfx::Insets insets = GetInsets(); | 428 gfx::Insets insets = GetInsets(); |
209 int total_width = std::max(kMinComboboxWidth, content_size_.width()) + | 429 int total_width = std::max(kMinComboboxWidth, content_size_.width()) + |
210 insets.width() + kDisclosureArrowLeftPadding + | 430 insets.width() + GetDisclosureArrowLeftPadding() + |
211 disclosure_arrow_->width() + kDisclosureArrowRightPadding; | 431 disclosure_arrow_->width() + GetDisclosureArrowRightPadding(); |
212 | |
213 return gfx::Size(total_width, content_size_.height() + insets.height()); | 432 return gfx::Size(total_width, content_size_.height() + insets.height()); |
214 } | 433 } |
215 | 434 |
216 const char* Combobox::GetClassName() const { | 435 const char* Combobox::GetClassName() const { |
217 return kViewClassName; | 436 return kViewClassName; |
218 } | 437 } |
219 | 438 |
220 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) { | 439 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) { |
221 // Escape should close the drop down list when it is active, not host UI. | 440 // Escape should close the drop down list when it is active, not host UI. |
222 if (e.key_code() != ui::VKEY_ESCAPE || | 441 if (e.key_code() != ui::VKEY_ESCAPE || |
223 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) { | 442 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) { |
224 return false; | 443 return false; |
225 } | 444 } |
226 return dropdown_open_; | 445 return dropdown_open_; |
227 } | 446 } |
228 | 447 |
229 bool Combobox::OnMousePressed(const ui::MouseEvent& mouse_event) { | |
230 RequestFocus(); | |
231 const base::TimeDelta delta = base::Time::Now() - closed_time_; | |
232 if (mouse_event.IsLeftMouseButton() && | |
233 (delta.InMilliseconds() > kMinimumMsBetweenButtonClicks)) { | |
234 UpdateFromModel(); | |
235 ShowDropDownMenu(ui::MENU_SOURCE_MOUSE); | |
236 } | |
237 | |
238 return true; | |
239 } | |
240 | |
241 bool Combobox::OnMouseDragged(const ui::MouseEvent& mouse_event) { | |
242 return true; | |
243 } | |
244 | |
245 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) { | 448 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) { |
246 // TODO(oshima): handle IME. | 449 // TODO(oshima): handle IME. |
247 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED); | 450 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED); |
248 | 451 |
249 DCHECK_GE(selected_index_, 0); | 452 DCHECK_GE(selected_index_, 0); |
250 DCHECK_LT(selected_index_, model()->GetItemCount()); | 453 DCHECK_LT(selected_index_, model()->GetItemCount()); |
251 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount()) | 454 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount()) |
252 selected_index_ = 0; | 455 selected_index_ = 0; |
253 | 456 |
254 bool show_menu = false; | 457 bool show_menu = false; |
(...skipping 24 matching lines...) Expand all Loading... | |
279 case ui::VKEY_HOME: | 482 case ui::VKEY_HOME: |
280 case ui::VKEY_PRIOR: // Page up. | 483 case ui::VKEY_PRIOR: // Page up. |
281 new_index = GetAdjacentIndex(model(), 1, -1); | 484 new_index = GetAdjacentIndex(model(), 1, -1); |
282 break; | 485 break; |
283 | 486 |
284 // Move to the previous item if any. | 487 // Move to the previous item if any. |
285 case ui::VKEY_UP: | 488 case ui::VKEY_UP: |
286 new_index = GetAdjacentIndex(model(), -1, selected_index_); | 489 new_index = GetAdjacentIndex(model(), -1, selected_index_); |
287 break; | 490 break; |
288 | 491 |
492 // Click the button only when the button style mode. | |
493 case ui::VKEY_SPACE: | |
494 if (style_ == STYLE_NOTIFY_ON_CLICK) { | |
495 // When pressing space, the click event will be raised after the key is | |
496 // released. | |
497 text_button_->SetState(Button::STATE_PRESSED); | |
498 } | |
499 break; | |
500 | |
501 // Click the button only when the button style mode. | |
502 case ui::VKEY_RETURN: | |
503 HandleClickEvent(); | |
504 break; | |
505 | |
289 default: | 506 default: |
290 return false; | 507 return false; |
291 } | 508 } |
292 | 509 |
293 if (show_menu) { | 510 if (show_menu) { |
294 UpdateFromModel(); | 511 UpdateFromModel(); |
295 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); | 512 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); |
296 } else if (new_index != selected_index_ && new_index != kNoSelection) { | 513 } else if (new_index != selected_index_ && new_index != kNoSelection) { |
297 DCHECK(!model()->IsItemSeparatorAt(new_index)); | 514 DCHECK(!model()->IsItemSeparatorAt(new_index)); |
298 selected_index_ = new_index; | 515 selected_index_ = new_index; |
299 OnSelectionChanged(); | 516 OnSelectionChanged(); |
300 } | 517 } |
301 | 518 |
302 return true; | 519 return true; |
303 } | 520 } |
304 | 521 |
305 bool Combobox::OnKeyReleased(const ui::KeyEvent& e) { | 522 bool Combobox::OnKeyReleased(const ui::KeyEvent& e) { |
306 return false; // crbug.com/127520 | 523 if (style_ != STYLE_NOTIFY_ON_CLICK) |
307 } | 524 return false; // crbug.com/127520 |
308 | 525 |
309 void Combobox::OnGestureEvent(ui::GestureEvent* gesture) { | 526 if (e.key_code() == ui::VKEY_SPACE) |
310 if (gesture->type() == ui::ET_GESTURE_TAP) { | 527 HandleClickEvent(); |
311 UpdateFromModel(); | 528 |
312 ShowDropDownMenu(ui::MENU_SOURCE_TOUCH); | 529 return false; |
313 gesture->StopPropagation(); | |
314 return; | |
315 } | |
316 View::OnGestureEvent(gesture); | |
317 } | 530 } |
318 | 531 |
319 void Combobox::OnPaint(gfx::Canvas* canvas) { | 532 void Combobox::OnPaint(gfx::Canvas* canvas) { |
320 OnPaintBackground(canvas); | 533 switch (style_) { |
321 PaintText(canvas); | 534 case STYLE_SHOW_DROP_DOWN_ON_CLICK: { |
322 OnPaintBorder(canvas); | 535 OnPaintBackground(canvas); |
536 PaintText(canvas); | |
537 OnPaintBorder(canvas); | |
538 break; | |
539 } | |
540 case STYLE_NOTIFY_ON_CLICK: { | |
541 PaintButtons(canvas); | |
542 PaintText(canvas); | |
543 break; | |
544 } | |
545 } | |
323 } | 546 } |
324 | 547 |
325 void Combobox::OnFocus() { | 548 void Combobox::OnFocus() { |
326 GetInputMethod()->OnFocus(); | 549 GetInputMethod()->OnFocus(); |
327 View::OnFocus(); | 550 View::OnFocus(); |
328 // Border renders differently when focused. | 551 // Border renders differently when focused. |
329 SchedulePaint(); | 552 SchedulePaint(); |
330 } | 553 } |
331 | 554 |
332 void Combobox::OnBlur() { | 555 void Combobox::OnBlur() { |
333 GetInputMethod()->OnBlur(); | 556 GetInputMethod()->OnBlur(); |
334 if (selector_) | 557 if (selector_) |
335 selector_->OnViewBlur(); | 558 selector_->OnViewBlur(); |
336 // Border renders differently when focused. | 559 // Border renders differently when focused. |
337 SchedulePaint(); | 560 SchedulePaint(); |
338 } | 561 } |
339 | 562 |
340 void Combobox::GetAccessibleState(ui::AccessibleViewState* state) { | 563 void Combobox::GetAccessibleState(ui::AccessibleViewState* state) { |
341 state->role = ui::AccessibilityTypes::ROLE_COMBOBOX; | 564 state->role = ui::AccessibilityTypes::ROLE_COMBOBOX; |
342 state->name = accessible_name_; | 565 state->name = accessible_name_; |
343 state->value = model_->GetItemAt(selected_index_); | 566 state->value = model_->GetItemAt(selected_index_); |
344 state->index = selected_index_; | 567 state->index = selected_index_; |
345 state->count = model_->GetItemCount(); | 568 state->count = model_->GetItemCount(); |
346 } | 569 } |
347 | 570 |
348 void Combobox::OnModelChanged() { | 571 void Combobox::OnModelChanged() { |
349 ModelChanged(); | 572 ModelChanged(); |
350 } | 573 } |
351 | 574 |
575 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) { | |
576 RequestFocus(); | |
577 | |
578 if (sender == text_button_) { | |
579 HandleClickEvent(); | |
580 } else { | |
581 DCHECK_EQ(arrow_button_, sender); | |
582 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when | |
583 // cliking this while the dropdown menu is opened. | |
584 const base::TimeDelta delta = base::Time::Now() - closed_time_; | |
585 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks) | |
586 return; | |
587 | |
588 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE; | |
589 if (event.IsKeyEvent()) | |
590 source_type = ui::MENU_SOURCE_KEYBOARD; | |
591 else if (event.IsGestureEvent() || event.IsTouchEvent()) | |
592 source_type = ui::MENU_SOURCE_TOUCH; | |
593 ShowDropDownMenu(source_type); | |
594 } | |
595 } | |
596 | |
352 void Combobox::UpdateFromModel() { | 597 void Combobox::UpdateFromModel() { |
353 int max_width = 0; | 598 int max_width = 0; |
354 const gfx::Font& font = Combobox::GetFont(); | 599 const gfx::Font& font = Combobox::GetFont(); |
355 | 600 |
356 MenuItemView* menu = new MenuItemView(this); | 601 MenuItemView* menu = new MenuItemView(this); |
357 // MenuRunner owns |menu|. | 602 // MenuRunner owns |menu|. |
358 dropdown_list_menu_runner_.reset(new MenuRunner(menu)); | 603 dropdown_list_menu_runner_.reset(new MenuRunner(menu)); |
359 | 604 |
360 int num_items = model()->GetItemCount(); | 605 int num_items = model()->GetItemCount(); |
361 for (int i = 0; i < num_items; ++i) { | 606 for (int i = 0; i < num_items; ++i) { |
(...skipping 15 matching lines...) Expand all Loading... | |
377 content_size_.SetSize(max_width, font.GetHeight()); | 622 content_size_.SetSize(max_width, font.GetHeight()); |
378 } | 623 } |
379 | 624 |
380 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const { | 625 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const { |
381 rect->set_x(GetMirroredXForRect(*rect)); | 626 rect->set_x(GetMirroredXForRect(*rect)); |
382 } | 627 } |
383 | 628 |
384 void Combobox::PaintText(gfx::Canvas* canvas) { | 629 void Combobox::PaintText(gfx::Canvas* canvas) { |
385 gfx::Insets insets = GetInsets(); | 630 gfx::Insets insets = GetInsets(); |
386 | 631 |
387 canvas->Save(); | 632 gfx::ScopedCanvas scoped_canvas(canvas); |
388 canvas->ClipRect(GetContentsBounds()); | 633 canvas->ClipRect(GetContentsBounds()); |
389 | 634 |
390 int x = insets.left(); | 635 int x = insets.left(); |
391 int y = insets.top(); | 636 int y = insets.top(); |
392 int text_height = height() - insets.height(); | 637 int text_height = height() - insets.height(); |
393 SkColor text_color = invalid() ? kInvalidTextColor : | 638 SkColor text_color = invalid() ? kInvalidTextColor : |
394 GetNativeTheme()->GetSystemColor( | 639 GetNativeTheme()->GetSystemColor( |
395 ui::NativeTheme::kColorId_LabelEnabledColor); | 640 ui::NativeTheme::kColorId_LabelEnabledColor); |
396 | 641 |
397 DCHECK_GE(selected_index_, 0); | 642 DCHECK_GE(selected_index_, 0); |
398 DCHECK_LT(selected_index_, model()->GetItemCount()); | 643 DCHECK_LT(selected_index_, model()->GetItemCount()); |
399 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount()) | 644 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount()) |
400 selected_index_ = 0; | 645 selected_index_ = 0; |
401 string16 text = model()->GetItemAt(selected_index_); | 646 string16 text = model()->GetItemAt(selected_index_); |
402 | 647 |
403 int disclosure_arrow_offset = width() - disclosure_arrow_->width() | 648 int disclosure_arrow_offset = width() - disclosure_arrow_->width() - |
404 - kDisclosureArrowLeftPadding - kDisclosureArrowRightPadding; | 649 GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding(); |
405 | 650 |
406 const gfx::Font& font = Combobox::GetFont(); | 651 const gfx::Font& font = Combobox::GetFont(); |
407 int text_width = font.GetStringWidth(text); | 652 int text_width = font.GetStringWidth(text); |
408 if ((text_width + insets.width()) > disclosure_arrow_offset) | 653 if ((text_width + insets.width()) > disclosure_arrow_offset) |
409 text_width = disclosure_arrow_offset - insets.width(); | 654 text_width = disclosure_arrow_offset - insets.width(); |
410 | 655 |
411 gfx::Rect text_bounds(x, y, text_width, text_height); | 656 gfx::Rect text_bounds(x, y, text_width, text_height); |
412 AdjustBoundsForRTLUI(&text_bounds); | 657 AdjustBoundsForRTLUI(&text_bounds); |
413 canvas->DrawStringInt(text, font, text_color, text_bounds); | 658 canvas->DrawStringInt(text, font, text_color, text_bounds); |
414 | 659 |
415 gfx::Rect arrow_bounds(disclosure_arrow_offset + kDisclosureArrowLeftPadding, | 660 int arrow_x = disclosure_arrow_offset + GetDisclosureArrowLeftPadding(); |
661 gfx::Rect arrow_bounds(arrow_x, | |
416 height() / 2 - disclosure_arrow_->height() / 2, | 662 height() / 2 - disclosure_arrow_->height() / 2, |
417 disclosure_arrow_->width(), | 663 disclosure_arrow_->width(), |
418 disclosure_arrow_->height()); | 664 disclosure_arrow_->height()); |
419 AdjustBoundsForRTLUI(&arrow_bounds); | 665 AdjustBoundsForRTLUI(&arrow_bounds); |
420 | 666 |
421 SkPaint paint; | 667 SkPaint paint; |
422 // This makes the arrow subtractive. | 668 // This makes the arrow subtractive. |
423 if (invalid()) | 669 if (invalid()) |
424 paint.setXfermodeMode(SkXfermode::kDstOut_Mode); | 670 paint.setXfermodeMode(SkXfermode::kDstOut_Mode); |
425 canvas->DrawImageInt(*disclosure_arrow_, arrow_bounds.x(), arrow_bounds.y(), | 671 canvas->DrawImageInt(*disclosure_arrow_, arrow_bounds.x(), arrow_bounds.y(), |
426 paint); | 672 paint); |
673 } | |
427 | 674 |
428 canvas->Restore(); | 675 void Combobox::PaintButtons(gfx::Canvas* canvas) { |
676 DCHECK(style_ == STYLE_NOTIFY_ON_CLICK); | |
677 | |
678 gfx::ScopedCanvas scoped_canvas(canvas); | |
679 if (base::i18n::IsRTL()) { | |
680 canvas->Translate(gfx::Vector2d(width(), 0)); | |
681 canvas->Scale(-1, 1); | |
682 } | |
683 | |
684 bool focused = HasFocus(); | |
685 const std::vector<const gfx::ImageSkia*>& arrow_button_images = | |
686 menu_button_images_[focused][ | |
687 arrow_button_->state() == Button::STATE_HOVERED ? | |
688 Button::STATE_NORMAL : arrow_button_->state()]; | |
689 | |
690 int text_button_width = width() - arrow_button_images[0]->width(); | |
sky
2013/12/05 16:46:32
Is there a reason you need this code here rather t
hajimehoshi
2013/12/06 12:27:04
Oh, I just didn't realize this... Thanks. I remove
| |
691 int text_button_hover_alpha = | |
692 text_button_->state() == Button::STATE_PRESSED ? 0 : | |
693 static_cast<int>(static_cast<TransparentButton*>(text_button_)-> | |
694 GetAnimationValue() * 255); | |
695 if (text_button_hover_alpha < 255) { | |
696 canvas->SaveLayerAlpha(255 - text_button_hover_alpha); | |
697 Painter* text_button_painter = | |
698 body_button_painters_[focused][ | |
699 text_button_->state() == Button::STATE_HOVERED ? | |
700 Button::STATE_NORMAL : text_button_->state()].get(); | |
701 Painter::PaintPainterAt(canvas, text_button_painter, | |
702 gfx::Rect(0, 0, text_button_width, height())); | |
703 canvas->Restore(); | |
704 } | |
705 if (0 < text_button_hover_alpha) { | |
706 canvas->SaveLayerAlpha(text_button_hover_alpha); | |
707 Painter* text_button_hovered_painter = | |
708 body_button_painters_[focused][Button::STATE_HOVERED].get(); | |
709 Painter::PaintPainterAt(canvas, text_button_hovered_painter, | |
710 gfx::Rect(0, 0, text_button_width, height())); | |
711 canvas->Restore(); | |
712 } | |
713 | |
714 int arrow_button_x = text_button_width; | |
715 int arrow_button_hover_alpha = | |
716 arrow_button_->state() == Button::STATE_PRESSED ? 0 : | |
717 static_cast<int>(static_cast<TransparentButton*>(arrow_button_)-> | |
718 GetAnimationValue() * 255); | |
719 if (arrow_button_hover_alpha < 255) { | |
720 canvas->SaveLayerAlpha(255 - arrow_button_hover_alpha); | |
721 PaintArrowButton(canvas, arrow_button_images, arrow_button_x, height()); | |
722 canvas->Restore(); | |
723 } | |
724 if (0 < arrow_button_hover_alpha) { | |
725 canvas->SaveLayerAlpha(arrow_button_hover_alpha); | |
726 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images = | |
727 menu_button_images_[focused][Button::STATE_HOVERED]; | |
728 PaintArrowButton(canvas, arrow_button_hovered_images, | |
729 arrow_button_x, height()); | |
730 canvas->Restore(); | |
731 } | |
429 } | 732 } |
430 | 733 |
431 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) { | 734 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) { |
432 if (!dropdown_list_menu_runner_.get()) | 735 if (!dropdown_list_menu_runner_.get()) |
433 UpdateFromModel(); | 736 UpdateFromModel(); |
434 | 737 |
435 // Extend the menu to the width of the combobox. | 738 // Extend the menu to the width of the combobox. |
436 MenuItemView* menu = dropdown_list_menu_runner_->GetMenu(); | 739 MenuItemView* menu = dropdown_list_menu_runner_->GetMenu(); |
437 SubmenuView* submenu = menu->CreateSubmenu(); | 740 SubmenuView* submenu = menu->CreateSubmenu(); |
438 submenu->set_minimum_preferred_width(size().width() - | 741 submenu->set_minimum_preferred_width( |
439 (kMenuBorderWidthLeft + kMenuBorderWidthRight)); | 742 size().width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight)); |
440 | 743 |
441 gfx::Rect lb = GetLocalBounds(); | 744 gfx::Rect lb = GetLocalBounds(); |
442 gfx::Point menu_position(lb.origin()); | 745 gfx::Point menu_position(lb.origin()); |
443 | 746 |
444 // Inset the menu's requested position so the border of the menu lines up | 747 // Inset the menu's requested position so the border of the menu lines up |
445 // with the border of the combobox. | 748 // with the border of the combobox. |
446 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft); | 749 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft); |
447 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop); | 750 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop); |
448 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight)); | 751 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight)); |
449 | 752 |
450 View::ConvertPointToScreen(this, &menu_position); | 753 View::ConvertPointToScreen(this, &menu_position); |
451 if (menu_position.x() < 0) | 754 if (menu_position.x() < 0) |
452 menu_position.set_x(0); | 755 menu_position.set_x(0); |
453 | 756 |
454 gfx::Rect bounds(menu_position, lb.size()); | 757 gfx::Rect bounds(menu_position, lb.size()); |
455 | 758 |
759 Button::ButtonState original_state; | |
760 if (arrow_button_) { | |
761 original_state = arrow_button_->state(); | |
762 arrow_button_->SetState(Button::STATE_PRESSED); | |
763 } | |
456 dropdown_open_ = true; | 764 dropdown_open_ = true; |
457 if (dropdown_list_menu_runner_->RunMenuAt(GetWidget(), NULL, bounds, | 765 if (dropdown_list_menu_runner_->RunMenuAt(GetWidget(), NULL, bounds, |
458 MenuItemView::TOPLEFT, source_type, MenuRunner::COMBOBOX) == | 766 MenuItemView::TOPLEFT, source_type, MenuRunner::COMBOBOX) == |
459 MenuRunner::MENU_DELETED) | 767 MenuRunner::MENU_DELETED) { |
460 return; | 768 return; |
769 } | |
461 dropdown_open_ = false; | 770 dropdown_open_ = false; |
771 if (arrow_button_) | |
772 arrow_button_->SetState(original_state); | |
462 closed_time_ = base::Time::Now(); | 773 closed_time_ = base::Time::Now(); |
463 | 774 |
464 // Need to explicitly clear mouse handler so that events get sent | 775 // Need to explicitly clear mouse handler so that events get sent |
465 // properly after the menu finishes running. If we don't do this, then | 776 // properly after the menu finishes running. If we don't do this, then |
466 // the first click to other parts of the UI is eaten. | 777 // the first click to other parts of the UI is eaten. |
467 SetMouseHandler(NULL); | 778 SetMouseHandler(NULL); |
468 } | 779 } |
469 | 780 |
470 void Combobox::OnSelectionChanged() { | 781 void Combobox::OnSelectionChanged() { |
471 if (listener_) | 782 if (listener_) |
472 listener_->OnSelectedIndexChanged(this); | 783 listener_->OnSelectedIndexChanged(this); |
473 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_VALUE_CHANGED, false); | 784 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_VALUE_CHANGED, false); |
474 SchedulePaint(); | 785 SchedulePaint(); |
475 } | 786 } |
476 | 787 |
477 int Combobox::MenuCommandToIndex(int menu_command_id) const { | 788 int Combobox::MenuCommandToIndex(int menu_command_id) const { |
478 // (note that the id received is offset by kFirstMenuItemId) | 789 // (note that the id received is offset by kFirstMenuItemId) |
479 // Revert menu ID offset to map back to combobox model. | 790 // Revert menu ID offset to map back to combobox model. |
480 int index = menu_command_id - kFirstMenuItemId; | 791 int index = menu_command_id - kFirstMenuItemId; |
481 DCHECK_LT(index, model()->GetItemCount()); | 792 DCHECK_LT(index, model()->GetItemCount()); |
482 return index; | 793 return index; |
483 } | 794 } |
484 | 795 |
796 int Combobox::GetDisclosureArrowLeftPadding() const { | |
797 switch (style_) { | |
798 case STYLE_SHOW_DROP_DOWN_ON_CLICK: | |
799 return kDisclosureArrowLeftPadding; | |
800 case STYLE_NOTIFY_ON_CLICK: | |
801 return kDisclosureArrowButtonLeftPadding; | |
802 } | |
803 NOTREACHED(); | |
804 return 0; | |
805 } | |
806 | |
807 int Combobox::GetDisclosureArrowRightPadding() const { | |
808 switch (style_) { | |
809 case STYLE_SHOW_DROP_DOWN_ON_CLICK: | |
810 return kDisclosureArrowRightPadding; | |
811 case STYLE_NOTIFY_ON_CLICK: | |
812 return kDisclosureArrowButtonRightPadding; | |
813 } | |
814 NOTREACHED(); | |
815 return 0; | |
816 } | |
817 | |
818 void Combobox::HandleClickEvent() { | |
819 if (style_ != STYLE_NOTIFY_ON_CLICK) | |
820 return; | |
821 | |
822 if (listener_) | |
823 listener_->OnComboboxTextButtonClicked(this); | |
824 } | |
825 | |
485 } // namespace views | 826 } // namespace views |
OLD | NEW |