// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/controls/combobox/combobox.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/strings/utf_string_conversions.h"
#include "grit/ui_resources.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/animation/throb_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/common_theme.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/background.h"
#include "ui/views/color_constants.h"
#include "ui/views/controls/button/custom_button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/combobox/combobox_listener.h"
#include "ui/views/controls/focusable_border.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/menu/menu_runner_handler.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/controls/prefix_selector.h"
#include "ui/views/ime/input_method.h"
#include "ui/views/mouse_constants.h"
#include "ui/views/painter.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
// Menu border widths
const int kMenuBorderWidthLeft = 1;
const int kMenuBorderWidthTop = 1;
const int kMenuBorderWidthRight = 1;
// Limit how small a combobox can be.
const int kMinComboboxWidth = 25;
// Size of the combobox arrow margins
const int kDisclosureArrowLeftPadding = 7;
const int kDisclosureArrowRightPadding = 7;
const int kDisclosureArrowButtonLeftPadding = 11;
const int kDisclosureArrowButtonRightPadding = 12;
// Define the id of the first item in the menu (since it needs to be > 0)
const int kFirstMenuItemId = 1000;
// Used to indicate that no item is currently selected by the user.
const int kNoSelection = -1;
const int kBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON);
const int kHoveredBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
const int kPressedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
const int kFocusedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
const int kFocusedHoveredBodyButtonImages[] =
IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
const int kFocusedPressedBodyButtonImages[] =
IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
#define MENU_IMAGE_GRID(x) { \
x ## _MENU_TOP, x ## _MENU_CENTER, x ## _MENU_BOTTOM, }
const int kMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON);
const int kHoveredMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
const int kPressedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
const int kFocusedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
const int kFocusedHoveredMenuButtonImages[] =
MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
const int kFocusedPressedMenuButtonImages[] =
MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
#undef MENU_IMAGE_GRID
// The transparent button which holds a button state but is not rendered.
class TransparentButton : public CustomButton {
public:
TransparentButton(ButtonListener* listener)
: CustomButton(listener) {
SetAnimationDuration(LabelButton::kHoverAnimationDurationMs);
}
virtual ~TransparentButton() {}
virtual bool OnMousePressed(const ui::MouseEvent& mouse_event) OVERRIDE {
parent()->RequestFocus();
return true;
}
double GetAnimationValue() const {
return hover_animation_->GetCurrentValue();
}
private:
DISALLOW_COPY_AND_ASSIGN(TransparentButton);
};
// Returns the next or previous valid index (depending on |increment|'s value).
// Skips separator or disabled indices. Returns -1 if there is no valid adjacent
// index.
int GetAdjacentIndex(ui::ComboboxModel* model, int increment, int index) {
DCHECK(increment == -1 || increment == 1);
index += increment;
while (index >= 0 && index < model->GetItemCount()) {
if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index))
return index;
index += increment;
}
return kNoSelection;
}
// Returns the image resource ids of an array for the body button.
//
// TODO(hajimehoshi): This function should return the images for the 'disabled'
// status. (crbug/270052)
const int* GetBodyButtonImageIds(bool focused,
Button::ButtonState state,
size_t* num) {
DCHECK(num);
*num = 9;
switch (state) {
case Button::STATE_DISABLED:
return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
case Button::STATE_NORMAL:
return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
case Button::STATE_HOVERED:
return focused ?
kFocusedHoveredBodyButtonImages : kHoveredBodyButtonImages;
case Button::STATE_PRESSED:
return focused ?
kFocusedPressedBodyButtonImages : kPressedBodyButtonImages;
default:
NOTREACHED();
}
return NULL;
}
// Returns the image resource ids of an array for the menu button.
const int* GetMenuButtonImageIds(bool focused,
Button::ButtonState state,
size_t* num) {
DCHECK(num);
*num = 3;
switch (state) {
case Button::STATE_DISABLED:
return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
case Button::STATE_NORMAL:
return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
case Button::STATE_HOVERED:
return focused ?
kFocusedHoveredMenuButtonImages : kHoveredMenuButtonImages;
case Button::STATE_PRESSED:
return focused ?
kFocusedPressedMenuButtonImages : kPressedMenuButtonImages;
default:
NOTREACHED();
}
return NULL;
}
// Returns the images for the menu buttons.
std::vector<const gfx::ImageSkia*> GetMenuButtonImages(
bool focused,
Button::ButtonState state) {
const int* ids;
size_t num_ids;
ids = GetMenuButtonImageIds(focused, state, &num_ids);
std::vector<const gfx::ImageSkia*> images;
images.reserve(num_ids);
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
for (size_t i = 0; i < num_ids; i++)
images.push_back(rb.GetImageSkiaNamed(ids[i]));
return images;
}
// Paints three images in a column at the given location. The center image is
// stretched so as to fit the given height.
void PaintImagesVertically(gfx::Canvas* canvas,
const gfx::ImageSkia& top_image,
const gfx::ImageSkia& center_image,
const gfx::ImageSkia& bottom_image,
int x, int y, int width, int height) {
canvas->DrawImageInt(top_image,
0, 0, top_image.width(), top_image.height(),
x, y, width, top_image.height(), false);
y += top_image.height();
int center_height = height - top_image.height() - bottom_image.height();
canvas->DrawImageInt(center_image,
0, 0, center_image.width(), center_image.height(),
x, y, width, center_height, false);
y += center_height;
canvas->DrawImageInt(bottom_image,
0, 0, bottom_image.width(), bottom_image.height(),
x, y, width, bottom_image.height(), false);
}
// Paints the arrow button.
void PaintArrowButton(
gfx::Canvas* canvas,
const std::vector<const gfx::ImageSkia*>& arrow_button_images,
int x, int height) {
PaintImagesVertically(canvas,
*arrow_button_images[0],
*arrow_button_images[1],
*arrow_button_images[2],
x, 0, arrow_button_images[0]->width(), height);
}
} // namespace
// static
const char Combobox::kViewClassName[] = "views/Combobox";
////////////////////////////////////////////////////////////////////////////////
// Combobox, public:
Combobox::Combobox(ui::ComboboxModel* model)
: model_(model),
style_(STYLE_NORMAL),
listener_(NULL),
selected_index_(model_->GetDefaultIndex()),
invalid_(false),
dropdown_open_(false),
text_button_(new TransparentButton(this)),
arrow_button_(new TransparentButton(this)),
weak_ptr_factory_(this) {
model_->AddObserver(this);
UpdateFromModel();
SetFocusable(true);
UpdateBorder();
// Initialize the button images.
Button::ButtonState button_states[] = {
Button::STATE_DISABLED,
Button::STATE_NORMAL,
Button::STATE_HOVERED,
Button::STATE_PRESSED,
};
for (int i = 0; i < 2; i++) {
for (size_t state_index = 0; state_index < arraysize(button_states);
state_index++) {
Button::ButtonState state = button_states[state_index];
size_t num;
bool focused = !!i;
const int* ids = GetBodyButtonImageIds(focused, state, &num);
body_button_painters_[focused][state].reset(
Painter::CreateImageGridPainter(ids));
menu_button_images_[focused][state] = GetMenuButtonImages(focused, state);
}
}
text_button_->SetVisible(true);
arrow_button_->SetVisible(true);
text_button_->SetFocusable(false);
arrow_button_->SetFocusable(false);
AddChildView(text_button_);
AddChildView(arrow_button_);
}
Combobox::~Combobox() {
model_->RemoveObserver(this);
}
// static
const gfx::FontList& Combobox::GetFontList() {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
return rb.GetFontList(ui::ResourceBundle::BaseFont);
}
void Combobox::SetStyle(Style style) {
if (style_ == style)
return;
style_ = style;
if (style_ == STYLE_ACTION)
selected_index_ = 0;
UpdateBorder();
UpdateFromModel();
PreferredSizeChanged();
}
void Combobox::ModelChanged() {
selected_index_ = std::min(0, model_->GetItemCount());
UpdateFromModel();
PreferredSizeChanged();
}
void Combobox::SetSelectedIndex(int index) {
if (style_ == STYLE_ACTION)
return;
selected_index_ = index;
SchedulePaint();
}
bool Combobox::SelectValue(const base::string16& value) {
if (style_ == STYLE_ACTION)
return false;
for (int i = 0; i < model()->GetItemCount(); ++i) {
if (value == model()->GetItemAt(i)) {
SetSelectedIndex(i);
return true;
}
}
return false;
}
void Combobox::SetAccessibleName(const base::string16& name) {
accessible_name_ = name;
}
void Combobox::SetInvalid(bool invalid) {
if (invalid == invalid_)
return;
invalid_ = invalid;
UpdateBorder();
SchedulePaint();
}
ui::TextInputClient* Combobox::GetTextInputClient() {
if (!selector_)
selector_.reset(new PrefixSelector(this));
return selector_.get();
}
void Combobox::Layout() {
PrefixDelegate::Layout();
gfx::Insets insets = GetInsets();
int text_button_width = 0;
int arrow_button_width = 0;
switch (style_) {
case STYLE_NORMAL: {
arrow_button_width = width();
break;
}
case STYLE_ACTION: {
arrow_button_width = GetDisclosureArrowLeftPadding() +
ArrowSize().width() +
GetDisclosureArrowRightPadding();
text_button_width = width() - arrow_button_width;
break;
}
}
int arrow_button_x = std::max(0, text_button_width);
text_button_->SetBounds(0, 0, std::max(0, text_button_width), height());
arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height());
}
bool Combobox::IsItemChecked(int id) const {
return false;
}
bool Combobox::IsCommandEnabled(int id) const {
return model()->IsItemEnabledAt(MenuCommandToIndex(id));
}
void Combobox::ExecuteCommand(int id) {
selected_index_ = MenuCommandToIndex(id);
OnPerformAction();
}
bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const {
return false;
}
int Combobox::GetRowCount() {
return model()->GetItemCount();
}
int Combobox::GetSelectedRow() {
return selected_index_;
}
void Combobox::SetSelectedRow(int row) {
int prev_index = selected_index_;
SetSelectedIndex(row);
if (selected_index_ != prev_index)
OnPerformAction();
}
base::string16 Combobox::GetTextForRow(int row) {
return model()->IsItemSeparatorAt(row) ? base::string16() :
model()->GetItemAt(row);
}
////////////////////////////////////////////////////////////////////////////////
// Combobox, View overrides:
gfx::Size Combobox::GetPreferredSize() const {
// The preferred size will drive the local bounds which in turn is used to set
// the minimum width for the dropdown list.
gfx::Insets insets = GetInsets();
int total_width = std::max(kMinComboboxWidth, content_size_.width()) +
insets.width() + GetDisclosureArrowLeftPadding() +
ArrowSize().width() + GetDisclosureArrowRightPadding();
return gfx::Size(total_width, content_size_.height() + insets.height());
}
const char* Combobox::GetClassName() const {
return kViewClassName;
}
bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
// Escape should close the drop down list when it is active, not host UI.
if (e.key_code() != ui::VKEY_ESCAPE ||
e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) {
return false;
}
return dropdown_open_;
}
bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
// TODO(oshima): handle IME.
DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);
DCHECK_GE(selected_index_, 0);
DCHECK_LT(selected_index_, model()->GetItemCount());
if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
selected_index_ = 0;
bool show_menu = false;
int new_index = kNoSelection;
switch (e.key_code()) {
// Show the menu on F4 without modifiers.
case ui::VKEY_F4:
if (e.IsAltDown() || e.IsAltGrDown() || e.IsControlDown())
return false;
show_menu = true;
break;
// Move to the next item if any, or show the menu on Alt+Down like Windows.
case ui::VKEY_DOWN:
if (e.IsAltDown())
show_menu = true;
else
new_index = GetAdjacentIndex(model(), 1, selected_index_);
break;
// Move to the end of the list.
case ui::VKEY_END:
case ui::VKEY_NEXT: // Page down.
new_index = GetAdjacentIndex(model(), -1, model()->GetItemCount());
break;
// Move to the beginning of the list.
case ui::VKEY_HOME:
case ui::VKEY_PRIOR: // Page up.
new_index = GetAdjacentIndex(model(), 1, -1);
break;
// Move to the previous item if any.
case ui::VKEY_UP:
new_index = GetAdjacentIndex(model(), -1, selected_index_);
break;
// Click the button only when the button style mode.
case ui::VKEY_SPACE:
if (style_ == STYLE_ACTION) {
// When pressing space, the click event will be raised after the key is
// released.
text_button_->SetState(Button::STATE_PRESSED);
} else {
return false;
}
break;
// Click the button only when the button style mode.
case ui::VKEY_RETURN:
if (style_ != STYLE_ACTION)
return false;
OnPerformAction();
break;
default:
return false;
}
if (show_menu) {
UpdateFromModel();
ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD);
} else if (new_index != selected_index_ && new_index != kNoSelection &&
style_ != STYLE_ACTION) {
DCHECK(!model()->IsItemSeparatorAt(new_index));
selected_index_ = new_index;
OnPerformAction();
}
return true;
}
bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
if (style_ != STYLE_ACTION)
return false; // crbug.com/127520
if (e.key_code() == ui::VKEY_SPACE && style_ == STYLE_ACTION)
OnPerformAction();
return false;
}
void Combobox::OnPaint(gfx::Canvas* canvas) {
switch (style_) {
case STYLE_NORMAL: {
OnPaintBackground(canvas);
PaintText(canvas);
OnPaintBorder(canvas);
break;
}
case STYLE_ACTION: {
PaintButtons(canvas);
PaintText(canvas);
break;
}
}
}
void Combobox::OnFocus() {
GetInputMethod()->OnFocus();
View::OnFocus();
// Border renders differently when focused.
SchedulePaint();
}
void Combobox::OnBlur() {
GetInputMethod()->OnBlur();
if (selector_)
selector_->OnViewBlur();
// Border renders differently when focused.
SchedulePaint();
}
void Combobox::GetAccessibleState(ui::AXViewState* state) {
state->role = ui::AX_ROLE_COMBO_BOX;
state->name = accessible_name_;
state->value = model_->GetItemAt(selected_index_);
state->index = selected_index_;
state->count = model_->GetItemCount();
}
void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
DCHECK_EQ(model, model_);
ModelChanged();
}
void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
if (!enabled())
return;
RequestFocus();
if (sender == text_button_) {
OnPerformAction();
} else {
DCHECK_EQ(arrow_button_, sender);
// TODO(hajimehoshi): Fix the problem that the arrow button blinks when
// cliking this while the dropdown menu is opened.
const base::TimeDelta delta = base::Time::Now() - closed_time_;
if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks)
return;
ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
if (event.IsKeyEvent())
source_type = ui::MENU_SOURCE_KEYBOARD;
else if (event.IsGestureEvent() || event.IsTouchEvent())
source_type = ui::MENU_SOURCE_TOUCH;
ShowDropDownMenu(source_type);
}
}
void Combobox::UpdateFromModel() {
const gfx::FontList& font_list = Combobox::GetFontList();
MenuItemView* menu = new MenuItemView(this);
// MenuRunner owns |menu|.
dropdown_list_menu_runner_.reset(new MenuRunner(menu));
int num_items = model()->GetItemCount();
int width = 0;
bool text_item_appended = false;
for (int i = 0; i < num_items; ++i) {
// When STYLE_ACTION is used, the first item and the following separators
// are not added to the dropdown menu. It is assumed that the first item is
// always selected and rendered on the top of the action button.
if (model()->IsItemSeparatorAt(i)) {
if (text_item_appended || style_ != STYLE_ACTION)
menu->AppendSeparator();
continue;
}
base::string16 text = model()->GetItemAt(i);
// Inserting the Unicode formatting characters if necessary so that the
// text is displayed correctly in right-to-left UIs.
base::i18n::AdjustStringForLocaleDirection(&text);
if (style_ != STYLE_ACTION || i > 0) {
menu->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
text_item_appended = true;
}
if (style_ != STYLE_ACTION || i == selected_index_)
width = std::max(width, gfx::GetStringWidth(text, font_list));
}
content_size_.SetSize(width, font_list.GetHeight());
}
void Combobox::UpdateBorder() {
scoped_ptr<FocusableBorder> border(new FocusableBorder());
if (style_ == STYLE_ACTION)
border->SetInsets(8, 13, 8, 13);
if (invalid_)
border->SetColor(kWarningColor);
SetBorder(border.PassAs<Border>());
}
void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
rect->set_x(GetMirroredXForRect(*rect));
}
void Combobox::PaintText(gfx::Canvas* canvas) {
gfx::Insets insets = GetInsets();
gfx::ScopedCanvas scoped_canvas(canvas);
canvas->ClipRect(GetContentsBounds());
int x = insets.left();
int y = insets.top();
int text_height = height() - insets.height();
SkColor text_color = GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_LabelEnabledColor);
DCHECK_GE(selected_index_, 0);
DCHECK_LT(selected_index_, model()->GetItemCount());
if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
selected_index_ = 0;
base::string16 text = model()->GetItemAt(selected_index_);
gfx::Size arrow_size = ArrowSize();
int disclosure_arrow_offset = width() - arrow_size.width() -
GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();
const gfx::FontList& font_list = Combobox::GetFontList();
int text_width = gfx::GetStringWidth(text, font_list);
if ((text_width + insets.width()) > disclosure_arrow_offset)
text_width = disclosure_arrow_offset - insets.width();
gfx::Rect text_bounds(x, y, text_width, text_height);
AdjustBoundsForRTLUI(&text_bounds);
canvas->DrawStringRect(text, font_list, text_color, text_bounds);
int arrow_x = disclosure_arrow_offset + GetDisclosureArrowLeftPadding();
gfx::Rect arrow_bounds(arrow_x,
height() / 2 - arrow_size.height() / 2,
arrow_size.width(),
arrow_size.height());
AdjustBoundsForRTLUI(&arrow_bounds);
// TODO(estade): hack alert! Remove this direct call into CommonTheme. For now
// STYLE_ACTION isn't properly themed so we have to override the NativeTheme
// behavior. See crbug.com/384071
if (style_ == STYLE_ACTION) {
ui::CommonThemePaintComboboxArrow(canvas->sk_canvas(), arrow_bounds);
} else {
ui::NativeTheme::ExtraParams ignored;
GetNativeTheme()->Paint(canvas->sk_canvas(),
ui::NativeTheme::kComboboxArrow,
ui::NativeTheme::kNormal,
arrow_bounds,
ignored);
}
}
void Combobox::PaintButtons(gfx::Canvas* canvas) {
DCHECK(style_ == STYLE_ACTION);
gfx::ScopedCanvas scoped_canvas(canvas);
if (base::i18n::IsRTL()) {
canvas->Translate(gfx::Vector2d(width(), 0));
canvas->Scale(-1, 1);
}
bool focused = HasFocus();
const std::vector<const gfx::ImageSkia*>& arrow_button_images =
menu_button_images_[focused][
arrow_button_->state() == Button::STATE_HOVERED ?
Button::STATE_NORMAL : arrow_button_->state()];
int text_button_hover_alpha =
text_button_->state() == Button::STATE_PRESSED ? 0 :
static_cast<int>(static_cast<TransparentButton*>(text_button_)->
GetAnimationValue() * 255);
if (text_button_hover_alpha < 255) {
canvas->SaveLayerAlpha(255 - text_button_hover_alpha);
Painter* text_button_painter =
body_button_painters_[focused][
text_button_->state() == Button::STATE_HOVERED ?
Button::STATE_NORMAL : text_button_->state()].get();
Painter::PaintPainterAt(canvas, text_button_painter,
gfx::Rect(0, 0, text_button_->width(), height()));
canvas->Restore();
}
if (0 < text_button_hover_alpha) {
canvas->SaveLayerAlpha(text_button_hover_alpha);
Painter* text_button_hovered_painter =
body_button_painters_[focused][Button::STATE_HOVERED].get();
Painter::PaintPainterAt(canvas, text_button_hovered_painter,
gfx::Rect(0, 0, text_button_->width(), height()));
canvas->Restore();
}
int arrow_button_hover_alpha =
arrow_button_->state() == Button::STATE_PRESSED ? 0 :
static_cast<int>(static_cast<TransparentButton*>(arrow_button_)->
GetAnimationValue() * 255);
if (arrow_button_hover_alpha < 255) {
canvas->SaveLayerAlpha(255 - arrow_button_hover_alpha);
PaintArrowButton(canvas, arrow_button_images, arrow_button_->x(), height());
canvas->Restore();
}
if (0 < arrow_button_hover_alpha) {
canvas->SaveLayerAlpha(arrow_button_hover_alpha);
const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images =
menu_button_images_[focused][Button::STATE_HOVERED];
PaintArrowButton(canvas, arrow_button_hovered_images,
arrow_button_->x(), height());
canvas->Restore();
}
}
void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
if (!dropdown_list_menu_runner_.get())
UpdateFromModel();
// Extend the menu to the width of the combobox.
MenuItemView* menu = dropdown_list_menu_runner_->GetMenu();
SubmenuView* submenu = menu->CreateSubmenu();
submenu->set_minimum_preferred_width(
size().width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
gfx::Rect lb = GetLocalBounds();
gfx::Point menu_position(lb.origin());
if (style_ == STYLE_NORMAL) {
// Inset the menu's requested position so the border of the menu lines up
// with the border of the combobox.
menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
}
lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
View::ConvertPointToScreen(this, &menu_position);
if (menu_position.x() < 0)
menu_position.set_x(0);
gfx::Rect bounds(menu_position, lb.size());
Button::ButtonState original_state = Button::STATE_NORMAL;
if (arrow_button_) {
original_state = arrow_button_->state();
arrow_button_->SetState(Button::STATE_PRESSED);
}
dropdown_open_ = true;
MenuAnchorPosition anchor_position =
style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT;
if (dropdown_list_menu_runner_->RunMenuAt(GetWidget(), NULL, bounds,
anchor_position, source_type,
MenuRunner::COMBOBOX) ==
MenuRunner::MENU_DELETED) {
return;
}
dropdown_open_ = false;
if (arrow_button_)
arrow_button_->SetState(original_state);
closed_time_ = base::Time::Now();
// Need to explicitly clear mouse handler so that events get sent
// properly after the menu finishes running. If we don't do this, then
// the first click to other parts of the UI is eaten.
SetMouseHandler(NULL);
}
void Combobox::OnPerformAction() {
NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false);
SchedulePaint();
// This combobox may be deleted by the listener.
base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr();
if (listener_)
listener_->OnPerformAction(this);
if (weak_ptr && style_ == STYLE_ACTION)
selected_index_ = 0;
}
int Combobox::MenuCommandToIndex(int menu_command_id) const {
// (note that the id received is offset by kFirstMenuItemId)
// Revert menu ID offset to map back to combobox model.
int index = menu_command_id - kFirstMenuItemId;
DCHECK_LT(index, model()->GetItemCount());
return index;
}
int Combobox::GetDisclosureArrowLeftPadding() const {
switch (style_) {
case STYLE_NORMAL:
return kDisclosureArrowLeftPadding;
case STYLE_ACTION:
return kDisclosureArrowButtonLeftPadding;
}
NOTREACHED();
return 0;
}
int Combobox::GetDisclosureArrowRightPadding() const {
switch (style_) {
case STYLE_NORMAL:
return kDisclosureArrowRightPadding;
case STYLE_ACTION:
return kDisclosureArrowButtonRightPadding;
}
NOTREACHED();
return 0;
}
gfx::Size Combobox::ArrowSize() const {
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
// TODO(estade): hack alert! This should always use GetNativeTheme(). For now
// STYLE_ACTION isn't properly themed so we have to override the NativeTheme
// behavior. See crbug.com/384071
const ui::NativeTheme* native_theme_for_arrow = style_ == STYLE_ACTION ?
ui::NativeTheme::instance() :
GetNativeTheme();
#else
const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme();
#endif
ui::NativeTheme::ExtraParams ignored;
return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow,
ui::NativeTheme::kNormal,
ignored);
}
} // namespace views