OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this |
| 2 // source code is governed by a BSD-style license that can be found in the |
| 3 // LICENSE file. |
| 4 |
| 5 #include "views/controls/menu/native_menu_gtk.h" |
| 6 |
| 7 #include "base/string_util.h" |
| 8 #include "base/time.h" |
| 9 #include "views/accelerator.h" |
| 10 #include "views/controls/menu/menu_2.h" |
| 11 |
| 12 namespace { |
| 13 // Data passed to the UpdateStateCallback from gtk_container_foreach. |
| 14 struct UpdateStateData { |
| 15 // The model to retrieve state from. |
| 16 views::Menu2Model* model; |
| 17 // The index within said model. |
| 18 int index; |
| 19 }; |
| 20 |
| 21 // Data passed to the MenuPositionFunc from gtk_menu_popup |
| 22 struct Position { |
| 23 // The point to run the menu at. |
| 24 gfx::Point point; |
| 25 // The alignment of the menu at that point. |
| 26 views::Menu2::Alignment alignment; |
| 27 }; |
| 28 |
| 29 std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label) { |
| 30 std::string ret; |
| 31 ret.reserve(label.length()); |
| 32 for (size_t i = 0; i < label.length(); ++i) { |
| 33 if ('&' == label[i]) { |
| 34 if (i + 1 < label.length() && '&' == label[i + 1]) { |
| 35 ret.push_back(label[i]); |
| 36 ++i; |
| 37 } else { |
| 38 ret.push_back('_'); |
| 39 } |
| 40 } else { |
| 41 ret.push_back(label[i]); |
| 42 } |
| 43 } |
| 44 |
| 45 return ret; |
| 46 } |
| 47 |
| 48 // Returns true if the menu item type specified can be executed as a command. |
| 49 bool MenuTypeCanExecute(views::Menu2Model::ItemType type) { |
| 50 return type == views::Menu2Model::TYPE_COMMAND || |
| 51 type == views::Menu2Model::TYPE_CHECK || |
| 52 type == views::Menu2Model::TYPE_RADIO; |
| 53 } |
| 54 |
| 55 } // namespace |
| 56 |
| 57 namespace views { |
| 58 |
| 59 //////////////////////////////////////////////////////////////////////////////// |
| 60 // NativeMenuGtk, public: |
| 61 |
| 62 NativeMenuGtk::NativeMenuGtk(Menu2Model* model, |
| 63 Menu2Delegate* delegate) |
| 64 : model_(model), |
| 65 delegate_(delegate), |
| 66 menu_(NULL) { |
| 67 } |
| 68 |
| 69 NativeMenuGtk::~NativeMenuGtk() { |
| 70 gtk_widget_destroy(menu_); |
| 71 } |
| 72 |
| 73 //////////////////////////////////////////////////////////////////////////////// |
| 74 // NativeMenuGtk, MenuWrapper implementation: |
| 75 |
| 76 void NativeMenuGtk::RunMenuAt(const gfx::Point& point, int alignment) { |
| 77 Position position = { point, static_cast<Menu2::Alignment>(alignment) }; |
| 78 // TODO(beng): value of '1' will not work for context menus! |
| 79 gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, MenuPositionFunc, &position, 1, |
| 80 gtk_get_current_event_time()); |
| 81 } |
| 82 |
| 83 void NativeMenuGtk::Rebuild() { |
| 84 ResetMenu(); |
| 85 |
| 86 GtkRadioMenuItem* last_radio_item = NULL; |
| 87 for (int i = 0; i < model_->GetItemCount(); ++i) { |
| 88 Menu2Model::ItemType type = model_->GetTypeAt(i); |
| 89 if (type == Menu2Model::TYPE_SEPARATOR) |
| 90 AddSeparatorAt(i); |
| 91 else |
| 92 AddMenuItemAt(i, &last_radio_item); |
| 93 } |
| 94 } |
| 95 |
| 96 void NativeMenuGtk::UpdateStates() { |
| 97 UpdateStateData data = { model_, 0 }; |
| 98 gtk_container_foreach(GTK_CONTAINER(menu_), &UpdateStateCallback, &data); |
| 99 } |
| 100 |
| 101 gfx::NativeMenu NativeMenuGtk::GetNativeMenu() const { |
| 102 return menu_; |
| 103 } |
| 104 |
| 105 //////////////////////////////////////////////////////////////////////////////// |
| 106 // NativeMenuGtk, private: |
| 107 |
| 108 void NativeMenuGtk::AddSeparatorAt(int index) { |
| 109 GtkWidget* separator = gtk_separator_menu_item_new(); |
| 110 gtk_widget_show(separator); |
| 111 gtk_menu_append(menu_, separator); |
| 112 } |
| 113 |
| 114 void NativeMenuGtk::AddMenuItemAt(int index, |
| 115 GtkRadioMenuItem** last_radio_item) { |
| 116 GtkWidget* menu_item = NULL; |
| 117 std::string label = ConvertAcceleratorsFromWindowsStyle(WideToUTF8( |
| 118 model_->GetLabelAt(index))); |
| 119 |
| 120 Menu2Model::ItemType type = model_->GetTypeAt(index); |
| 121 switch (type) { |
| 122 case Menu2Model::TYPE_CHECK: |
| 123 menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str()); |
| 124 break; |
| 125 case Menu2Model::TYPE_RADIO: |
| 126 if (*last_radio_item) { |
| 127 menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget( |
| 128 *last_radio_item, label.c_str()); |
| 129 } else { |
| 130 menu_item = gtk_radio_menu_item_new_with_mnemonic(NULL, label.c_str()); |
| 131 } |
| 132 break; |
| 133 case Menu2Model::TYPE_SUBMENU: |
| 134 case Menu2Model::TYPE_COMMAND: |
| 135 menu_item = gtk_menu_item_new_with_mnemonic(label.c_str()); |
| 136 break; |
| 137 default: |
| 138 NOTREACHED(); |
| 139 break; |
| 140 } |
| 141 |
| 142 // TODO(beng): icons |
| 143 |
| 144 if (type == Menu2Model::TYPE_SUBMENU) { |
| 145 // TODO(beng): we're leaking these objects right now... consider some other |
| 146 // arrangement. |
| 147 Menu2* submenu = new Menu2(model_->GetSubmenuModelAt(index), delegate_); |
| 148 g_object_set_data(G_OBJECT(menu_item), "submenu", submenu); |
| 149 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), |
| 150 submenu->GetNativeMenu()); |
| 151 } |
| 152 |
| 153 views::Accelerator accelerator(0, false, false, false); |
| 154 if (model_->GetAcceleratorAt(index, &accelerator)) { |
| 155 // TODO(beng): accelerators w/gtk_widget_add_accelerator. |
| 156 } |
| 157 g_object_set_data(G_OBJECT(menu_item), "position", |
| 158 reinterpret_cast<void*>(index)); |
| 159 g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(CallActivate), |
| 160 this); |
| 161 gtk_widget_show(menu_item); |
| 162 gtk_menu_append(menu_, menu_item); |
| 163 } |
| 164 |
| 165 // static |
| 166 void NativeMenuGtk::UpdateStateCallback(GtkWidget* menu_item, gpointer data) { |
| 167 UpdateStateData* usd = reinterpret_cast<UpdateStateData*>(data); |
| 168 gtk_widget_set_sensitive(menu_item, usd->model->IsEnabledAt(usd->index)); |
| 169 if (GTK_IS_CHECK_MENU_ITEM(menu_item)) { |
| 170 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), |
| 171 usd->model->IsItemCheckedAt(usd->index)); |
| 172 } |
| 173 // Recurse into submenus, too. |
| 174 if (GTK_IS_MENU_ITEM(menu_item)) { |
| 175 if (gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item))) { |
| 176 Menu2* submenu = |
| 177 reinterpret_cast<Menu2*>(g_object_get_data(G_OBJECT(menu_item), |
| 178 "submenu")); |
| 179 if (submenu) |
| 180 submenu->UpdateStates(); |
| 181 } |
| 182 } |
| 183 ++usd->index; |
| 184 } |
| 185 |
| 186 void NativeMenuGtk::ResetMenu() { |
| 187 if (menu_) |
| 188 gtk_widget_destroy(menu_); |
| 189 menu_ = gtk_menu_new(); |
| 190 } |
| 191 |
| 192 // static |
| 193 void NativeMenuGtk::MenuPositionFunc(GtkMenu* menu, |
| 194 int* x, |
| 195 int* y, |
| 196 gboolean* push_in, |
| 197 void* data) { |
| 198 Position* position = reinterpret_cast<Position*>(data); |
| 199 // TODO(beng): RTL |
| 200 *x = position->point.x(); |
| 201 *y = position->point.y(); |
| 202 if (position->alignment == Menu2::ALIGN_TOPRIGHT) { |
| 203 GtkRequisition menu_req; |
| 204 gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); |
| 205 *x -= menu_req.width; |
| 206 } |
| 207 *push_in = FALSE; |
| 208 } |
| 209 |
| 210 void NativeMenuGtk::OnActivate(GtkMenuItem* menu_item) { |
| 211 int position = reinterpret_cast<int>(g_object_get_data(G_OBJECT(menu_item), |
| 212 "position")); |
| 213 if (model_->IsEnabledAt(position) && |
| 214 MenuTypeCanExecute(model_->GetTypeAt(position))) { |
| 215 delegate_->ExecuteCommand(model_, model_->GetCommandIdAt(position)); |
| 216 } |
| 217 } |
| 218 |
| 219 // static |
| 220 void NativeMenuGtk::CallActivate(GtkMenuItem* menu_item, |
| 221 NativeMenuGtk* native_menu) { |
| 222 native_menu->OnActivate(menu_item); |
| 223 } |
| 224 |
| 225 //////////////////////////////////////////////////////////////////////////////// |
| 226 // MenuWrapper, public: |
| 227 |
| 228 // static |
| 229 MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) { |
| 230 return new NativeMenuGtk(menu->model(), menu->delegate()); |
| 231 } |
| 232 |
| 233 } // namespace views |
OLD | NEW |