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