OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "views/controls/combobox/native_combobox_views.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/command_line.h" | |
10 #include "base/utf_string_conversions.h" | |
11 #include "ui/base/keycodes/keyboard_codes.h" | |
12 #include "ui/base/models/combobox_model.h" | |
13 #include "ui/base/resource/resource_bundle.h" | |
14 #include "ui/gfx/canvas.h" | |
15 #include "ui/gfx/canvas_skia.h" | |
16 #include "ui/gfx/font.h" | |
17 #include "ui/gfx/path.h" | |
18 #include "ui/views/widget/root_view.h" | |
19 #include "ui/views/widget/widget.h" | |
20 #include "views/background.h" | |
21 #include "views/border.h" | |
22 #include "views/controls/combobox/combobox.h" | |
23 #include "views/controls/focusable_border.h" | |
24 #include "views/controls/menu/menu_runner.h" | |
25 #include "views/controls/menu/submenu_view.h" | |
26 | |
27 #if defined(OS_LINUX) | |
28 #include "ui/gfx/gtk_util.h" | |
29 #endif | |
30 | |
31 namespace { | |
32 | |
33 // Define the size of the insets. | |
34 const int kTopInsetSize = 4; | |
35 const int kLeftInsetSize = 4; | |
36 const int kBottomInsetSize = 4; | |
37 const int kRightInsetSize = 4; | |
38 | |
39 // Limit how small a combobox can be. | |
40 const int kMinComboboxWidth = 148; | |
41 | |
42 // Size of the combobox arrow. | |
43 const int kComboboxArrowSize = 9; | |
44 const int kComboboxArrowOffset = 7; | |
45 const int kComboboxArrowMargin = 12; | |
46 | |
47 // Color settings for text and border. | |
48 // These are tentative, and should be derived from theme, system | |
49 // settings and current settings. | |
50 const SkColor kDefaultBorderColor = SK_ColorGRAY; | |
51 const SkColor kTextColor = SK_ColorBLACK; | |
52 | |
53 // Define the id of the first item in the menu (since it needs to be > 0) | |
54 const int kFirstMenuItemId = 1000; | |
55 | |
56 } // namespace | |
57 | |
58 namespace views { | |
59 | |
60 const char NativeComboboxViews::kViewClassName[] = | |
61 "views/NativeComboboxViews"; | |
62 | |
63 NativeComboboxViews::NativeComboboxViews(Combobox* parent) | |
64 : combobox_(parent), | |
65 text_border_(new FocusableBorder()), | |
66 dropdown_open_(false), | |
67 selected_item_(-1), | |
68 content_width_(0), | |
69 content_height_(0) { | |
70 set_border(text_border_); | |
71 } | |
72 | |
73 NativeComboboxViews::~NativeComboboxViews() { | |
74 } | |
75 | |
76 //////////////////////////////////////////////////////////////////////////////// | |
77 // NativeComboboxViews, View overrides: | |
78 | |
79 bool NativeComboboxViews::OnMousePressed(const views::MouseEvent& mouse_event) { | |
80 combobox_->RequestFocus(); | |
81 if (mouse_event.IsLeftMouseButton()) { | |
82 UpdateFromModel(); | |
83 ShowDropDownMenu(); | |
84 } | |
85 | |
86 return true; | |
87 } | |
88 | |
89 bool NativeComboboxViews::OnMouseDragged(const views::MouseEvent& mouse_event) { | |
90 return true; | |
91 } | |
92 | |
93 bool NativeComboboxViews::OnKeyPressed(const views::KeyEvent& key_event) { | |
94 // TODO(oshima): handle IME. | |
95 DCHECK(key_event.type() == ui::ET_KEY_PRESSED); | |
96 | |
97 // Check if we are in the default state (-1) and set to first item. | |
98 if(selected_item_ == -1) | |
99 selected_item_ = 0; | |
100 | |
101 int new_item = selected_item_; | |
102 switch(key_event.key_code()){ | |
103 | |
104 // move to the next element if any | |
105 case ui::VKEY_DOWN: | |
106 if (new_item < (combobox_->model()->GetItemCount() - 1)) | |
107 new_item++; | |
108 break; | |
109 | |
110 // move to the end of the list | |
111 case ui::VKEY_END: | |
112 case ui::VKEY_NEXT: | |
113 new_item = combobox_->model()->GetItemCount() - 1; | |
114 break; | |
115 | |
116 // move to the top of the list | |
117 case ui::VKEY_HOME: | |
118 case ui::VKEY_PRIOR: | |
119 new_item = 0; | |
120 break; | |
121 | |
122 // move to the previous element if any | |
123 case ui::VKEY_UP: | |
124 if (new_item > 0) | |
125 new_item--; | |
126 break; | |
127 | |
128 default: | |
129 return false; | |
130 | |
131 } | |
132 | |
133 if(new_item != selected_item_) { | |
134 selected_item_ = new_item; | |
135 combobox_->SelectionChanged(); | |
136 SchedulePaint(); | |
137 } | |
138 | |
139 return true; | |
140 } | |
141 | |
142 bool NativeComboboxViews::OnKeyReleased(const views::KeyEvent& key_event) { | |
143 return true; | |
144 } | |
145 | |
146 void NativeComboboxViews::OnPaint(gfx::Canvas* canvas) { | |
147 text_border_->set_has_focus(combobox_->HasFocus()); | |
148 OnPaintBackground(canvas); | |
149 PaintText(canvas); | |
150 OnPaintBorder(canvas); | |
151 } | |
152 | |
153 void NativeComboboxViews::OnFocus() { | |
154 NOTREACHED(); | |
155 } | |
156 | |
157 void NativeComboboxViews::OnBlur() { | |
158 NOTREACHED(); | |
159 } | |
160 | |
161 ///////////////////////////////////////////////////////////////// | |
162 // NativeComboboxViews, NativeComboboxWrapper overrides: | |
163 | |
164 void NativeComboboxViews::UpdateFromModel() { | |
165 int max_width = 0; | |
166 const gfx::Font &font = GetFont(); | |
167 | |
168 MenuItemView* menu = new MenuItemView(this); | |
169 // MenuRunner owns |menu|. | |
170 dropdown_list_menu_runner_.reset(new MenuRunner(menu)); | |
171 | |
172 int num_items = combobox_->model()->GetItemCount(); | |
173 for (int i = 0; i < num_items; ++i) { | |
174 string16 text = combobox_->model()->GetItemAt(i); | |
175 | |
176 // Inserting the Unicode formatting characters if necessary so that the | |
177 // text is displayed correctly in right-to-left UIs. | |
178 base::i18n::AdjustStringForLocaleDirection(&text); | |
179 | |
180 menu->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL); | |
181 max_width = std::max(max_width, font.GetStringWidth(text)); | |
182 } | |
183 | |
184 content_width_ = max_width; | |
185 content_height_ = font.GetFontSize(); | |
186 } | |
187 | |
188 void NativeComboboxViews::UpdateSelectedItem() { | |
189 selected_item_ = combobox_->selected_item(); | |
190 } | |
191 | |
192 void NativeComboboxViews::UpdateEnabled() { | |
193 SetEnabled(combobox_->IsEnabled()); | |
194 } | |
195 | |
196 int NativeComboboxViews::GetSelectedItem() const { | |
197 return selected_item_; | |
198 } | |
199 | |
200 bool NativeComboboxViews::IsDropdownOpen() const { | |
201 return dropdown_open_; | |
202 } | |
203 | |
204 gfx::Size NativeComboboxViews::GetPreferredSize() { | |
205 if (content_width_ == 0) | |
206 UpdateFromModel(); | |
207 | |
208 // TODO(saintlou) the preferred size will drive the local bounds | |
209 // which in turn is used to set the minimum width for the dropdown | |
210 gfx::Insets insets = GetInsets(); | |
211 return gfx::Size(std::min(kMinComboboxWidth, | |
212 content_width_ + 2 * (insets.width())), | |
213 content_height_ + 2 * (insets.height())); | |
214 } | |
215 | |
216 View* NativeComboboxViews::GetView() { | |
217 return this; | |
218 } | |
219 | |
220 void NativeComboboxViews::SetFocus() { | |
221 text_border_->set_has_focus(true); | |
222 } | |
223 | |
224 bool NativeComboboxViews::HandleKeyPressed(const KeyEvent& e) { | |
225 return OnKeyPressed(e); | |
226 } | |
227 | |
228 bool NativeComboboxViews::HandleKeyReleased(const KeyEvent& e) { | |
229 return true; | |
230 } | |
231 | |
232 void NativeComboboxViews::HandleFocus() { | |
233 SchedulePaint(); | |
234 } | |
235 | |
236 void NativeComboboxViews::HandleBlur() { | |
237 } | |
238 | |
239 gfx::NativeView NativeComboboxViews::GetTestingHandle() const { | |
240 NOTREACHED(); | |
241 return NULL; | |
242 } | |
243 | |
244 ///////////////////////////////////////////////////////////////// | |
245 // NativeComboboxViews, views::MenuDelegate overrides: | |
246 // (note that the id received is offset by kFirstMenuItemId) | |
247 | |
248 bool NativeComboboxViews::IsItemChecked(int id) const { | |
249 return false; | |
250 } | |
251 | |
252 bool NativeComboboxViews::IsCommandEnabled(int id) const { | |
253 return true; | |
254 } | |
255 | |
256 void NativeComboboxViews::ExecuteCommand(int id) { | |
257 // revert menu offset to map back to combobox model | |
258 id -= kFirstMenuItemId; | |
259 DCHECK_LT(id, combobox_->model()->GetItemCount()); | |
260 selected_item_ = id; | |
261 combobox_->SelectionChanged(); | |
262 SchedulePaint(); | |
263 } | |
264 | |
265 bool NativeComboboxViews::GetAccelerator(int id, ui::Accelerator* accel) { | |
266 return false; | |
267 } | |
268 | |
269 ///////////////////////////////////////////////////////////////// | |
270 // NativeComboboxViews private methods: | |
271 | |
272 const gfx::Font& NativeComboboxViews::GetFont() const { | |
273 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
274 return rb.GetFont(ResourceBundle::BaseFont); | |
275 } | |
276 | |
277 // tip_x and tip_y are the coordinates of the tip of an arrow head which is | |
278 // drawn as an isoscele triangle | |
279 // shift_x and shift_y are offset from tip_x and tip_y to specify the relative | |
280 // positions of the 2 equal angles (ie not the angle at the tip of the arrow) | |
281 // Note: the width of the base (the side opposite the tip) is 2 * shift_x | |
282 void NativeComboboxViews::DrawArrow(gfx::Canvas* canvas, | |
283 int tip_x, | |
284 int tip_y, | |
285 int shift_x, | |
286 int shift_y) const { | |
287 SkPaint paint; | |
288 paint.setStyle(SkPaint::kStrokeAndFill_Style); | |
289 paint.setColor(kTextColor); | |
290 paint.setAntiAlias(true); | |
291 gfx::Path path; | |
292 path.incReserve(4); | |
293 path.moveTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y)); | |
294 path.lineTo(SkIntToScalar(tip_x + shift_x), SkIntToScalar(tip_y + shift_y)); | |
295 path.lineTo(SkIntToScalar(tip_x - shift_x), SkIntToScalar(tip_y + shift_y)); | |
296 path.close(); | |
297 canvas->GetSkCanvas()->drawPath(path, paint); | |
298 } | |
299 | |
300 | |
301 void NativeComboboxViews::PaintText(gfx::Canvas* canvas) { | |
302 gfx::Insets insets = GetInsets(); | |
303 | |
304 canvas->Save(); | |
305 canvas->ClipRect(GetContentsBounds()); | |
306 | |
307 int x = insets.left(); | |
308 int y = insets.top(); | |
309 int text_height = height() - insets.height(); | |
310 SkColor text_color = kTextColor; | |
311 | |
312 int index = GetSelectedItem(); | |
313 if (index < 0 || index > combobox_->model()->GetItemCount()) | |
314 index = 0; | |
315 string16 text = combobox_->model()->GetItemAt(index); | |
316 | |
317 const gfx::Font& font = GetFont(); | |
318 int width = font.GetStringWidth(text); | |
319 | |
320 canvas->DrawStringInt(text, font, text_color, x, y, width, text_height); | |
321 | |
322 // draw the double arrow | |
323 gfx::Rect lb = GetLocalBounds(); | |
324 DrawArrow(canvas, | |
325 lb.width() - (kComboboxArrowSize / 2) - kComboboxArrowOffset, | |
326 lb.height() / 2 - kComboboxArrowSize, | |
327 kComboboxArrowSize / 2, | |
328 kComboboxArrowSize - 2); | |
329 DrawArrow(canvas, | |
330 lb.width() - (kComboboxArrowSize / 2) - kComboboxArrowOffset, | |
331 lb.height() / 2 + kComboboxArrowSize, | |
332 -kComboboxArrowSize / 2, | |
333 -(kComboboxArrowSize - 2)); | |
334 | |
335 // draw the margin | |
336 canvas->DrawLineInt(kDefaultBorderColor, | |
337 lb.width() - kComboboxArrowSize - kComboboxArrowMargin, | |
338 kTopInsetSize, | |
339 lb.width() - kComboboxArrowSize - kComboboxArrowMargin, | |
340 lb.height() - kBottomInsetSize); | |
341 | |
342 canvas->Restore(); | |
343 } | |
344 | |
345 void NativeComboboxViews::ShowDropDownMenu() { | |
346 | |
347 if (!dropdown_list_menu_runner_.get()) | |
348 UpdateFromModel(); | |
349 | |
350 // Extend the menu to the width of the combobox. | |
351 SubmenuView* submenu = dropdown_list_menu_runner_->GetMenu()->CreateSubmenu(); | |
352 submenu->set_minimum_preferred_width(size().width()); | |
353 | |
354 gfx::Rect lb = GetLocalBounds(); | |
355 gfx::Point menu_position(lb.origin()); | |
356 View::ConvertPointToScreen(this, &menu_position); | |
357 if (menu_position.x() < 0) | |
358 menu_position.set_x(0); | |
359 | |
360 gfx::Rect bounds(menu_position, lb.size()); | |
361 | |
362 dropdown_open_ = true; | |
363 if (dropdown_list_menu_runner_->RunMenuAt( | |
364 NULL, NULL, bounds, MenuItemView::TOPLEFT, | |
365 MenuRunner::HAS_MNEMONICS) == MenuRunner::MENU_DELETED) | |
366 return; | |
367 dropdown_open_ = false; | |
368 | |
369 // Need to explicitly clear mouse handler so that events get sent | |
370 // properly after the menu finishes running. If we don't do this, then | |
371 // the first click to other parts of the UI is eaten. | |
372 SetMouseHandler(NULL); | |
373 } | |
374 | |
375 //////////////////////////////////////////////////////////////////////////////// | |
376 // NativeComboboxWrapper, public: | |
377 | |
378 #if defined(USE_AURA) | |
379 // static | |
380 NativeComboboxWrapper* NativeComboboxWrapper::CreateWrapper( | |
381 Combobox* combobox) { | |
382 return new NativeComboboxViews(combobox); | |
383 } | |
384 #endif | |
385 | |
386 } // namespace views | |
OLD | NEW |