1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/views/frame/global_menu_bar_x11.h" 6 7#include <dlfcn.h> 8#include <glib-object.h> 9 10#include "base/debug/leak_annotations.h" 11#include "base/logging.h" 12#include "base/prefs/pref_service.h" 13#include "base/stl_util.h" 14#include "base/strings/string_number_conversions.h" 15#include "base/strings/stringprintf.h" 16#include "base/strings/utf_string_conversions.h" 17#include "chrome/app/chrome_command_ids.h" 18#include "chrome/browser/browser_process.h" 19#include "chrome/browser/chrome_notification_types.h" 20#include "chrome/browser/history/top_sites.h" 21#include "chrome/browser/profiles/profile.h" 22#include "chrome/browser/profiles/profile_info_cache.h" 23#include "chrome/browser/profiles/profile_manager.h" 24#include "chrome/browser/sessions/tab_restore_service.h" 25#include "chrome/browser/sessions/tab_restore_service_factory.h" 26#include "chrome/browser/ui/browser.h" 27#include "chrome/browser/ui/browser_commands.h" 28#include "chrome/browser/ui/browser_list.h" 29#include "chrome/browser/ui/browser_tab_restore_service_delegate.h" 30#include "chrome/browser/ui/views/frame/browser_desktop_window_tree_host_x11.h" 31#include "chrome/browser/ui/views/frame/browser_view.h" 32#include "chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h" 33#include "chrome/common/pref_names.h" 34#include "chrome/grit/generated_resources.h" 35#include "content/public/browser/notification_source.h" 36#include "ui/base/accelerators/menu_label_accelerator_util_linux.h" 37#include "ui/base/l10n/l10n_util.h" 38#include "ui/events/keycodes/keyboard_code_conversion_x.h" 39#include "ui/gfx/text_elider.h" 40 41// libdbusmenu-glib types 42typedef struct _DbusmenuMenuitem DbusmenuMenuitem; 43typedef DbusmenuMenuitem* (*dbusmenu_menuitem_new_func)(); 44typedef bool (*dbusmenu_menuitem_child_add_position_func)( 45 DbusmenuMenuitem* parent, 46 DbusmenuMenuitem* child, 47 unsigned int position); 48typedef DbusmenuMenuitem* (*dbusmenu_menuitem_child_append_func)( 49 DbusmenuMenuitem* parent, 50 DbusmenuMenuitem* child); 51typedef bool (*dbusmenu_menuitem_child_delete_func)( 52 DbusmenuMenuitem* parent, 53 DbusmenuMenuitem* child); 54typedef GList* (*dbusmenu_menuitem_get_children_func)( 55 DbusmenuMenuitem* item); 56typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_func)( 57 DbusmenuMenuitem* item, 58 const char* property, 59 const char* value); 60typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_variant_func)( 61 DbusmenuMenuitem* item, 62 const char* property, 63 GVariant* value); 64typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_bool_func)( 65 DbusmenuMenuitem* item, 66 const char* property, 67 bool value); 68typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_int_func)( 69 DbusmenuMenuitem* item, 70 const char* property, 71 int value); 72 73typedef struct _DbusmenuServer DbusmenuServer; 74typedef DbusmenuServer* (*dbusmenu_server_new_func)(const char* object); 75typedef void (*dbusmenu_server_set_root_func)(DbusmenuServer* self, 76 DbusmenuMenuitem* root); 77 78// A line in the static menu definitions. 79struct GlobalMenuBarCommand { 80 int str_id; 81 int command; 82 int tag; 83}; 84 85namespace { 86 87// Retrieved functions from libdbusmenu-glib. 88 89// DbusmenuMenuItem methods: 90dbusmenu_menuitem_new_func menuitem_new = NULL; 91dbusmenu_menuitem_get_children_func menuitem_get_children = NULL; 92dbusmenu_menuitem_child_add_position_func menuitem_child_add_position = NULL; 93dbusmenu_menuitem_child_append_func menuitem_child_append = NULL; 94dbusmenu_menuitem_child_delete_func menuitem_child_delete = NULL; 95dbusmenu_menuitem_property_set_func menuitem_property_set = NULL; 96dbusmenu_menuitem_property_set_variant_func menuitem_property_set_variant = 97 NULL; 98dbusmenu_menuitem_property_set_bool_func menuitem_property_set_bool = NULL; 99dbusmenu_menuitem_property_set_int_func menuitem_property_set_int = NULL; 100 101// DbusmenuServer methods: 102dbusmenu_server_new_func server_new = NULL; 103dbusmenu_server_set_root_func server_set_root = NULL; 104 105// Properties that we set on menu items: 106const char kPropertyEnabled[] = "enabled"; 107const char kPropertyLabel[] = "label"; 108const char kPropertyShortcut[] = "shortcut"; 109const char kPropertyType[] = "type"; 110const char kPropertyToggleType[] = "toggle-type"; 111const char kPropertyToggleState[] = "toggle-state"; 112const char kPropertyVisible[] = "visible"; 113 114const char kTypeCheckmark[] = "checkmark"; 115const char kTypeSeparator[] = "separator"; 116 117// Data set on GObjectgs. 118const char kTypeTag[] = "type-tag"; 119const char kHistoryItem[] = "history-item"; 120const char kProfileId[] = "profile-id"; 121 122// The maximum number of most visited items to display. 123const unsigned int kMostVisitedCount = 8; 124 125// The number of recently closed items to get. 126const unsigned int kRecentlyClosedCount = 8; 127 128// Menus more than this many chars long will get trimmed. 129const int kMaximumMenuWidthInChars = 50; 130 131// Constants used in menu definitions. 132const int MENU_SEPARATOR =-1; 133const int MENU_END = -2; 134const int MENU_DISABLED_ID = -3; 135 136// These tag values are used to refer to menu items. 137const int TAG_MOST_VISITED = 1; 138const int TAG_RECENTLY_CLOSED = 2; 139const int TAG_MOST_VISITED_HEADER = 3; 140const int TAG_RECENTLY_CLOSED_HEADER = 4; 141const int TAG_PROFILES = 5; 142 143GlobalMenuBarCommand file_menu[] = { 144 { IDS_NEW_TAB, IDC_NEW_TAB }, 145 { IDS_NEW_WINDOW, IDC_NEW_WINDOW }, 146 { IDS_NEW_INCOGNITO_WINDOW, IDC_NEW_INCOGNITO_WINDOW }, 147 { IDS_REOPEN_CLOSED_TABS_LINUX, IDC_RESTORE_TAB }, 148 { IDS_OPEN_FILE_LINUX, IDC_OPEN_FILE }, 149 { IDS_OPEN_LOCATION_LINUX, IDC_FOCUS_LOCATION }, 150 151 { MENU_SEPARATOR, MENU_SEPARATOR }, 152 153 { IDS_CREATE_SHORTCUTS, IDC_CREATE_SHORTCUTS }, 154 155 { MENU_SEPARATOR, MENU_SEPARATOR }, 156 157 { IDS_CLOSE_WINDOW_LINUX, IDC_CLOSE_WINDOW }, 158 { IDS_CLOSE_TAB_LINUX, IDC_CLOSE_TAB }, 159 { IDS_SAVE_PAGE, IDC_SAVE_PAGE }, 160 161 { MENU_SEPARATOR, MENU_SEPARATOR }, 162 163 { IDS_PRINT, IDC_PRINT }, 164 165 { MENU_END, MENU_END } 166}; 167 168GlobalMenuBarCommand edit_menu[] = { 169 { IDS_CUT, IDC_CUT }, 170 { IDS_COPY, IDC_COPY }, 171 { IDS_PASTE, IDC_PASTE }, 172 173 { MENU_SEPARATOR, MENU_SEPARATOR }, 174 175 { IDS_FIND, IDC_FIND }, 176 177 { MENU_SEPARATOR, MENU_SEPARATOR }, 178 179 { IDS_PREFERENCES, IDC_OPTIONS }, 180 181 { MENU_END, MENU_END } 182}; 183 184GlobalMenuBarCommand view_menu[] = { 185 { IDS_SHOW_BOOKMARK_BAR, IDC_SHOW_BOOKMARK_BAR }, 186 187 { MENU_SEPARATOR, MENU_SEPARATOR }, 188 189 { IDS_STOP_MENU_LINUX, IDC_STOP }, 190 { IDS_RELOAD_MENU_LINUX, IDC_RELOAD }, 191 192 { MENU_SEPARATOR, MENU_SEPARATOR }, 193 194 { IDS_FULLSCREEN, IDC_FULLSCREEN }, 195 { IDS_TEXT_DEFAULT_LINUX, IDC_ZOOM_NORMAL }, 196 { IDS_TEXT_BIGGER_LINUX, IDC_ZOOM_PLUS }, 197 { IDS_TEXT_SMALLER_LINUX, IDC_ZOOM_MINUS }, 198 199 { MENU_END, MENU_END } 200}; 201 202GlobalMenuBarCommand history_menu[] = { 203 { IDS_HISTORY_HOME_LINUX, IDC_HOME }, 204 { IDS_HISTORY_BACK_LINUX, IDC_BACK }, 205 { IDS_HISTORY_FORWARD_LINUX, IDC_FORWARD }, 206 207 { MENU_SEPARATOR, MENU_SEPARATOR }, 208 209 { IDS_HISTORY_VISITED_LINUX, MENU_DISABLED_ID, TAG_MOST_VISITED_HEADER }, 210 211 { MENU_SEPARATOR, MENU_SEPARATOR }, 212 213 { IDS_HISTORY_CLOSED_LINUX, MENU_DISABLED_ID, TAG_RECENTLY_CLOSED_HEADER }, 214 215 { MENU_SEPARATOR, MENU_SEPARATOR }, 216 217 { IDS_SHOWFULLHISTORY_LINK, IDC_SHOW_HISTORY }, 218 219 { MENU_END, MENU_END } 220}; 221 222GlobalMenuBarCommand tools_menu[] = { 223 { IDS_SHOW_DOWNLOADS, IDC_SHOW_DOWNLOADS }, 224 { IDS_SHOW_HISTORY, IDC_SHOW_HISTORY }, 225 { IDS_SHOW_EXTENSIONS, IDC_MANAGE_EXTENSIONS }, 226 227 { MENU_SEPARATOR, MENU_SEPARATOR }, 228 229 { IDS_TASK_MANAGER, IDC_TASK_MANAGER }, 230 { IDS_CLEAR_BROWSING_DATA, IDC_CLEAR_BROWSING_DATA }, 231 232 { MENU_SEPARATOR, MENU_SEPARATOR }, 233 234 { IDS_VIEW_SOURCE, IDC_VIEW_SOURCE }, 235 { IDS_DEV_TOOLS, IDC_DEV_TOOLS }, 236 { IDS_DEV_TOOLS_CONSOLE, IDC_DEV_TOOLS_CONSOLE }, 237 { IDS_DEV_TOOLS_DEVICES, IDC_DEV_TOOLS_DEVICES }, 238 239 { MENU_END, MENU_END } 240}; 241 242GlobalMenuBarCommand help_menu[] = { 243#if defined(GOOGLE_CHROME_BUILD) 244 { IDS_FEEDBACK, IDC_FEEDBACK }, 245#endif 246 { IDS_HELP_PAGE , IDC_HELP_PAGE_VIA_MENU }, 247 { MENU_END, MENU_END } 248}; 249 250GlobalMenuBarCommand profiles_menu[] = { 251 { MENU_SEPARATOR, MENU_SEPARATOR }, 252 { MENU_END, MENU_END } 253}; 254 255void EnsureMethodsLoaded() { 256 static bool attempted_load = false; 257 if (attempted_load) 258 return; 259 attempted_load = true; 260 261 void* dbusmenu_lib = dlopen("libdbusmenu-glib.so", RTLD_LAZY); 262 if (!dbusmenu_lib) 263 dbusmenu_lib = dlopen("libdbusmenu-glib.so.4", RTLD_LAZY); 264 if (!dbusmenu_lib) 265 return; 266 267 // DbusmenuMenuItem methods. 268 menuitem_new = reinterpret_cast<dbusmenu_menuitem_new_func>( 269 dlsym(dbusmenu_lib, "dbusmenu_menuitem_new")); 270 menuitem_child_add_position = 271 reinterpret_cast<dbusmenu_menuitem_child_add_position_func>( 272 dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_add_position")); 273 menuitem_child_append = reinterpret_cast<dbusmenu_menuitem_child_append_func>( 274 dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_append")); 275 menuitem_child_delete = reinterpret_cast<dbusmenu_menuitem_child_delete_func>( 276 dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_delete")); 277 menuitem_get_children = reinterpret_cast<dbusmenu_menuitem_get_children_func>( 278 dlsym(dbusmenu_lib, "dbusmenu_menuitem_get_children")); 279 menuitem_property_set = reinterpret_cast<dbusmenu_menuitem_property_set_func>( 280 dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set")); 281 menuitem_property_set_variant = 282 reinterpret_cast<dbusmenu_menuitem_property_set_variant_func>( 283 dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_variant")); 284 menuitem_property_set_bool = 285 reinterpret_cast<dbusmenu_menuitem_property_set_bool_func>( 286 dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_bool")); 287 menuitem_property_set_int = 288 reinterpret_cast<dbusmenu_menuitem_property_set_int_func>( 289 dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_int")); 290 291 // DbusmenuServer methods. 292 server_new = reinterpret_cast<dbusmenu_server_new_func>( 293 dlsym(dbusmenu_lib, "dbusmenu_server_new")); 294 server_set_root = reinterpret_cast<dbusmenu_server_set_root_func>( 295 dlsym(dbusmenu_lib, "dbusmenu_server_set_root")); 296} 297 298} // namespace 299 300struct GlobalMenuBarX11::HistoryItem { 301 HistoryItem() : session_id(0) {} 302 303 // The title for the menu item. 304 base::string16 title; 305 // The URL that will be navigated to if the user selects this item. 306 GURL url; 307 308 // This ID is unique for a browser session and can be passed to the 309 // TabRestoreService to re-open the closed window or tab that this 310 // references. A non-0 session ID indicates that this is an entry can be 311 // restored that way. Otherwise, the URL will be used to open the item and 312 // this ID will be 0. 313 SessionID::id_type session_id; 314 315 // If the HistoryItem is a window, this will be the vector of tabs. Note 316 // that this is a list of weak references. The |menu_item_map_| is the owner 317 // of all items. If it is not a window, then the entry is a single page and 318 // the vector will be empty. 319 std::vector<HistoryItem*> tabs; 320 321 private: 322 DISALLOW_COPY_AND_ASSIGN(HistoryItem); 323}; 324 325GlobalMenuBarX11::GlobalMenuBarX11(BrowserView* browser_view, 326 BrowserDesktopWindowTreeHostX11* host) 327 : browser_(browser_view->browser()), 328 profile_(browser_->profile()), 329 browser_view_(browser_view), 330 host_(host), 331 server_(NULL), 332 root_item_(NULL), 333 history_menu_(NULL), 334 profiles_menu_(NULL), 335 top_sites_(NULL), 336 tab_restore_service_(NULL), 337 weak_ptr_factory_(this) { 338 EnsureMethodsLoaded(); 339 340 if (server_new) 341 host_->AddObserver(this); 342} 343 344GlobalMenuBarX11::~GlobalMenuBarX11() { 345 if (server_) { 346 Disable(); 347 348 if (tab_restore_service_) 349 tab_restore_service_->RemoveObserver(this); 350 351 g_object_unref(server_); 352 host_->RemoveObserver(this); 353 } 354 BrowserList::RemoveObserver(this); 355} 356 357// static 358std::string GlobalMenuBarX11::GetPathForWindow(unsigned long xid) { 359 return base::StringPrintf("/com/canonical/menu/%lX", xid); 360} 361 362DbusmenuMenuitem* GlobalMenuBarX11::BuildSeparator() { 363 DbusmenuMenuitem* item = menuitem_new(); 364 menuitem_property_set(item, kPropertyType, kTypeSeparator); 365 menuitem_property_set_bool(item, kPropertyVisible, true); 366 return item; 367} 368 369DbusmenuMenuitem* GlobalMenuBarX11::BuildMenuItem( 370 const std::string& label, 371 int tag_id) { 372 DbusmenuMenuitem* item = menuitem_new(); 373 menuitem_property_set(item, kPropertyLabel, label.c_str()); 374 menuitem_property_set_bool(item, kPropertyVisible, true); 375 376 if (tag_id) 377 g_object_set_data(G_OBJECT(item), kTypeTag, GINT_TO_POINTER(tag_id)); 378 379 return item; 380} 381 382void GlobalMenuBarX11::InitServer(unsigned long xid) { 383 std::string path = GetPathForWindow(xid); 384 { 385 ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/314087 386 server_ = server_new(path.c_str()); 387 } 388 389 root_item_ = menuitem_new(); 390 menuitem_property_set(root_item_, kPropertyLabel, "Root"); 391 menuitem_property_set_bool(root_item_, kPropertyVisible, true); 392 393 // First build static menu content. 394 BuildStaticMenu(root_item_, IDS_FILE_MENU_LINUX, file_menu); 395 BuildStaticMenu(root_item_, IDS_EDIT_MENU_LINUX, edit_menu); 396 BuildStaticMenu(root_item_, IDS_VIEW_MENU_LINUX, view_menu); 397 history_menu_ = BuildStaticMenu( 398 root_item_, IDS_HISTORY_MENU_LINUX, history_menu); 399 BuildStaticMenu(root_item_, IDS_TOOLS_MENU_LINUX, tools_menu); 400 profiles_menu_ = BuildStaticMenu( 401 root_item_, IDS_PROFILES_OPTIONS_GROUP_NAME, profiles_menu); 402 BuildStaticMenu(root_item_, IDS_HELP_MENU_LINUX, help_menu); 403 404 // We have to connect to |history_menu_item|'s "activate" signal instead of 405 // |history_menu|'s "show" signal because we are not supposed to modify the 406 // menu during "show" 407 g_signal_connect(history_menu_, "about-to-show", 408 G_CALLBACK(OnHistoryMenuAboutToShowThunk), this); 409 410 for (CommandIDMenuItemMap::const_iterator it = id_to_menu_item_.begin(); 411 it != id_to_menu_item_.end(); ++it) { 412 menuitem_property_set_bool(it->second, kPropertyEnabled, 413 chrome::IsCommandEnabled(browser_, it->first)); 414 415 ui::Accelerator accelerator; 416 if (browser_view_->GetAccelerator(it->first, &accelerator)) 417 RegisterAccelerator(it->second, accelerator); 418 419 chrome::AddCommandObserver(browser_, it->first, this); 420 } 421 422 pref_change_registrar_.Init(browser_->profile()->GetPrefs()); 423 pref_change_registrar_.Add( 424 bookmarks::prefs::kShowBookmarkBar, 425 base::Bind(&GlobalMenuBarX11::OnBookmarkBarVisibilityChanged, 426 base::Unretained(this))); 427 OnBookmarkBarVisibilityChanged(); 428 429 top_sites_ = profile_->GetTopSites(); 430 if (top_sites_) { 431 GetTopSitesData(); 432 433 // Register for notification when TopSites changes so that we can update 434 // ourself. 435 registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED, 436 content::Source<history::TopSites>(top_sites_)); 437 } 438 439 ProfileManager* profile_manager = g_browser_process->profile_manager(); 440 DCHECK(profile_manager); 441 avatar_menu_.reset(new AvatarMenu( 442 &profile_manager->GetProfileInfoCache(), this, NULL)); 443 avatar_menu_->RebuildMenu(); 444 BrowserList::AddObserver(this); 445 446 RebuildProfilesMenu(); 447 448 server_set_root(server_, root_item_); 449} 450 451void GlobalMenuBarX11::Disable() { 452 for (CommandIDMenuItemMap::const_iterator it = id_to_menu_item_.begin(); 453 it != id_to_menu_item_.end(); ++it) { 454 chrome::RemoveCommandObserver(browser_, it->first, this); 455 } 456 id_to_menu_item_.clear(); 457 458 pref_change_registrar_.RemoveAll(); 459} 460 461DbusmenuMenuitem* GlobalMenuBarX11::BuildStaticMenu( 462 DbusmenuMenuitem* parent, 463 int menu_str_id, 464 GlobalMenuBarCommand* commands) { 465 DbusmenuMenuitem* top = menuitem_new(); 466 menuitem_property_set( 467 top, kPropertyLabel, 468 ui::RemoveWindowsStyleAccelerators( 469 l10n_util::GetStringUTF8(menu_str_id)).c_str()); 470 menuitem_property_set_bool(top, kPropertyVisible, true); 471 472 for (int i = 0; commands[i].str_id != MENU_END; ++i) { 473 DbusmenuMenuitem* menu_item = NULL; 474 int command_id = commands[i].command; 475 if (commands[i].str_id == MENU_SEPARATOR) { 476 menu_item = BuildSeparator(); 477 } else { 478 std::string label = ui::ConvertAcceleratorsFromWindowsStyle( 479 l10n_util::GetStringUTF8(commands[i].str_id)); 480 481 menu_item = BuildMenuItem(label, commands[i].tag); 482 483 if (command_id == MENU_DISABLED_ID) { 484 menuitem_property_set_bool(menu_item, kPropertyEnabled, false); 485 } else { 486 if (command_id == IDC_SHOW_BOOKMARK_BAR) 487 menuitem_property_set(menu_item, kPropertyToggleType, kTypeCheckmark); 488 489 id_to_menu_item_.insert(std::make_pair(command_id, menu_item)); 490 g_object_set_data(G_OBJECT(menu_item), "command-id", 491 GINT_TO_POINTER(command_id)); 492 g_signal_connect(menu_item, "item-activated", 493 G_CALLBACK(OnItemActivatedThunk), this); 494 } 495 } 496 497 menuitem_child_append(top, menu_item); 498 g_object_unref(menu_item); 499 } 500 501 menuitem_child_append(parent, top); 502 g_object_unref(top); 503 return top; 504} 505 506void GlobalMenuBarX11::RegisterAccelerator(DbusmenuMenuitem* item, 507 const ui::Accelerator& accelerator) { 508 // A translation of libdbusmenu-gtk's menuitem_property_set_shortcut() 509 // translated from GDK types to ui::Accelerator types. 510 GVariantBuilder builder; 511 g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); 512 513 if (accelerator.IsCtrlDown()) 514 g_variant_builder_add(&builder, "s", "Control"); 515 if (accelerator.IsAltDown()) 516 g_variant_builder_add(&builder, "s", "Alt"); 517 if (accelerator.IsShiftDown()) 518 g_variant_builder_add(&builder, "s", "Shift"); 519 520 char* name = XKeysymToString(XKeysymForWindowsKeyCode( 521 accelerator.key_code(), false)); 522 if (!name) { 523 NOTIMPLEMENTED(); 524 return; 525 } 526 g_variant_builder_add(&builder, "s", name); 527 528 GVariant* inside_array = g_variant_builder_end(&builder); 529 g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); 530 g_variant_builder_add_value(&builder, inside_array); 531 GVariant* outside_array = g_variant_builder_end(&builder); 532 533 menuitem_property_set_variant(item, kPropertyShortcut, outside_array); 534} 535 536GlobalMenuBarX11::HistoryItem* GlobalMenuBarX11::HistoryItemForTab( 537 const TabRestoreService::Tab& entry) { 538 const sessions::SerializedNavigationEntry& current_navigation = 539 entry.navigations.at(entry.current_navigation_index); 540 HistoryItem* item = new HistoryItem(); 541 item->title = current_navigation.title(); 542 item->url = current_navigation.virtual_url(); 543 item->session_id = entry.id; 544 545 return item; 546} 547 548void GlobalMenuBarX11::AddHistoryItemToMenu(HistoryItem* item, 549 DbusmenuMenuitem* menu, 550 int tag, 551 int index) { 552 base::string16 title = item->title; 553 std::string url_string = item->url.possibly_invalid_spec(); 554 555 if (title.empty()) 556 title = base::UTF8ToUTF16(url_string); 557 gfx::ElideString(title, kMaximumMenuWidthInChars, &title); 558 559 DbusmenuMenuitem* menu_item = BuildMenuItem(base::UTF16ToUTF8(title), tag); 560 g_signal_connect(menu_item, "item-activated", 561 G_CALLBACK(OnHistoryItemActivatedThunk), this); 562 563 g_object_set_data_full(G_OBJECT(menu_item), kHistoryItem, item, 564 DeleteHistoryItem); 565 menuitem_child_add_position(menu, menu_item, index); 566 g_object_unref(menu_item); 567} 568 569void GlobalMenuBarX11::GetTopSitesData() { 570 DCHECK(top_sites_); 571 572 top_sites_->GetMostVisitedURLs( 573 base::Bind(&GlobalMenuBarX11::OnTopSitesReceived, 574 weak_ptr_factory_.GetWeakPtr()), false); 575} 576 577void GlobalMenuBarX11::OnTopSitesReceived( 578 const history::MostVisitedURLList& visited_list) { 579 ClearMenuSection(history_menu_, TAG_MOST_VISITED); 580 581 int index = GetIndexOfMenuItemWithTag(history_menu_, 582 TAG_MOST_VISITED_HEADER) + 1; 583 584 for (size_t i = 0; i < visited_list.size() && i < kMostVisitedCount; ++i) { 585 const history::MostVisitedURL& visited = visited_list[i]; 586 if (visited.url.spec().empty()) 587 break; // This is the signal that there are no more real visited sites. 588 589 HistoryItem* item = new HistoryItem(); 590 item->title = visited.title; 591 item->url = visited.url; 592 593 AddHistoryItemToMenu(item, 594 history_menu_, 595 TAG_MOST_VISITED, 596 index++); 597 } 598} 599 600void GlobalMenuBarX11::OnBookmarkBarVisibilityChanged() { 601 CommandIDMenuItemMap::iterator it = 602 id_to_menu_item_.find(IDC_SHOW_BOOKMARK_BAR); 603 if (it != id_to_menu_item_.end()) { 604 PrefService* prefs = browser_->profile()->GetPrefs(); 605 // Note: Unlike the GTK version, we don't appear to need to do tricks where 606 // we block activation while setting the toggle. 607 menuitem_property_set_int( 608 it->second, 609 kPropertyToggleState, 610 prefs->GetBoolean(bookmarks::prefs::kShowBookmarkBar)); 611 } 612} 613 614void GlobalMenuBarX11::RebuildProfilesMenu() { 615 ClearMenuSection(profiles_menu_, TAG_PROFILES); 616 617 // Don't call avatar_menu_->GetActiveProfileIndex() as the as the index might 618 // be incorrect if RebuildProfilesMenu() is called while we deleting the 619 // active profile and closing all its browser windows. 620 int active_profile_index = -1; 621 622 for (size_t i = 0; i < avatar_menu_->GetNumberOfItems(); ++i) { 623 const AvatarMenu::Item& item = avatar_menu_->GetItemAt(i); 624 base::string16 title = item.name; 625 gfx::ElideString(title, kMaximumMenuWidthInChars, &title); 626 627 DbusmenuMenuitem* menu_item = BuildMenuItem( 628 base::UTF16ToUTF8(title), TAG_PROFILES); 629 g_object_set_data(G_OBJECT(menu_item), kProfileId, GINT_TO_POINTER(i)); 630 g_signal_connect(menu_item, "item-activated", 631 G_CALLBACK(OnProfileItemActivatedThunk), this); 632 menuitem_property_set(menu_item, kPropertyToggleType, kTypeCheckmark); 633 menuitem_property_set_int(menu_item, kPropertyToggleState, item.active); 634 635 if (item.active) 636 active_profile_index = i; 637 638 menuitem_child_add_position(profiles_menu_, menu_item, i); 639 g_object_unref(menu_item); 640 } 641 642 // There is a separator between the list of profiles and the possible actions. 643 int index = avatar_menu_->GetNumberOfItems() + 1; 644 645 DbusmenuMenuitem* edit_profile_item = BuildMenuItem( 646 l10n_util::GetStringUTF8(IDS_PROFILES_MANAGE_BUTTON_LABEL), TAG_PROFILES); 647 DbusmenuMenuitem* create_profile_item = BuildMenuItem( 648 l10n_util::GetStringUTF8(IDS_PROFILES_CREATE_BUTTON_LABEL), 649 TAG_PROFILES); 650 651 // There is no active profile in Guest mode, in which case the action buttons 652 // should be disabled. 653 if (active_profile_index >= 0) { 654 g_object_set_data(G_OBJECT(edit_profile_item), kProfileId, 655 GINT_TO_POINTER(active_profile_index)); 656 g_signal_connect(edit_profile_item, "item-activated", 657 G_CALLBACK(OnEditProfileItemActivatedThunk), this); 658 g_signal_connect(create_profile_item, "item-activated", 659 G_CALLBACK(OnCreateProfileItemActivatedThunk), this); 660 } else { 661 menuitem_property_set_bool(edit_profile_item, kPropertyEnabled, false); 662 menuitem_property_set_bool(create_profile_item, kPropertyEnabled, false); 663 } 664 665 menuitem_child_add_position(profiles_menu_, edit_profile_item, index++); 666 menuitem_child_add_position(profiles_menu_, create_profile_item, index); 667 g_object_unref(edit_profile_item); 668 g_object_unref(create_profile_item); 669} 670 671int GlobalMenuBarX11::GetIndexOfMenuItemWithTag(DbusmenuMenuitem* menu, 672 int tag_id) { 673 GList* childs = menuitem_get_children(menu); 674 int i = 0; 675 for (; childs != NULL; childs = childs->next, i++) { 676 int tag = 677 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(childs->data), kTypeTag)); 678 if (tag == tag_id) 679 return i; 680 } 681 682 NOTREACHED(); 683 return -1; 684} 685 686void GlobalMenuBarX11::ClearMenuSection(DbusmenuMenuitem* menu, int tag_id) { 687 std::vector<DbusmenuMenuitem*> menuitems_to_delete; 688 689 GList* childs = menuitem_get_children(menu); 690 for (; childs != NULL; childs = childs->next) { 691 DbusmenuMenuitem* current_item = reinterpret_cast<DbusmenuMenuitem*>( 692 childs->data); 693 ClearMenuSection(current_item, tag_id); 694 695 int tag = 696 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(childs->data), kTypeTag)); 697 if (tag == tag_id) 698 menuitems_to_delete.push_back(current_item); 699 } 700 701 for (std::vector<DbusmenuMenuitem*>::const_iterator it = 702 menuitems_to_delete.begin(); it != menuitems_to_delete.end(); ++it) { 703 menuitem_child_delete(menu, *it); 704 } 705} 706 707// static 708void GlobalMenuBarX11::DeleteHistoryItem(void* void_item) { 709 HistoryItem* item = 710 reinterpret_cast<GlobalMenuBarX11::HistoryItem*>(void_item); 711 delete item; 712} 713 714void GlobalMenuBarX11::OnAvatarMenuChanged(AvatarMenu* avatar_menu) { 715 RebuildProfilesMenu(); 716} 717 718void GlobalMenuBarX11::OnBrowserSetLastActive(Browser* browser) { 719 // Rebuild the avatar menu so that the items have the correct active state. 720 avatar_menu_->RebuildMenu(); 721 avatar_menu_->ActiveBrowserChanged(browser); 722 RebuildProfilesMenu(); 723} 724 725void GlobalMenuBarX11::EnabledStateChangedForCommand(int id, bool enabled) { 726 CommandIDMenuItemMap::iterator it = id_to_menu_item_.find(id); 727 if (it != id_to_menu_item_.end()) 728 menuitem_property_set_bool(it->second, kPropertyEnabled, enabled); 729} 730 731void GlobalMenuBarX11::Observe(int type, 732 const content::NotificationSource& source, 733 const content::NotificationDetails& details) { 734 if (type == chrome::NOTIFICATION_TOP_SITES_CHANGED) { 735 GetTopSitesData(); 736 } else { 737 NOTREACHED(); 738 } 739} 740 741void GlobalMenuBarX11::TabRestoreServiceChanged(TabRestoreService* service) { 742 const TabRestoreService::Entries& entries = service->entries(); 743 744 ClearMenuSection(history_menu_, TAG_RECENTLY_CLOSED); 745 746 // We'll get the index the "Recently Closed" header. (This can vary depending 747 // on the number of "Most Visited" items. 748 int index = GetIndexOfMenuItemWithTag(history_menu_, 749 TAG_RECENTLY_CLOSED_HEADER) + 1; 750 751 unsigned int added_count = 0; 752 for (TabRestoreService::Entries::const_iterator it = entries.begin(); 753 it != entries.end() && added_count < kRecentlyClosedCount; ++it) { 754 TabRestoreService::Entry* entry = *it; 755 756 if (entry->type == TabRestoreService::WINDOW) { 757 TabRestoreService::Window* entry_win = 758 static_cast<TabRestoreService::Window*>(entry); 759 std::vector<TabRestoreService::Tab>& tabs = entry_win->tabs; 760 if (tabs.empty()) 761 continue; 762 763 // Create the item for the parent/window. 764 HistoryItem* item = new HistoryItem(); 765 item->session_id = entry_win->id; 766 767 std::string title = tabs.size() == 1 ? 768 l10n_util::GetStringUTF8( 769 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE) : 770 l10n_util::GetStringFUTF8( 771 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE, 772 base::IntToString16(tabs.size())); 773 DbusmenuMenuitem* parent_item = BuildMenuItem( 774 title, TAG_RECENTLY_CLOSED); 775 menuitem_child_add_position(history_menu_, parent_item, index++); 776 g_object_unref(parent_item); 777 778 // The mac version of this code allows the user to click on the parent 779 // menu item to have the same effect as clicking the restore window 780 // submenu item. GTK+ helpfully activates a menu item when it shows a 781 // submenu so toss that feature out. 782 DbusmenuMenuitem* restore_item = BuildMenuItem( 783 l10n_util::GetStringUTF8( 784 IDS_HISTORY_CLOSED_RESTORE_WINDOW_LINUX).c_str(), 785 TAG_RECENTLY_CLOSED); 786 g_signal_connect(restore_item, "item-activated", 787 G_CALLBACK(OnHistoryItemActivatedThunk), this); 788 g_object_set_data_full(G_OBJECT(restore_item), kHistoryItem, item, 789 DeleteHistoryItem); 790 menuitem_child_append(parent_item, restore_item); 791 g_object_unref(restore_item); 792 793 DbusmenuMenuitem* separator = BuildSeparator(); 794 menuitem_child_append(parent_item, separator); 795 g_object_unref(separator); 796 797 // Loop over the window's tabs and add them to the submenu. 798 int subindex = 2; 799 std::vector<TabRestoreService::Tab>::const_iterator iter; 800 for (iter = tabs.begin(); iter != tabs.end(); ++iter) { 801 TabRestoreService::Tab tab = *iter; 802 HistoryItem* tab_item = HistoryItemForTab(tab); 803 item->tabs.push_back(tab_item); 804 AddHistoryItemToMenu(tab_item, 805 parent_item, 806 TAG_RECENTLY_CLOSED, 807 subindex++); 808 } 809 810 ++added_count; 811 } else if (entry->type == TabRestoreService::TAB) { 812 TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry); 813 HistoryItem* item = HistoryItemForTab(*tab); 814 AddHistoryItemToMenu(item, 815 history_menu_, 816 TAG_RECENTLY_CLOSED, 817 index++); 818 ++added_count; 819 } 820 } 821} 822 823void GlobalMenuBarX11::TabRestoreServiceDestroyed( 824 TabRestoreService* service) { 825 tab_restore_service_ = NULL; 826} 827 828void GlobalMenuBarX11::OnWindowMapped(unsigned long xid) { 829 if (!server_) 830 InitServer(xid); 831 832 GlobalMenuBarRegistrarX11::GetInstance()->OnWindowMapped(xid); 833} 834 835void GlobalMenuBarX11::OnWindowUnmapped(unsigned long xid) { 836 GlobalMenuBarRegistrarX11::GetInstance()->OnWindowUnmapped(xid); 837} 838 839void GlobalMenuBarX11::OnItemActivated(DbusmenuMenuitem* item, 840 unsigned int timestamp) { 841 int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "command-id")); 842 chrome::ExecuteCommand(browser_, id); 843} 844 845void GlobalMenuBarX11::OnHistoryItemActivated(DbusmenuMenuitem* sender, 846 unsigned int timestamp) { 847 // Note: We don't have access to the event modifiers used to click the menu 848 // item since that happens in a different process. 849 HistoryItem* item = reinterpret_cast<HistoryItem*>( 850 g_object_get_data(G_OBJECT(sender), kHistoryItem)); 851 852 // If this item can be restored using TabRestoreService, do so. Otherwise, 853 // just load the URL. 854 TabRestoreService* service = 855 TabRestoreServiceFactory::GetForProfile(profile_); 856 if (item->session_id && service) { 857 service->RestoreEntryById(browser_->tab_restore_service_delegate(), 858 item->session_id, browser_->host_desktop_type(), 859 UNKNOWN); 860 } else { 861 DCHECK(item->url.is_valid()); 862 browser_->OpenURL(content::OpenURLParams( 863 item->url, 864 content::Referrer(), 865 NEW_FOREGROUND_TAB, 866 ui::PAGE_TRANSITION_AUTO_BOOKMARK, 867 false)); 868 } 869} 870 871void GlobalMenuBarX11::OnHistoryMenuAboutToShow(DbusmenuMenuitem* item) { 872 if (!tab_restore_service_) { 873 tab_restore_service_ = TabRestoreServiceFactory::GetForProfile(profile_); 874 if (tab_restore_service_) { 875 tab_restore_service_->LoadTabsFromLastSession(); 876 tab_restore_service_->AddObserver(this); 877 878 // If LoadTabsFromLastSession doesn't load tabs, it won't call 879 // TabRestoreServiceChanged(). This ensures that all new windows after 880 // the first one will have their menus populated correctly. 881 TabRestoreServiceChanged(tab_restore_service_); 882 } 883 } 884} 885 886void GlobalMenuBarX11::OnProfileItemActivated(DbusmenuMenuitem* sender, 887 unsigned int timestamp) { 888 int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sender), kProfileId)); 889 avatar_menu_->SwitchToProfile(id, false, ProfileMetrics::SWITCH_PROFILE_MENU); 890} 891 892void GlobalMenuBarX11::OnEditProfileItemActivated(DbusmenuMenuitem* sender, 893 unsigned int timestamp) { 894 int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sender), kProfileId)); 895 avatar_menu_->EditProfile(id); 896} 897 898void GlobalMenuBarX11::OnCreateProfileItemActivated(DbusmenuMenuitem* sender, 899 unsigned int timestamp) { 900 avatar_menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_MENU); 901} 902