// 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/menu/menu_item_view.h"

#include <stddef.h>

#include "base/i18n/case_conversion.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/menu_model.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/common_theme.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/controls/menu/menu_image_util.h"
#include "ui/views/controls/menu/menu_scroll_view_container.h"
#include "ui/views/controls/menu/menu_separator.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/widget/widget.h"

namespace views {

namespace {

// EmptyMenuMenuItem ---------------------------------------------------------

// EmptyMenuMenuItem is used when a menu has no menu items. EmptyMenuMenuItem
// is itself a MenuItemView, but it uses a different ID so that it isn't
// identified as a MenuItemView.

class EmptyMenuMenuItem : public MenuItemView {
 public:
  explicit EmptyMenuMenuItem(MenuItemView* parent)
      : MenuItemView(parent, 0, EMPTY) {
    // Set this so that we're not identified as a normal menu item.
    set_id(kEmptyMenuItemViewID);
    SetTitle(l10n_util::GetStringUTF16(IDS_APP_MENU_EMPTY_SUBMENU));
    SetEnabled(false);
  }

  bool GetTooltipText(const gfx::Point& p,
                      base::string16* tooltip) const override {
    // Empty menu items shouldn't have a tooltip.
    return false;
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(EmptyMenuMenuItem);
};

}  // namespace

// Padding between child views.
static const int kChildXPadding = 8;

// MenuItemView ---------------------------------------------------------------

// static
const int MenuItemView::kMenuItemViewID = 1001;

// static
const int MenuItemView::kEmptyMenuItemViewID =
    MenuItemView::kMenuItemViewID + 1;

// static
int MenuItemView::icon_area_width_ = 0;

// static
int MenuItemView::label_start_;

// static
int MenuItemView::item_right_margin_;

// static
int MenuItemView::pref_menu_height_;

// static
const char MenuItemView::kViewClassName[] = "MenuItemView";

MenuItemView::MenuItemView(MenuDelegate* delegate)
    : delegate_(delegate),
      controller_(NULL),
      canceled_(false),
      parent_menu_item_(NULL),
      type_(SUBMENU),
      selected_(false),
      command_(0),
      submenu_(NULL),
      has_mnemonics_(false),
      show_mnemonics_(false),
      has_icons_(false),
      icon_view_(NULL),
      top_margin_(-1),
      bottom_margin_(-1),
      left_icon_margin_(0),
      right_icon_margin_(0),
      requested_menu_position_(POSITION_BEST_FIT),
      actual_menu_position_(requested_menu_position_),
      use_right_margin_(true) {
  // NOTE: don't check the delegate for NULL, UpdateMenuPartSizes() supplies a
  // NULL delegate.
  Init(NULL, 0, SUBMENU, delegate);
}

void MenuItemView::ChildPreferredSizeChanged(View* child) {
  invalidate_dimensions();
  PreferredSizeChanged();
}

bool MenuItemView::GetTooltipText(const gfx::Point& p,
                                  base::string16* tooltip) const {
  *tooltip = tooltip_;
  if (!tooltip->empty())
    return true;

  if (GetType() == SEPARATOR)
    return false;

  const MenuController* controller = GetMenuController();
  if (!controller || controller->exit_type() != MenuController::EXIT_NONE) {
    // Either the menu has been closed or we're in the process of closing the
    // menu. Don't attempt to query the delegate as it may no longer be valid.
    return false;
  }

  const MenuItemView* root_menu_item = GetRootMenuItem();
  if (root_menu_item->canceled_) {
    // TODO(sky): if |canceled_| is true, controller->exit_type() should be
    // something other than EXIT_NONE, but crash reports seem to indicate
    // otherwise. Figure out why this is needed.
    return false;
  }

  const MenuDelegate* delegate = GetDelegate();
  CHECK(delegate);
  gfx::Point location(p);
  ConvertPointToScreen(this, &location);
  *tooltip = delegate->GetTooltipText(command_, location);
  return !tooltip->empty();
}

void MenuItemView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
  node_data->role = ui::AX_ROLE_MENU_ITEM;

  base::string16 item_text;
  if (IsContainer()) {
    // The first child is taking over, just use its accessible name instead of
    // |title_|.
    View* child = child_at(0);
    ui::AXNodeData node_data;
    child->GetAccessibleNodeData(&node_data);
    item_text = node_data.GetString16Attribute(ui::AX_ATTR_NAME);
  } else {
    item_text = title_;
  }
  node_data->SetName(GetAccessibleNameForMenuItem(item_text, GetMinorText()));

  switch (GetType()) {
    case SUBMENU:
      node_data->AddState(ui::AX_STATE_HASPOPUP);
      break;
    case CHECKBOX:
    case RADIO: {
      const bool is_checked = GetDelegate()->IsItemChecked(GetCommand());
      const ui::AXCheckedState checked_state =
          is_checked ? ui::AX_CHECKED_STATE_TRUE : ui::AX_CHECKED_STATE_FALSE;
      node_data->AddIntAttribute(ui::AX_ATTR_CHECKED_STATE, checked_state);
    } break;
    case NORMAL:
    case SEPARATOR:
    case EMPTY:
      // No additional accessibility states currently for these menu states.
      break;
  }

  base::char16 mnemonic = GetMnemonic();
  if (mnemonic != '\0') {
    node_data->AddStringAttribute(
        ui::AX_ATTR_KEY_SHORTCUTS,
        base::UTF16ToUTF8(base::string16(1, mnemonic)));
  }
}

// static
bool MenuItemView::IsBubble(MenuAnchorPosition anchor) {
  return anchor == MENU_ANCHOR_BUBBLE_LEFT ||
         anchor == MENU_ANCHOR_BUBBLE_RIGHT ||
         anchor == MENU_ANCHOR_BUBBLE_ABOVE ||
         anchor == MENU_ANCHOR_BUBBLE_BELOW;
}

// static
base::string16 MenuItemView::GetAccessibleNameForMenuItem(
      const base::string16& item_text, const base::string16& minor_text) {
  base::string16 accessible_name = item_text;

  // Filter out the "&" for accessibility clients.
  size_t index = 0;
  const base::char16 amp = '&';
  while ((index = accessible_name.find(amp, index)) != base::string16::npos &&
         index + 1 < accessible_name.length()) {
    accessible_name.replace(index, accessible_name.length() - index,
                            accessible_name.substr(index + 1));

    // Special case for "&&" (escaped for "&").
    if (accessible_name[index] == '&')
      ++index;
  }

  // Append subtext.
  if (!minor_text.empty()) {
    accessible_name.push_back(' ');
    accessible_name.append(minor_text);
  }

  return accessible_name;
}

void MenuItemView::Cancel() {
  if (controller_ && !canceled_) {
    canceled_ = true;
    controller_->Cancel(MenuController::EXIT_ALL);
  }
}

MenuItemView* MenuItemView::AddMenuItemAt(
    int index,
    int item_id,
    const base::string16& label,
    const base::string16& sublabel,
    const base::string16& minor_text,
    const gfx::ImageSkia& icon,
    Type type,
    ui::MenuSeparatorType separator_style) {
  DCHECK_NE(type, EMPTY);
  DCHECK_LE(0, index);
  if (!submenu_)
    CreateSubmenu();
  DCHECK_GE(submenu_->child_count(), index);
  if (type == SEPARATOR) {
    submenu_->AddChildViewAt(new MenuSeparator(separator_style), index);
    return NULL;
  }
  MenuItemView* item = new MenuItemView(this, item_id, type);
  if (label.empty() && GetDelegate())
    item->SetTitle(GetDelegate()->GetLabel(item_id));
  else
    item->SetTitle(label);
  item->SetSubtitle(sublabel);
  item->SetMinorText(minor_text);
  if (!icon.isNull())
    item->SetIcon(icon);
  if (type == SUBMENU)
    item->CreateSubmenu();
  if (GetDelegate() && !GetDelegate()->IsCommandVisible(item_id))
    item->SetVisible(false);
  submenu_->AddChildViewAt(item, index);
  return item;
}

void MenuItemView::RemoveMenuItemAt(int index) {
  DCHECK(submenu_);
  DCHECK_LE(0, index);
  DCHECK_GT(submenu_->child_count(), index);

  View* item = submenu_->child_at(index);
  DCHECK(item);
  submenu_->RemoveChildView(item);

  // RemoveChildView() does not delete the item, which is a good thing
  // in case a submenu is being displayed while items are being removed.
  // Deletion will be done by ChildrenChanged() or at destruction.
  removed_items_.push_back(item);
}

MenuItemView* MenuItemView::AppendMenuItem(int item_id,
                                           const base::string16& label,
                                           Type type) {
  return AppendMenuItemImpl(item_id, label, base::string16(), base::string16(),
      gfx::ImageSkia(), type, ui::NORMAL_SEPARATOR);
}

MenuItemView* MenuItemView::AppendSubMenu(int item_id,
                                          const base::string16& label) {
  return AppendMenuItemImpl(item_id, label, base::string16(), base::string16(),
      gfx::ImageSkia(), SUBMENU, ui::NORMAL_SEPARATOR);
}

MenuItemView* MenuItemView::AppendSubMenuWithIcon(int item_id,
                                                  const base::string16& label,
                                                  const gfx::ImageSkia& icon) {
  return AppendMenuItemImpl(item_id, label, base::string16(), base::string16(),
                            icon, SUBMENU, ui::NORMAL_SEPARATOR);
}

MenuItemView* MenuItemView::AppendMenuItemWithLabel(
    int item_id,
    const base::string16& label) {
  return AppendMenuItem(item_id, label, NORMAL);
}

MenuItemView* MenuItemView::AppendDelegateMenuItem(int item_id) {
  return AppendMenuItem(item_id, base::string16(), NORMAL);
}

void MenuItemView::AppendSeparator() {
  AppendMenuItemImpl(0, base::string16(), base::string16(), base::string16(),
                     gfx::ImageSkia(), SEPARATOR, ui::NORMAL_SEPARATOR);
}

MenuItemView* MenuItemView::AppendMenuItemWithIcon(int item_id,
                                                   const base::string16& label,
                                                   const gfx::ImageSkia& icon) {
  return AppendMenuItemImpl(item_id, label, base::string16(), base::string16(),
                            icon, NORMAL, ui::NORMAL_SEPARATOR);
}

MenuItemView* MenuItemView::AppendMenuItemImpl(
    int item_id,
    const base::string16& label,
    const base::string16& sublabel,
    const base::string16& minor_text,
    const gfx::ImageSkia& icon,
    Type type,
    ui::MenuSeparatorType separator_style) {
  const int index = submenu_ ? submenu_->child_count() : 0;
  return AddMenuItemAt(index, item_id, label, sublabel, minor_text, icon, type,
                       separator_style);
}

SubmenuView* MenuItemView::CreateSubmenu() {
  if (!submenu_) {
    submenu_ = new SubmenuView(this);

    // Initialize the submenu indicator icon (arrow).
    submenu_arrow_image_view_ = new ImageView();
    AddChildView(submenu_arrow_image_view_);
  }

  return submenu_;
}

bool MenuItemView::HasSubmenu() const {
  return (submenu_ != NULL);
}

SubmenuView* MenuItemView::GetSubmenu() const {
  return submenu_;
}

bool MenuItemView::SubmenuIsShowing() const {
  return HasSubmenu() && GetSubmenu()->IsShowing();
}

void MenuItemView::SetTitle(const base::string16& title) {
  title_ = title;
  invalidate_dimensions();  // Triggers preferred size recalculation.
}

void MenuItemView::SetSubtitle(const base::string16& subtitle) {
  subtitle_ = subtitle;
  invalidate_dimensions();  // Triggers preferred size recalculation.
}

void MenuItemView::SetMinorText(const base::string16& minor_text) {
  minor_text_ = minor_text;
  invalidate_dimensions();  // Triggers preferred size recalculation.
}

void MenuItemView::SetSelected(bool selected) {
  selected_ = selected;
  SchedulePaint();
}

void MenuItemView::SetTooltip(const base::string16& tooltip, int item_id) {
  MenuItemView* item = GetMenuItemByID(item_id);
  DCHECK(item);
  item->tooltip_ = tooltip;
}

void MenuItemView::SetIcon(const gfx::ImageSkia& icon, int item_id) {
  MenuItemView* item = GetMenuItemByID(item_id);
  DCHECK(item);
  item->SetIcon(icon);
}

void MenuItemView::SetIcon(const gfx::ImageSkia& icon) {
  if (icon.isNull()) {
    SetIconView(NULL);
    return;
  }

  ImageView* icon_view = new ImageView();
  icon_view->SetImage(&icon);
  SetIconView(icon_view);
}

void MenuItemView::SetIconView(View* icon_view) {
  if (icon_view_) {
    RemoveChildView(icon_view_);
    delete icon_view_;
    icon_view_ = NULL;
  }
  if (icon_view) {
    AddChildView(icon_view);
    icon_view_ = icon_view;
  }
  Layout();
  SchedulePaint();
}

void MenuItemView::OnPaint(gfx::Canvas* canvas) {
  PaintButton(canvas, PB_NORMAL);
}

gfx::Size MenuItemView::CalculatePreferredSize() const {
  const MenuItemDimensions& dimensions(GetDimensions());
  return gfx::Size(dimensions.standard_width + dimensions.children_width,
                   dimensions.height);
}

int MenuItemView::GetHeightForWidth(int width) const {
  // If this isn't a container, we can just use the preferred size's height.
  if (!IsContainer())
    return GetPreferredSize().height();

  int height = child_at(0)->GetHeightForWidth(width);
  if (!icon_view_ && GetRootMenuItem()->has_icons())
    height = std::max(height, MenuConfig::instance().check_height);
  height += GetBottomMargin() + GetTopMargin();

  return height;
}

const MenuItemView::MenuItemDimensions& MenuItemView::GetDimensions() const {
  if (!is_dimensions_valid())
    dimensions_ = CalculateDimensions();
  DCHECK(is_dimensions_valid());
  return dimensions_;
}

MenuController* MenuItemView::GetMenuController() {
  return GetRootMenuItem()->controller_.get();
}

const MenuController* MenuItemView::GetMenuController() const {
  return GetRootMenuItem()->controller_.get();
}

MenuDelegate* MenuItemView::GetDelegate() {
  return GetRootMenuItem()->delegate_;
}

const MenuDelegate* MenuItemView::GetDelegate() const {
  return GetRootMenuItem()->delegate_;
}

MenuItemView* MenuItemView::GetRootMenuItem() {
  return const_cast<MenuItemView*>(
      static_cast<const MenuItemView*>(this)->GetRootMenuItem());
}

const MenuItemView* MenuItemView::GetRootMenuItem() const {
  const MenuItemView* item = this;
  for (const MenuItemView* parent = GetParentMenuItem(); parent;
       parent = item->GetParentMenuItem())
    item = parent;
  return item;
}

base::char16 MenuItemView::GetMnemonic() {
  if (!GetRootMenuItem()->has_mnemonics_)
    return 0;

  size_t index = 0;
  do {
    index = title_.find('&', index);
    if (index != base::string16::npos) {
      if (index + 1 != title_.size() && title_[index + 1] != '&') {
        base::char16 char_array[] = { title_[index + 1], 0 };
        // TODO(jshin): What about Turkish locale? See http://crbug.com/81719.
        // If the mnemonic is capital I and the UI language is Turkish,
        // lowercasing it results in 'small dotless i', which is different
        // from a 'dotted i'. Similar issues may exist for az and lt locales.
        return base::i18n::ToLower(char_array)[0];
      }
      index++;
    }
  } while (index != base::string16::npos);
  return 0;
}

MenuItemView* MenuItemView::GetMenuItemByID(int id) {
  if (GetCommand() == id)
    return this;
  if (!HasSubmenu())
    return NULL;
  for (int i = 0; i < GetSubmenu()->child_count(); ++i) {
    View* child = GetSubmenu()->child_at(i);
    if (child->id() == MenuItemView::kMenuItemViewID) {
      MenuItemView* result = static_cast<MenuItemView*>(child)->
          GetMenuItemByID(id);
      if (result)
        return result;
    }
  }
  return NULL;
}

void MenuItemView::ChildrenChanged() {
  MenuController* controller = GetMenuController();
  if (controller) {
    // Handles the case where we were empty and are no longer empty.
    RemoveEmptyMenus();

    // Handles the case where we were not empty, but now are.
    AddEmptyMenus();

    controller->MenuChildrenChanged(this);

    if (submenu_) {
      // Force a paint and layout. This handles the case of the top
      // level window's size remaining the same, resulting in no
      // change to the submenu's size and no layout.
      submenu_->Layout();
      submenu_->SchedulePaint();
      // Update the menu selection after layout.
      controller->UpdateSubmenuSelection(submenu_);
    }
  }

  for (auto* item : removed_items_)
    delete item;
  removed_items_.clear();
}

void MenuItemView::Layout() {
  if (!has_children())
    return;

  if (IsContainer()) {
    View* child = child_at(0);
    gfx::Size size = child->GetPreferredSize();
    child->SetBounds(0, GetTopMargin(), size.width(), size.height());
  } else {
    // Child views are laid out right aligned and given the full height. To
    // right align start with the last view and progress to the first.
    int x = width() - (use_right_margin_ ? item_right_margin_ : 0);
    for (int i = child_count() - 1; i >= 0; --i) {
      View* child = child_at(i);
      if (icon_view_ == child)
        continue;
      if (radio_check_image_view_ == child)
        continue;
      if (submenu_arrow_image_view_ == child)
        continue;
      int width = child->GetPreferredSize().width();
      child->SetBounds(x - width, 0, width, height());
      x -= width + kChildXPadding;
    }
    // Position |icon_view|.
    const MenuConfig& config = MenuConfig::instance();
    if (icon_view_) {
      icon_view_->SizeToPreferredSize();
      gfx::Size size = icon_view_->GetPreferredSize();
      int x = config.item_left_margin + left_icon_margin_ +
              (icon_area_width_ - size.width()) / 2;
      if (config.icons_in_label || type_ == CHECKBOX || type_ == RADIO)
        x = label_start_;
      int y =
          (height() + GetTopMargin() - GetBottomMargin() - size.height()) / 2;
      icon_view_->SetPosition(gfx::Point(x, y));
    }

    if (radio_check_image_view_) {
      int x = config.item_left_margin + left_icon_margin_;
      int y =
          (height() + GetTopMargin() - GetBottomMargin() - kMenuCheckSize) / 2;
      radio_check_image_view_->SetBounds(x, y, kMenuCheckSize, kMenuCheckSize);
    }

    if (submenu_arrow_image_view_) {
      int x = this->width() - config.arrow_width - config.arrow_to_edge_padding;
      int y =
          (height() + GetTopMargin() - GetBottomMargin() - kSubmenuArrowSize) /
          2;
      submenu_arrow_image_view_->SetBounds(x, y, config.arrow_width,
                                           kSubmenuArrowSize);
    }
  }
}

void MenuItemView::SetMargins(int top_margin, int bottom_margin) {
  top_margin_ = top_margin;
  bottom_margin_ = bottom_margin;

  invalidate_dimensions();
}

MenuItemView::MenuItemView(MenuItemView* parent,
                           int command,
                           MenuItemView::Type type)
    : delegate_(NULL),
      controller_(NULL),
      canceled_(false),
      parent_menu_item_(parent),
      type_(type),
      selected_(false),
      command_(command),
      submenu_(NULL),
      has_mnemonics_(false),
      show_mnemonics_(false),
      has_icons_(false),
      icon_view_(NULL),
      top_margin_(-1),
      bottom_margin_(-1),
      left_icon_margin_(0),
      right_icon_margin_(0),
      requested_menu_position_(POSITION_BEST_FIT),
      actual_menu_position_(requested_menu_position_),
      use_right_margin_(true) {
  Init(parent, command, type, NULL);
}

MenuItemView::~MenuItemView() {
  delete submenu_;
  for (auto* item : removed_items_)
    delete item;
}

const char* MenuItemView::GetClassName() const {
  return kViewClassName;
}

// Calculates all sizes that we can from the OS.
//
// This is invoked prior to Running a menu.
void MenuItemView::UpdateMenuPartSizes() {
  const MenuConfig& config = MenuConfig::instance();

  item_right_margin_ = config.label_to_arrow_padding + config.arrow_width +
                       config.arrow_to_edge_padding;
  icon_area_width_ = config.check_width;
  if (has_icons_)
    icon_area_width_ = std::max(icon_area_width_, GetMaxIconViewWidth());

  label_start_ = config.item_left_margin + icon_area_width_;
  int padding = 0;
  if (config.always_use_icon_to_label_padding) {
    padding = config.icon_to_label_padding;
  } else if (!config.icons_in_label) {
    padding = (has_icons_ || HasChecksOrRadioButtons()) ?
        config.icon_to_label_padding : 0;
  }
  label_start_ += padding;

  EmptyMenuMenuItem menu_item(this);
  menu_item.set_controller(GetMenuController());
  pref_menu_height_ = menu_item.GetPreferredSize().height();
}

void MenuItemView::Init(MenuItemView* parent,
                        int command,
                        MenuItemView::Type type,
                        MenuDelegate* delegate) {
  delegate_ = delegate;
  controller_ = NULL;
  canceled_ = false;
  parent_menu_item_ = parent;
  type_ = type;
  selected_ = false;
  command_ = command;
  submenu_ = NULL;
  radio_check_image_view_ = nullptr;
  submenu_arrow_image_view_ = nullptr;
  show_mnemonics_ = false;
  // Assign our ID, this allows SubmenuItemView to find MenuItemViews.
  set_id(kMenuItemViewID);
  has_icons_ = false;

  if (type_ == CHECKBOX || type_ == RADIO) {
    radio_check_image_view_ = new ImageView();
    bool show_check_radio_icon =
        type_ == RADIO ||
        (type_ == CHECKBOX && GetDelegate()->IsItemChecked(GetCommand()));
    radio_check_image_view_->SetVisible(show_check_radio_icon);
    radio_check_image_view_->set_can_process_events_within_subtree(false);
    AddChildView(radio_check_image_view_);
  }

  if (submenu_arrow_image_view_)
    submenu_arrow_image_view_->SetVisible(HasSubmenu());

  // Don't request enabled status from the root menu item as it is just
  // a container for real items.  EMPTY items will be disabled.
  MenuDelegate* root_delegate = GetDelegate();
  if (parent && type != EMPTY && root_delegate)
    SetEnabled(root_delegate->IsCommandEnabled(command));
}

void MenuItemView::PrepareForRun(bool is_first_menu,
                                 bool has_mnemonics,
                                 bool show_mnemonics) {
  // Currently we only support showing the root.
  DCHECK(!parent_menu_item_);

  // Force us to have a submenu.
  CreateSubmenu();
  actual_menu_position_ = requested_menu_position_;
  canceled_ = false;

  has_mnemonics_ = has_mnemonics;
  show_mnemonics_ = has_mnemonics && show_mnemonics;

  AddEmptyMenus();

  if (is_first_menu) {
    // Only update the menu size if there are no menus showing, otherwise
    // things may shift around.
    UpdateMenuPartSizes();
  }
}

int MenuItemView::GetDrawStringFlags() {
  int flags = 0;
  if (base::i18n::IsRTL())
    flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
  else
    flags |= gfx::Canvas::TEXT_ALIGN_LEFT;

  if (GetRootMenuItem()->has_mnemonics_) {
    if (MenuConfig::instance().show_mnemonics ||
        GetRootMenuItem()->show_mnemonics_) {
      flags |= gfx::Canvas::SHOW_PREFIX;
    } else {
      flags |= gfx::Canvas::HIDE_PREFIX;
    }
  }
  return flags;
}

const gfx::FontList& MenuItemView::GetFontList() const {
  const MenuDelegate* delegate = GetDelegate();
  if (delegate) {
    const gfx::FontList* font_list = delegate->GetLabelFontList(GetCommand());
    if (font_list)
      return *font_list;
  }
  return MenuConfig::instance().font_list;
}

void MenuItemView::AddEmptyMenus() {
  DCHECK(HasSubmenu());
  if (!submenu_->HasVisibleChildren()) {
    submenu_->AddChildViewAt(new EmptyMenuMenuItem(this), 0);
  } else {
    for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count;
         ++i) {
      MenuItemView* child = submenu_->GetMenuItemAt(i);
      if (child->HasSubmenu())
        child->AddEmptyMenus();
    }
  }
}

void MenuItemView::RemoveEmptyMenus() {
  DCHECK(HasSubmenu());
  // Iterate backwards as we may end up removing views, which alters the child
  // view count.
  for (int i = submenu_->child_count() - 1; i >= 0; --i) {
    View* child = submenu_->child_at(i);
    if (child->id() == MenuItemView::kMenuItemViewID) {
      MenuItemView* menu_item = static_cast<MenuItemView*>(child);
      if (menu_item->HasSubmenu())
        menu_item->RemoveEmptyMenus();
    } else if (child->id() == EmptyMenuMenuItem::kEmptyMenuItemViewID) {
      submenu_->RemoveChildView(child);
      delete child;
      child = NULL;
    }
  }
}

void MenuItemView::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
  rect->set_x(GetMirroredXForRect(*rect));
}

void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
  const MenuConfig& config = MenuConfig::instance();
  bool render_selection =
      (mode == PB_NORMAL && IsSelected() &&
       parent_menu_item_->GetSubmenu()->GetShowSelection(this) &&
       (NonIconChildViewsCount() == 0));

  MenuDelegate *delegate = GetDelegate();
  bool emphasized =
      delegate && delegate->GetShouldUseNormalForegroundColor(GetCommand());
  // Render the background. As MenuScrollViewContainer draws the background, we
  // only need the background when we want it to look different, as when we're
  // selected.
  ui::NativeTheme* native_theme = GetNativeTheme();
  if (render_selection) {
    gfx::Rect item_bounds(0, 0, width(), height());
    AdjustBoundsForRTLUI(&item_bounds);

    native_theme->Paint(canvas->sk_canvas(),
                        ui::NativeTheme::kMenuItemBackground,
                        ui::NativeTheme::kHovered,
                        item_bounds,
                        ui::NativeTheme::ExtraParams());
  }

  const int top_margin = GetTopMargin();
  const int bottom_margin = GetBottomMargin();
  const int available_height = height() - top_margin - bottom_margin;

  // Calculate some colors.
  SkColor fg_color = GetTextColor(false, render_selection, emphasized);
  SkColor icon_color = color_utils::DeriveDefaultIconColor(fg_color);

  // Render the check.
  if (type_ == CHECKBOX && delegate->IsItemChecked(GetCommand())) {
    radio_check_image_view_->SetImage(GetMenuCheckImage(icon_color));
  } else if (type_ == RADIO) {
    radio_check_image_view_->SetImage(GetRadioButtonImage(
        delegate->IsItemChecked(GetCommand()), render_selection, icon_color));
  }

  // Render the foreground.
  const gfx::FontList& font_list = GetFontList();
  int accel_width = parent_menu_item_->GetSubmenu()->max_minor_text_width();
  int label_start = GetLabelStartForThisItem();

  int width = this->width() - label_start - accel_width -
      (!delegate ||
       delegate->ShouldReserveSpaceForSubmenuIndicator() ?
           item_right_margin_ : config.arrow_to_edge_padding);
  gfx::Rect text_bounds(label_start, top_margin, width,
                        subtitle_.empty() ? available_height
                                          : available_height / 2);
  text_bounds.set_x(GetMirroredXForRect(text_bounds));
  int flags = GetDrawStringFlags();
  if (mode == PB_FOR_DRAG)
    flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING;
  canvas->DrawStringRectWithFlags(title(), font_list, fg_color, text_bounds,
                                  flags);
  if (!subtitle_.empty()) {
    canvas->DrawStringRectWithFlags(
        subtitle_,
        font_list,
        GetNativeTheme()->GetSystemColor(
            ui::NativeTheme::kColorId_MenuItemSubtitleColor),
        text_bounds + gfx::Vector2d(0, font_list.GetHeight()),
        flags);
  }

  PaintMinorText(canvas, GetTextColor(true, render_selection, emphasized));

  // Set the submenu indicator (arrow) image and color.
  if (HasSubmenu())
    submenu_arrow_image_view_->SetImage(GetSubmenuArrowImage(icon_color));
}

void MenuItemView::PaintMinorText(gfx::Canvas* canvas, SkColor color) {
  base::string16 minor_text = GetMinorText();
  if (minor_text.empty())
    return;

  int available_height = height() - GetTopMargin() - GetBottomMargin();
  int max_accel_width =
      parent_menu_item_->GetSubmenu()->max_minor_text_width();
  const MenuConfig& config = MenuConfig::instance();
  int accel_right_margin = config.align_arrow_and_shortcut ?
                           config.arrow_to_edge_padding :  item_right_margin_;
  gfx::Rect accel_bounds(width() - accel_right_margin - max_accel_width,
                         GetTopMargin(), max_accel_width, available_height);
  accel_bounds.set_x(GetMirroredXForRect(accel_bounds));
  int flags = GetDrawStringFlags();
  flags &= ~(gfx::Canvas::TEXT_ALIGN_RIGHT | gfx::Canvas::TEXT_ALIGN_LEFT);
  if (base::i18n::IsRTL())
    flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
  else
    flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
  canvas->DrawStringRectWithFlags(minor_text, GetFontList(), color,
                                  accel_bounds, flags);
}

SkColor MenuItemView::GetTextColor(bool minor,
                                   bool render_selection,
                                   bool emphasized) const {
  ui::NativeTheme::ColorId color_id =
      minor ? ui::NativeTheme::kColorId_MenuItemSubtitleColor
            : ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor;
  if (enabled()) {
    if (render_selection)
      color_id = ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor;
  } else {
    if (!emphasized)
      color_id = ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor;
  }
  return GetNativeTheme()->GetSystemColor(color_id);
}

void MenuItemView::DestroyAllMenuHosts() {
  if (!HasSubmenu())
    return;

  submenu_->Close();
  for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count;
       ++i) {
    submenu_->GetMenuItemAt(i)->DestroyAllMenuHosts();
  }
}

int MenuItemView::GetTopMargin() const {
  if (top_margin_ >= 0)
    return top_margin_;

  const MenuItemView* root = GetRootMenuItem();
  return root && root->has_icons_
             ? MenuConfig::instance().item_top_margin
             : MenuConfig::instance().item_no_icon_top_margin;
}

int MenuItemView::GetBottomMargin() const {
  if (bottom_margin_ >= 0)
    return bottom_margin_;

  const MenuItemView* root = GetRootMenuItem();
  return root && root->has_icons_
             ? MenuConfig::instance().item_bottom_margin
             : MenuConfig::instance().item_no_icon_bottom_margin;
}

gfx::Size MenuItemView::GetChildPreferredSize() const {
  if (!has_children())
    return gfx::Size();

  if (IsContainer())
    return child_at(0)->GetPreferredSize();

  int width = 0;
  for (int i = 0; i < child_count(); ++i) {
    const View* child = child_at(i);
    if (icon_view_ == child)
      continue;
    if (radio_check_image_view_ == child)
      continue;
    if (submenu_arrow_image_view_ == child)
      continue;
    if (i)
      width += kChildXPadding;
    width += child->GetPreferredSize().width();
  }
  int height = 0;
  if (icon_view_)
    height = icon_view_->GetPreferredSize().height();

  // If there is no icon view it returns a height of 0 to indicate that
  // we should use the title height instead.
  return gfx::Size(width, height);
}

MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const {
  gfx::Size child_size = GetChildPreferredSize();

  MenuItemDimensions dimensions;
  // Get the container height.
  dimensions.children_width = child_size.width();
  dimensions.height = child_size.height();
  // Adjust item content height if menu has both items with and without icons.
  // This way all menu items will have the same height.
  if (!icon_view_ && GetRootMenuItem()->has_icons()) {
    dimensions.height =
        std::max(dimensions.height, MenuConfig::instance().check_height);
  }
  dimensions.height += GetBottomMargin() + GetTopMargin();

  // In case of a container, only the container size needs to be filled.
  if (IsContainer())
    return dimensions;

  // Determine the length of the label text.
  const gfx::FontList& font_list = GetFontList();

  // Get Icon margin overrides for this particular item.
  const MenuDelegate* delegate = GetDelegate();
  if (delegate) {
    delegate->GetHorizontalIconMargins(command_,
                                       icon_area_width_,
                                       &left_icon_margin_,
                                       &right_icon_margin_);
  } else {
    left_icon_margin_ = 0;
    right_icon_margin_ = 0;
  }
  int label_start = GetLabelStartForThisItem();

  int string_width = gfx::GetStringWidth(title_, font_list);
  if (!subtitle_.empty()) {
    string_width = std::max(string_width,
                            gfx::GetStringWidth(subtitle_, font_list));
  }

  dimensions.standard_width = string_width + label_start +
      item_right_margin_;
  // Determine the length of the right-side text.
  base::string16 minor_text = GetMinorText();
  dimensions.minor_text_width =
      minor_text.empty() ? 0 : gfx::GetStringWidth(minor_text, font_list);

  // Determine the height to use.
  dimensions.height =
      std::max(dimensions.height,
               (subtitle_.empty() ? 0 : font_list.GetHeight()) +
               font_list.GetHeight() + GetBottomMargin() + GetTopMargin());
  dimensions.height =
      std::max(dimensions.height, MenuConfig::instance().item_min_height);
  return dimensions;
}

int MenuItemView::GetLabelStartForThisItem() const {
  const MenuConfig& config = MenuConfig::instance();
  int label_start = label_start_ + left_icon_margin_ + right_icon_margin_;
  if ((config.icons_in_label || type_ == CHECKBOX || type_ == RADIO) &&
      icon_view_)
    label_start += icon_view_->size().width() + config.icon_to_label_padding;

  return label_start;
}

base::string16 MenuItemView::GetMinorText() const {
  if (id() == kEmptyMenuItemViewID) {
    // Don't query the delegate for menus that represent no children.
    return base::string16();
  }

  ui::Accelerator accelerator;
  if (MenuConfig::instance().show_accelerators && GetDelegate() &&
      GetCommand() &&
      GetDelegate()->GetAccelerator(GetCommand(), &accelerator)) {
    return accelerator.GetShortcutText();
  }

  return minor_text_;
}

bool MenuItemView::IsContainer() const {
  // Let the first child take over |this| when we only have one child and no
  // title.
  return (NonIconChildViewsCount() == 1) && title_.empty();
}

int MenuItemView::NonIconChildViewsCount() const {
  // Note that what child_count() returns is the number of children,
  // not the number of menu items.
  return child_count() - (icon_view_ ? 1 : 0) -
         (radio_check_image_view_ ? 1 : 0) -
         (submenu_arrow_image_view_ ? 1 : 0);
}

int MenuItemView::GetMaxIconViewWidth() const {
  int width = 0;
  for (int i = 0; i < submenu_->GetMenuItemCount(); ++i) {
    MenuItemView* menu_item = submenu_->GetMenuItemAt(i);
    int temp_width = 0;
    if (menu_item->GetType() == CHECKBOX ||
        menu_item->GetType() == RADIO) {
      // If this item has a radio or checkbox, the icon will not affect
      // alignment of other items.
      continue;
    } else if (menu_item->HasSubmenu()) {
      temp_width = menu_item->GetMaxIconViewWidth();
    } else if (menu_item->icon_view() &&
               !MenuConfig::instance().icons_in_label) {
      temp_width = menu_item->icon_view()->GetPreferredSize().width();
    }
    width = std::max(width, temp_width);
  }
  return width;
}

bool MenuItemView::HasChecksOrRadioButtons() const {
  for (int i = 0; i < submenu_->GetMenuItemCount(); ++i) {
    MenuItemView* menu_item = submenu_->GetMenuItemAt(i);
    if (menu_item->HasSubmenu()) {
      if (menu_item->HasChecksOrRadioButtons())
        return true;
    } else {
      const Type& type = menu_item->GetType();
      if (type == CHECKBOX || type == RADIO)
        return true;
    }
  }
  return false;
}

}  // namespace views
