| Index: views/controls/menu/native_menu_gtk.cc
|
| ===================================================================
|
| --- views/controls/menu/native_menu_gtk.cc (revision 0)
|
| +++ views/controls/menu/native_menu_gtk.cc (revision 0)
|
| @@ -0,0 +1,233 @@
|
| +// Copyright (c) 2009 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/native_menu_gtk.h"
|
| +
|
| +#include "base/string_util.h"
|
| +#include "base/time.h"
|
| +#include "views/accelerator.h"
|
| +#include "views/controls/menu/menu_2.h"
|
| +
|
| +namespace {
|
| +// Data passed to the UpdateStateCallback from gtk_container_foreach.
|
| +struct UpdateStateData {
|
| + // The model to retrieve state from.
|
| + views::Menu2Model* model;
|
| + // The index within said model.
|
| + int index;
|
| +};
|
| +
|
| +// Data passed to the MenuPositionFunc from gtk_menu_popup
|
| +struct Position {
|
| + // The point to run the menu at.
|
| + gfx::Point point;
|
| + // The alignment of the menu at that point.
|
| + views::Menu2::Alignment alignment;
|
| +};
|
| +
|
| +std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label) {
|
| + std::string ret;
|
| + ret.reserve(label.length());
|
| + for (size_t i = 0; i < label.length(); ++i) {
|
| + if ('&' == label[i]) {
|
| + if (i + 1 < label.length() && '&' == label[i + 1]) {
|
| + ret.push_back(label[i]);
|
| + ++i;
|
| + } else {
|
| + ret.push_back('_');
|
| + }
|
| + } else {
|
| + ret.push_back(label[i]);
|
| + }
|
| + }
|
| +
|
| + return ret;
|
| +}
|
| +
|
| +// Returns true if the menu item type specified can be executed as a command.
|
| +bool MenuTypeCanExecute(views::Menu2Model::ItemType type) {
|
| + return type == views::Menu2Model::TYPE_COMMAND ||
|
| + type == views::Menu2Model::TYPE_CHECK ||
|
| + type == views::Menu2Model::TYPE_RADIO;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +namespace views {
|
| +
|
| +////////////////////////////////////////////////////////////////////////////////
|
| +// NativeMenuGtk, public:
|
| +
|
| +NativeMenuGtk::NativeMenuGtk(Menu2Model* model,
|
| + Menu2Delegate* delegate)
|
| + : model_(model),
|
| + delegate_(delegate),
|
| + menu_(NULL) {
|
| +}
|
| +
|
| +NativeMenuGtk::~NativeMenuGtk() {
|
| + gtk_widget_destroy(menu_);
|
| +}
|
| +
|
| +////////////////////////////////////////////////////////////////////////////////
|
| +// NativeMenuGtk, MenuWrapper implementation:
|
| +
|
| +void NativeMenuGtk::RunMenuAt(const gfx::Point& point, int alignment) {
|
| + Position position = { point, static_cast<Menu2::Alignment>(alignment) };
|
| + // TODO(beng): value of '1' will not work for context menus!
|
| + gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, MenuPositionFunc, &position, 1,
|
| + gtk_get_current_event_time());
|
| +}
|
| +
|
| +void NativeMenuGtk::Rebuild() {
|
| + ResetMenu();
|
| +
|
| + GtkRadioMenuItem* last_radio_item = NULL;
|
| + for (int i = 0; i < model_->GetItemCount(); ++i) {
|
| + Menu2Model::ItemType type = model_->GetTypeAt(i);
|
| + if (type == Menu2Model::TYPE_SEPARATOR)
|
| + AddSeparatorAt(i);
|
| + else
|
| + AddMenuItemAt(i, &last_radio_item);
|
| + }
|
| +}
|
| +
|
| +void NativeMenuGtk::UpdateStates() {
|
| + UpdateStateData data = { model_, 0 };
|
| + gtk_container_foreach(GTK_CONTAINER(menu_), &UpdateStateCallback, &data);
|
| +}
|
| +
|
| +gfx::NativeMenu NativeMenuGtk::GetNativeMenu() const {
|
| + return menu_;
|
| +}
|
| +
|
| +////////////////////////////////////////////////////////////////////////////////
|
| +// NativeMenuGtk, private:
|
| +
|
| +void NativeMenuGtk::AddSeparatorAt(int index) {
|
| + GtkWidget* separator = gtk_separator_menu_item_new();
|
| + gtk_widget_show(separator);
|
| + gtk_menu_append(menu_, separator);
|
| +}
|
| +
|
| +void NativeMenuGtk::AddMenuItemAt(int index,
|
| + GtkRadioMenuItem** last_radio_item) {
|
| + GtkWidget* menu_item = NULL;
|
| + std::string label = ConvertAcceleratorsFromWindowsStyle(WideToUTF8(
|
| + model_->GetLabelAt(index)));
|
| +
|
| + Menu2Model::ItemType type = model_->GetTypeAt(index);
|
| + switch (type) {
|
| + case Menu2Model::TYPE_CHECK:
|
| + menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str());
|
| + break;
|
| + case Menu2Model::TYPE_RADIO:
|
| + if (*last_radio_item) {
|
| + menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget(
|
| + *last_radio_item, label.c_str());
|
| + } else {
|
| + menu_item = gtk_radio_menu_item_new_with_mnemonic(NULL, label.c_str());
|
| + }
|
| + break;
|
| + case Menu2Model::TYPE_SUBMENU:
|
| + case Menu2Model::TYPE_COMMAND:
|
| + menu_item = gtk_menu_item_new_with_mnemonic(label.c_str());
|
| + break;
|
| + default:
|
| + NOTREACHED();
|
| + break;
|
| + }
|
| +
|
| + // TODO(beng): icons
|
| +
|
| + if (type == Menu2Model::TYPE_SUBMENU) {
|
| + // TODO(beng): we're leaking these objects right now... consider some other
|
| + // arrangement.
|
| + Menu2* submenu = new Menu2(model_->GetSubmenuModelAt(index), delegate_);
|
| + g_object_set_data(G_OBJECT(menu_item), "submenu", submenu);
|
| + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
|
| + submenu->GetNativeMenu());
|
| + }
|
| +
|
| + views::Accelerator accelerator(0, false, false, false);
|
| + if (model_->GetAcceleratorAt(index, &accelerator)) {
|
| + // TODO(beng): accelerators w/gtk_widget_add_accelerator.
|
| + }
|
| + g_object_set_data(G_OBJECT(menu_item), "position",
|
| + reinterpret_cast<void*>(index));
|
| + g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(CallActivate),
|
| + this);
|
| + gtk_widget_show(menu_item);
|
| + gtk_menu_append(menu_, menu_item);
|
| +}
|
| +
|
| +// static
|
| +void NativeMenuGtk::UpdateStateCallback(GtkWidget* menu_item, gpointer data) {
|
| + UpdateStateData* usd = reinterpret_cast<UpdateStateData*>(data);
|
| + gtk_widget_set_sensitive(menu_item, usd->model->IsEnabledAt(usd->index));
|
| + if (GTK_IS_CHECK_MENU_ITEM(menu_item)) {
|
| + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item),
|
| + usd->model->IsItemCheckedAt(usd->index));
|
| + }
|
| + // Recurse into submenus, too.
|
| + if (GTK_IS_MENU_ITEM(menu_item)) {
|
| + if (gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item))) {
|
| + Menu2* submenu =
|
| + reinterpret_cast<Menu2*>(g_object_get_data(G_OBJECT(menu_item),
|
| + "submenu"));
|
| + if (submenu)
|
| + submenu->UpdateStates();
|
| + }
|
| + }
|
| + ++usd->index;
|
| +}
|
| +
|
| +void NativeMenuGtk::ResetMenu() {
|
| + if (menu_)
|
| + gtk_widget_destroy(menu_);
|
| + menu_ = gtk_menu_new();
|
| +}
|
| +
|
| +// static
|
| +void NativeMenuGtk::MenuPositionFunc(GtkMenu* menu,
|
| + int* x,
|
| + int* y,
|
| + gboolean* push_in,
|
| + void* data) {
|
| + Position* position = reinterpret_cast<Position*>(data);
|
| + // TODO(beng): RTL
|
| + *x = position->point.x();
|
| + *y = position->point.y();
|
| + if (position->alignment == Menu2::ALIGN_TOPRIGHT) {
|
| + GtkRequisition menu_req;
|
| + gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
|
| + *x -= menu_req.width;
|
| + }
|
| + *push_in = FALSE;
|
| +}
|
| +
|
| +void NativeMenuGtk::OnActivate(GtkMenuItem* menu_item) {
|
| + int position = reinterpret_cast<int>(g_object_get_data(G_OBJECT(menu_item),
|
| + "position"));
|
| + if (model_->IsEnabledAt(position) &&
|
| + MenuTypeCanExecute(model_->GetTypeAt(position))) {
|
| + delegate_->ExecuteCommand(model_, model_->GetCommandIdAt(position));
|
| + }
|
| +}
|
| +
|
| +// static
|
| +void NativeMenuGtk::CallActivate(GtkMenuItem* menu_item,
|
| + NativeMenuGtk* native_menu) {
|
| + native_menu->OnActivate(menu_item);
|
| +}
|
| +
|
| +////////////////////////////////////////////////////////////////////////////////
|
| +// MenuWrapper, public:
|
| +
|
| +// static
|
| +MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) {
|
| + return new NativeMenuGtk(menu->model(), menu->delegate());
|
| +}
|
| +
|
| +} // namespace views
|
|
|