Add MenuModelAdapter to wrap ui::MenuModel with views::MenuDelegate interface.

- added ViewsDelegate::GetDispositionForEvent() pure virtual
- added MenuModelAdapter
- added unit test for MenuModelAdapter
- added comment for MenuModel::GetFirstItemIndex() that callers may pass NULL for NativeMenu argument
- fixed index offset bug in MenuModel::GetModelAndIndexForCommandId()
- fixed spurious call to MenuDelegate::IsCommandEnabled()

BUG=none
TEST=included


Review URL: http://codereview.chromium.org/7067032

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@86677 0039d316-1c4b-4281-b951-d872f2087c98
parent 4e5ec621
......@@ -65,6 +65,10 @@ class AccessibilityViewsDelegate : public views::ViewsDelegate {
virtual void AddRef() {}
virtual void ReleaseRef() {}
virtual int GetDispositionForEvent(int event_flags) OVERRIDE {
return 0;
}
DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
};
......
......@@ -101,6 +101,10 @@ class ViewsDelegateImpl : public views::ViewsDelegate {
MessageLoopForUI::current()->Quit();
}
virtual int GetDispositionForEvent(int event_flags) OVERRIDE {
return 0;
}
private:
DISALLOW_COPY_AND_ASSIGN(ViewsDelegateImpl);
};
......
......@@ -12,6 +12,7 @@
#include "chrome/browser/prefs/scoped_user_pref_update.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/views/accessibility_event_router_views.h"
#include "chrome/browser/ui/views/event_utils.h"
#include "chrome/browser/ui/window_sizer.h"
#include "chrome/common/pref_names.h"
#include "ui/base/clipboard/clipboard.h"
......@@ -145,3 +146,7 @@ void ChromeViewsDelegate::AddRef() {
void ChromeViewsDelegate::ReleaseRef() {
g_browser_process->ReleaseModule();
}
int ChromeViewsDelegate::GetDispositionForEvent(int event_flags) {
return event_utils::DispositionFromEventFlags(event_flags);
}
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Copyright (c) 2011 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.
......@@ -7,6 +7,7 @@
#pragma once
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "build/build_config.h"
#include "ui/base/accessibility/accessibility_types.h"
#include "views/views_delegate.h"
......@@ -21,31 +22,33 @@ class ChromeViewsDelegate : public views::ViewsDelegate {
virtual ~ChromeViewsDelegate() {}
// Overridden from views::ViewsDelegate:
virtual ui::Clipboard* GetClipboard() const;
virtual ui::Clipboard* GetClipboard() const OVERRIDE;
virtual void SaveWindowPlacement(views::Window* window,
const std::wstring& window_name,
const gfx::Rect& bounds,
bool maximized);
bool maximized) OVERRIDE;
virtual bool GetSavedWindowBounds(views::Window* window,
const std::wstring& window_name,
gfx::Rect* bounds) const;
gfx::Rect* bounds) const OVERRIDE;
virtual bool GetSavedMaximizedState(views::Window* window,
const std::wstring& window_name,
bool* maximized) const;
bool* maximized) const OVERRIDE;
virtual void NotifyAccessibilityEvent(
views::View* view, ui::AccessibilityTypes::Event event_type);
views::View* view, ui::AccessibilityTypes::Event event_type) OVERRIDE;
virtual void NotifyMenuItemFocused(
const std::wstring& menu_name,
const std::wstring& menu_item_name,
int item_index,
int item_count,
bool has_submenu);
bool has_submenu) OVERRIDE;
#if defined(OS_WIN)
virtual HICON GetDefaultWindowIcon() const;
virtual HICON GetDefaultWindowIcon() const OVERRIDE;
#endif
virtual void AddRef();
virtual void ReleaseRef();
virtual void AddRef() OVERRIDE;
virtual void ReleaseRef() OVERRIDE;
virtual int GetDispositionForEvent(int event_flags) OVERRIDE;
private:
DISALLOW_COPY_AND_ASSIGN(ChromeViewsDelegate);
......
......@@ -16,17 +16,19 @@ bool MenuModel::IsVisibleAt(int index) const {
bool MenuModel::GetModelAndIndexForCommandId(int command_id,
MenuModel** model, int* index) {
int item_count = (*model)->GetItemCount();
const int item_count = (*model)->GetItemCount();
const int index_offset = (*model)->GetFirstItemIndex(NULL);
for (int i = 0; i < item_count; ++i) {
if ((*model)->GetTypeAt(i) == TYPE_SUBMENU) {
MenuModel* submenu_model = (*model)->GetSubmenuModelAt(i);
const int candidate_index = i + index_offset;
if ((*model)->GetTypeAt(candidate_index) == TYPE_SUBMENU) {
MenuModel* submenu_model = (*model)->GetSubmenuModelAt(candidate_index);
if (GetModelAndIndexForCommandId(command_id, &submenu_model, index)) {
*model = submenu_model;
return true;
}
}
if ((*model)->GetCommandIdAt(i) == command_id) {
*index = i;
if ((*model)->GetCommandIdAt(candidate_index) == command_id) {
*index = candidate_index;
return true;
}
}
......
......@@ -44,7 +44,8 @@ class MenuModel {
// Returns the index of the first item. This is 0 for most menus except the
// system menu on Windows. |native_menu| is the menu to locate the start index
// within. It is guaranteed to be reset to a clean default state.
// within. It is guaranteed to be reset to a clean default state. Some
// callers of this method may pass NULL for native_menu.
// IMPORTANT: If the model implementation returns something _other_ than 0
// here, it must offset the values for |index| it passes to the
// methods below by this number - this is NOT done automatically!
......
......@@ -562,8 +562,10 @@ void MenuItemView::Init(MenuItemView* parent,
SetID(kMenuItemViewID);
has_icons_ = false;
// Don't request enabled status from the root menu item as it is just
// a container for real items.
MenuDelegate* root_delegate = GetDelegate();
if (root_delegate)
if (parent && root_delegate)
SetEnabled(root_delegate->IsCommandEnabled(command));
}
......
// Copyright (c) 2011 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 "views/controls/menu/menu_model_adapter.h"
#include "base/logging.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/menu_model.h"
#include "views/controls/menu/submenu_view.h"
#include "views/views_delegate.h"
namespace views {
MenuModelAdapter::MenuModelAdapter(ui::MenuModel* menu_model)
: menu_model_(menu_model) {
DCHECK(menu_model);
}
void MenuModelAdapter::BuildMenu(MenuItemView* menu) {
DCHECK(menu);
// Clear the menu.
if (menu->HasSubmenu()) {
const int subitem_count = menu->GetSubmenu()->child_count();
for (int i = 0; i < subitem_count; ++i)
menu->RemoveMenuItemAt(0);
}
menu_map_.clear();
menu_map_[menu] = menu_model_;
// Repopulate the menu.
BuildMenuImpl(menu, menu_model_);
menu->ChildrenChanged();
}
// MenuModelAdapter, MenuDelegate implementation:
void MenuModelAdapter::ExecuteCommand(int id) {
ui::MenuModel* model = menu_model_;
int index = 0;
if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
model->ActivatedAt(index);
return;
}
NOTREACHED();
}
void MenuModelAdapter::ExecuteCommand(int id, int mouse_event_flags) {
ui::MenuModel* model = menu_model_;
int index = 0;
if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
const int disposition =
ViewsDelegate::views_delegate->GetDispositionForEvent(
mouse_event_flags);
model->ActivatedAtWithDisposition(index, disposition);
return;
}
NOTREACHED();
}
bool MenuModelAdapter::GetAccelerator(int id,
views::Accelerator* accelerator) {
ui::MenuModel* model = menu_model_;
int index = 0;
if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
return model->GetAcceleratorAt(index, accelerator);
NOTREACHED();
return false;
}
std::wstring MenuModelAdapter::GetLabel(int id) const {
ui::MenuModel* model = menu_model_;
int index = 0;
if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
return UTF16ToWide(model->GetLabelAt(index));
NOTREACHED();
return std::wstring();
}
const gfx::Font& MenuModelAdapter::GetLabelFont(int id) const {
ui::MenuModel* model = menu_model_;
int index = 0;
if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
const gfx::Font* font = model->GetLabelFontAt(index);
return font ? *font : MenuDelegate::GetLabelFont(id);
}
NOTREACHED();
return MenuDelegate::GetLabelFont(id);
}
bool MenuModelAdapter::IsCommandEnabled(int id) const {
ui::MenuModel* model = menu_model_;
int index = 0;
if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
return model->IsEnabledAt(index);
NOTREACHED();
return false;
}
bool MenuModelAdapter::IsItemChecked(int id) const {
ui::MenuModel* model = menu_model_;
int index = 0;
if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
return model->IsItemCheckedAt(index);
NOTREACHED();
return false;
}
void MenuModelAdapter::SelectionChanged(MenuItemView* menu) {
const int id = menu->GetCommand();
ui::MenuModel* model = menu_model_;
int index = 0;
if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
model->HighlightChangedTo(index);
return;
}
NOTREACHED();
}
void MenuModelAdapter::WillShowMenu(MenuItemView* menu) {
// Look up the menu model for this menu.
const std::map<MenuItemView*, ui::MenuModel*>::const_iterator map_iterator =
menu_map_.find(menu);
if (map_iterator != menu_map_.end()) {
map_iterator->second->MenuWillShow();
return;
}
NOTREACHED();
}
// MenuModelAdapter, private:
void MenuModelAdapter::BuildMenuImpl(MenuItemView* menu, ui::MenuModel* model) {
DCHECK(menu);
DCHECK(model);
const int item_count = model->GetItemCount();
for (int i = 0; i < item_count; ++i) {
const int index = i + model->GetFirstItemIndex(NULL);
MenuItemView* item = menu->AppendMenuItemFromModel(
model, index, model->GetCommandIdAt(index));
if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SUBMENU) {
DCHECK(item);
DCHECK_EQ(MenuItemView::SUBMENU, item->GetType());
ui::MenuModel* submodel = model->GetSubmenuModelAt(index);
DCHECK(submodel);
BuildMenuImpl(item, submodel);
menu_map_[item] = submodel;
}
}
menu->set_has_icons(model->HasIcons());
}
} // views
// Copyright (c) 2011 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.
#ifndef VIEWS_CONTROLS_MENU_MENU_MODEL_ADAPTER_H_
#define VIEWS_CONTROLS_MENU_MENU_MODEL_ADAPTER_H_
#pragma once
#include <map>
#include "views/controls/menu/menu_delegate.h"
namespace ui {
class MenuModel;
}
namespace views {
class MenuItemView;
// This class wraps an instance of ui::MenuModel with the
// views::MenuDelegate interface required by views::MenuItemView.
class MenuModelAdapter : public MenuDelegate {
public:
// The caller retains ownership of the ui::MenuModel instance and
// must ensure it exists for the lifetime of the adapter. The
// base_id argument is the command id for the first menu item.
explicit MenuModelAdapter(ui::MenuModel* menu_model);
// Populate a MenuItemView menu with the ui::MenuModel items
// (including submenus).
virtual void BuildMenu(MenuItemView* menu);
protected:
// views::MenuDelegate implementation.
virtual void ExecuteCommand(int id) OVERRIDE;
virtual void ExecuteCommand(int id, int mouse_event_flags) OVERRIDE;
virtual bool GetAccelerator(int id,
views::Accelerator* accelerator) OVERRIDE;
virtual std::wstring GetLabel(int id) const OVERRIDE;
virtual const gfx::Font& GetLabelFont(int id) const OVERRIDE;
virtual bool IsCommandEnabled(int id) const OVERRIDE;
virtual bool IsItemChecked(int id) const OVERRIDE;
virtual void SelectionChanged(MenuItemView* menu) OVERRIDE;
virtual void WillShowMenu(MenuItemView* menu) OVERRIDE;
private:
// Implementation of BuildMenu(). index_offset is both input and output;
// on input it contains the offset from index to command id for the model,
// and on output it contains the offset for the next model.
void BuildMenuImpl(MenuItemView* menu, ui::MenuModel* model);
// Container of ui::MenuModel pointers as encountered by preorder
// traversal. The first element is always the top-level model
// passed to the constructor.
ui::MenuModel* menu_model_;
// Map MenuItems to MenuModels. Used to implement WillShowMenu().
std::map<MenuItemView*, ui::MenuModel*> menu_map_;
DISALLOW_COPY_AND_ASSIGN(MenuModelAdapter);
};
} // namespace views
#endif // VIEWS_CONTROLS_MENU_MENU_MODEL_ADAPTER_H_
// Copyright (c) 2011 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/base/l10n/l10n_util.h"
#include "ui/base/models/menu_model.h"
#include "ui/base/models/menu_model_delegate.h"
#include "views/controls/menu/menu_item_view.h"
#include "views/controls/menu/menu_model_adapter.h"
#include "views/controls/menu/submenu_view.h"
#include "views/test/views_test_base.h"
namespace {
// Base command id for test menu and its submenu.
const int kRootIdBase = 100;
const int kSubmenuIdBase = 200;
// Offset to return for GetFirstItemIndex(). This is an arbitrary
// number to ensure that we aren't assuming it is 0.
const int kFirstItemIndex = 25;
class MenuModelBase : public ui::MenuModel {
public:
MenuModelBase(int command_id_base) : command_id_base_(command_id_base),
last_activation_(-1) {
}
// ui::MenuModel implementation:
virtual bool HasIcons() const OVERRIDE {
return false;
}
virtual int GetFirstItemIndex(gfx::NativeMenu native_menu) const OVERRIDE {
return kFirstItemIndex;
}
virtual int GetItemCount() const OVERRIDE {
return static_cast<int>(items_.size());
}
virtual ItemType GetTypeAt(int index) const OVERRIDE {
return items_[index - GetFirstItemIndex(NULL)].type;
}
virtual int GetCommandIdAt(int index) const OVERRIDE {
return index - GetFirstItemIndex(NULL) + command_id_base_;
}
string16 GetLabelAt(int index) const OVERRIDE {
return items_[index - GetFirstItemIndex(NULL)].label;
}
virtual bool IsItemDynamicAt(int index) const OVERRIDE {
return false;
}
virtual const gfx::Font* GetLabelFontAt(int index) const OVERRIDE {
return NULL;
}
virtual bool GetAcceleratorAt(int index,
ui::Accelerator* accelerator) const OVERRIDE {
return false;
}
virtual bool IsItemCheckedAt(int index) const OVERRIDE {
return false;
}
virtual int GetGroupIdAt(int index) const OVERRIDE {
return 0;
}
virtual bool GetIconAt(int index, SkBitmap* icon) OVERRIDE {
return false;
}
virtual ui::ButtonMenuItemModel* GetButtonMenuItemAt(
int index) const OVERRIDE {
return NULL;
}
virtual bool IsEnabledAt(int index) const OVERRIDE {
return true;
}
virtual bool IsVisibleAt(int index) const OVERRIDE {
return true;
}
virtual MenuModel* GetSubmenuModelAt(int index) const OVERRIDE {
return items_[index - GetFirstItemIndex(NULL)].submenu;
}
virtual void HighlightChangedTo(int index) OVERRIDE {
}
virtual void ActivatedAt(int index) OVERRIDE {
set_last_activation(index);
}
virtual void ActivatedAtWithDisposition(int index, int disposition) OVERRIDE {
ActivatedAt(index);
}
virtual void MenuWillShow() OVERRIDE {
}
virtual void MenuClosed() OVERRIDE {
}
virtual void SetMenuModelDelegate(
ui::MenuModelDelegate* delegate) OVERRIDE {
}
// Item definition.
struct Item {
Item(ItemType item_type,
const std::string& item_label,
ui::MenuModel* item_submenu)
: type(item_type),
label(ASCIIToUTF16(item_label)),
submenu(item_submenu) {
}
ItemType type;
string16 label;
ui::MenuModel* submenu;
};
const Item& GetItemDefinition(int index) {
return items_[index];
}
// Access index argument to ActivatedAt() or ActivatedAtWithDisposition().
int last_activation() const { return last_activation_; }
void set_last_activation(int last_activation) {
last_activation_ = last_activation;
}
protected:
std::vector<Item> items_;
private:
int command_id_base_;
int last_activation_;
DISALLOW_COPY_AND_ASSIGN(MenuModelBase);
};
class SubmenuModel : public MenuModelBase {
public:
SubmenuModel() : MenuModelBase(kSubmenuIdBase) {
items_.push_back(Item(TYPE_COMMAND, "submenu item 0", NULL));
items_.push_back(Item(TYPE_COMMAND, "submenu item 1", NULL));
}
private:
DISALLOW_COPY_AND_ASSIGN(SubmenuModel);
};
class RootModel : public MenuModelBase {
public:
RootModel() : MenuModelBase(kRootIdBase) {
submenu_model_.reset(new SubmenuModel);
items_.push_back(Item(TYPE_COMMAND, "command 0", NULL));
items_.push_back(Item(TYPE_CHECK, "check 1", NULL));
items_.push_back(Item(TYPE_SEPARATOR, "", NULL));
items_.push_back(Item(TYPE_SUBMENU, "submenu 3", submenu_model_.get()));
items_.push_back(Item(TYPE_RADIO, "radio 4", NULL));
}
private:
scoped_ptr<MenuModel> submenu_model_;
DISALLOW_COPY_AND_ASSIGN(RootModel);
};
} // namespace
namespace views {
typedef ViewsTestBase MenuModelAdapterTest;
TEST_F(MenuModelAdapterTest, BasicTest) {
// Build model and adapter.
RootModel model;
views::MenuModelAdapter delegate(&model);
// Create menu. Build menu twice to check that rebuilding works properly.
scoped_ptr<views::MenuItemView> menu(new views::MenuItemView(&delegate));
delegate.BuildMenu(menu.get());
delegate.BuildMenu(menu.get());
EXPECT_TRUE(menu->HasSubmenu());
// Check top level menu items.
views::SubmenuView* item_container = menu->GetSubmenu();
EXPECT_EQ(5, item_container->child_count());
for (int i = 0; i < item_container->child_count(); ++i) {
const MenuModelBase::Item& model_item = model.GetItemDefinition(i);
const int id = i + kRootIdBase;
MenuItemView* item = menu->GetMenuItemByID(id);
if (!item) {
EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type);
continue;
}
// Check placement.
EXPECT_EQ(i, menu->GetSubmenu()->GetIndexOf(item));
// Check type.
switch (model_item.type) {
case ui::MenuModel::TYPE_COMMAND:
EXPECT_EQ(views::MenuItemView::NORMAL, item->GetType());
break;
case ui::MenuModel::TYPE_CHECK:
EXPECT_EQ(views::MenuItemView::CHECKBOX, item->GetType());
break;
case ui::MenuModel::TYPE_RADIO:
EXPECT_EQ(views::MenuItemView::RADIO, item->GetType());
break;
case ui::MenuModel::TYPE_SEPARATOR:
case ui::MenuModel::TYPE_BUTTON_ITEM:
break;
case ui::MenuModel::TYPE_SUBMENU:
EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType());
break;
}
// Check activation.
static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id);
EXPECT_EQ(i + kFirstItemIndex, model.last_activation());
model.set_last_activation(-1);
}
// Check submenu items.
views::MenuItemView* submenu = menu->GetMenuItemByID(103);
views::SubmenuView* subitem_container = submenu->GetSubmenu();
EXPECT_EQ(2, subitem_container->child_count());
for (int i = 0; i < subitem_container->child_count(); ++i) {
MenuModelBase* submodel = static_cast<MenuModelBase*>(
model.GetSubmenuModelAt(3 + kFirstItemIndex));
EXPECT_TRUE(submodel);
const MenuModelBase::Item& model_item = submodel->GetItemDefinition(i);
const int id = i + kSubmenuIdBase;
MenuItemView* item = menu->GetMenuItemByID(id);
if (!item) {
EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type);
continue;
}
// Check placement.
EXPECT_EQ(i, submenu->GetSubmenu()->GetIndexOf(item));
// Check type.
switch (model_item.type) {
case ui::MenuModel::TYPE_COMMAND:
EXPECT_EQ(views::MenuItemView::NORMAL, item->GetType());
break;
case ui::MenuModel::TYPE_CHECK:
EXPECT_EQ(views::MenuItemView::CHECKBOX, item->GetType());
break;
case ui::MenuModel::TYPE_RADIO:
EXPECT_EQ(views::MenuItemView::RADIO, item->GetType());
break;
case ui::MenuModel::TYPE_SEPARATOR:
case ui::MenuModel::TYPE_BUTTON_ITEM:
break;
case ui::MenuModel::TYPE_SUBMENU:
EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType());
break;
}
// Check activation.
static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id);
EXPECT_EQ(i + kFirstItemIndex, submodel->last_activation());
submodel->set_last_activation(-1);
}
}
} // namespace views
......@@ -51,6 +51,10 @@ class TestViewsDelegate : public views::ViewsDelegate {
virtual void AddRef() OVERRIDE {}
virtual void ReleaseRef() OVERRIDE {}
virtual int GetDispositionForEvent(int event_flags) OVERRIDE {
return 0;
}
private:
mutable scoped_ptr<ui::Clipboard> clipboard_;
......
......@@ -151,6 +151,8 @@
'controls/menu/menu_item_view.h',
'controls/menu/menu_item_view_gtk.cc',
'controls/menu/menu_item_view_win.cc',
'controls/menu/menu_model_adapter.cc',
'controls/menu/menu_model_adapter.h',
'controls/menu/menu_scroll_view_container.cc',
'controls/menu/menu_scroll_view_container.h',
'controls/menu/menu_separator.h',
......@@ -483,6 +485,7 @@
'controls/tabbed_pane/tabbed_pane_unittest.cc',
'controls/table/table_view_unittest.cc',
'controls/combobox/native_combobox_views_unittest.cc',
'controls/menu/menu_model_adapter_unittest.cc',
'controls/textfield/native_textfield_views_unittest.cc',
'controls/textfield/textfield_views_model_unittest.cc',
'events/event_unittest.cc',
......
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Copyright (c) 2011 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.
......@@ -80,6 +80,9 @@ class ViewsDelegate {
virtual void AddRef() = 0;
virtual void ReleaseRef() = 0;
// Converts views::Event::flags to a WindowOpenDisposition.
virtual int GetDispositionForEvent(int event_flags) = 0;
// The active ViewsDelegate used by the views system.
static ViewsDelegate* views_delegate;
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment