Home | History | Annotate | Download | only in gtk
      1 // Copyright (c) 2012 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/gtk/avatar_menu_bubble_gtk.h"
      6 
      7 #include "base/i18n/rtl.h"
      8 #include "base/strings/utf_string_conversions.h"
      9 #include "chrome/browser/browser_process.h"
     10 #include "chrome/browser/chrome_notification_types.h"
     11 #include "chrome/browser/profiles/avatar_menu_model.h"
     12 #include "chrome/browser/profiles/profile_info_cache.h"
     13 #include "chrome/browser/profiles/profile_manager.h"
     14 #include "chrome/browser/ui/browser.h"
     15 #include "chrome/browser/ui/gtk/avatar_menu_item_gtk.h"
     16 #include "chrome/browser/ui/gtk/browser_toolbar_gtk.h"
     17 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
     18 #include "chrome/browser/ui/gtk/event_utils.h"
     19 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
     20 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     21 #include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
     22 #include "content/public/browser/notification_source.h"
     23 #include "grit/generated_resources.h"
     24 #include "ui/base/gtk/gtk_hig_constants.h"
     25 #include "ui/base/l10n/l10n_util.h"
     26 
     27 namespace {
     28 
     29 // The minimum width in pixels of the bubble.
     30 const int kBubbleMinWidth = 175;
     31 
     32 // The number of pixels of padding on the left of the 'New Profile' link at the
     33 // bottom of the bubble.
     34 const int kNewProfileLinkLeftPadding = 40;
     35 
     36 }  // namespace
     37 
     38 AvatarMenuBubbleGtk::AvatarMenuBubbleGtk(Browser* browser,
     39                                          GtkWidget* anchor,
     40                                          BubbleGtk::FrameStyle arrow,
     41                                          const gfx::Rect* rect)
     42     : contents_(NULL),
     43       inner_contents_(NULL),
     44       theme_service_(GtkThemeService::GetFrom(browser->profile())),
     45       new_profile_link_(NULL),
     46       minimum_width_(kBubbleMinWidth),
     47       switching_(false) {
     48   avatar_menu_model_.reset(new AvatarMenuModel(
     49       &g_browser_process->profile_manager()->GetProfileInfoCache(),
     50       this, browser));
     51 
     52   OnAvatarMenuModelChanged(avatar_menu_model_.get());
     53 
     54   bubble_ = BubbleGtk::Show(anchor,
     55                             rect,
     56                             contents_,
     57                             arrow,
     58                             BubbleGtk::MATCH_SYSTEM_THEME |
     59                                 BubbleGtk::POPUP_WINDOW |
     60                                 BubbleGtk::GRAB_INPUT,
     61                             theme_service_,
     62                             this);  // |delegate|
     63   g_signal_connect(contents_, "destroy",
     64                    G_CALLBACK(&OnDestroyThunk), this);
     65 }
     66 
     67 AvatarMenuBubbleGtk::~AvatarMenuBubbleGtk() {}
     68 
     69 void AvatarMenuBubbleGtk::OnDestroy(GtkWidget* widget) {
     70   // We are self deleting, we have a destroy signal setup to catch when we
     71   // destroyed (via the BubbleGtk being destroyed), and delete ourself.
     72   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
     73 }
     74 
     75 void AvatarMenuBubbleGtk::BubbleClosing(BubbleGtk* bubble,
     76                                         bool closed_by_escape) {
     77   bubble_ = NULL;
     78 }
     79 
     80 void AvatarMenuBubbleGtk::OnAvatarMenuModelChanged(
     81     AvatarMenuModel* avatar_menu_model) {
     82   items_.clear();
     83   minimum_width_ = kBubbleMinWidth;
     84 
     85   InitContents();
     86 }
     87 
     88 void AvatarMenuBubbleGtk::OpenProfile(size_t profile_index) {
     89   if (!bubble_)
     90     return;
     91   GdkModifierType modifier_state;
     92   gtk_get_current_event_state(&modifier_state);
     93   guint modifier_state_uint = modifier_state;
     94   avatar_menu_model_->SwitchToProfile(profile_index,
     95       event_utils::DispositionFromGdkState(modifier_state_uint) == NEW_WINDOW);
     96   CloseBubble();
     97 }
     98 
     99 void AvatarMenuBubbleGtk::EditProfile(size_t profile_index) {
    100   if (!bubble_)
    101     return;
    102   avatar_menu_model_->EditProfile(profile_index);
    103   CloseBubble();
    104 }
    105 
    106 void AvatarMenuBubbleGtk::OnSizeRequest(GtkWidget* widget,
    107                                         GtkRequisition* req) {
    108   // Always use the maximum width ever requested.
    109   if (req->width < minimum_width_)
    110     req->width = minimum_width_;
    111   else
    112     minimum_width_ = req->width;
    113 }
    114 
    115 void AvatarMenuBubbleGtk::OnNewProfileLinkClicked(GtkWidget* link) {
    116   if (!bubble_)
    117     return;
    118   avatar_menu_model_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON);
    119   CloseBubble();
    120 }
    121 
    122 void AvatarMenuBubbleGtk::OnSwitchProfileLinkClicked(GtkWidget* link) {
    123   switching_ = true;
    124   OnAvatarMenuModelChanged(avatar_menu_model_.get());
    125 }
    126 
    127 void AvatarMenuBubbleGtk::InitMenuContents() {
    128   size_t profile_count = avatar_menu_model_->GetNumberOfItems();
    129   GtkWidget* items_vbox = gtk_vbox_new(FALSE, ui::kContentAreaSpacing);
    130   for (size_t i = 0; i < profile_count; ++i) {
    131     AvatarMenuModel::Item menu_item = avatar_menu_model_->GetItemAt(i);
    132     AvatarMenuItemGtk* item = new AvatarMenuItemGtk(
    133         this, menu_item, i, theme_service_);
    134     items_.push_back(item);
    135 
    136     gtk_box_pack_start(GTK_BOX(items_vbox), item->widget(), TRUE, TRUE, 0);
    137     gtk_widget_set_can_focus(item->widget(), TRUE);
    138     if (menu_item.active)
    139       gtk_container_set_focus_child(GTK_CONTAINER(items_vbox), item->widget());
    140   }
    141   gtk_box_pack_start(GTK_BOX(inner_contents_), items_vbox, TRUE, TRUE, 0);
    142 
    143   if (avatar_menu_model_->ShouldShowAddNewProfileLink()) {
    144     gtk_box_pack_start(GTK_BOX(inner_contents_),
    145                        gtk_hseparator_new(), TRUE, TRUE, 0);
    146 
    147     // The new profile link.
    148     new_profile_link_ = theme_service_->BuildChromeLinkButton(
    149         l10n_util::GetStringUTF8(IDS_PROFILES_CREATE_NEW_PROFILE_LINK));
    150     g_signal_connect(new_profile_link_, "clicked",
    151                      G_CALLBACK(OnNewProfileLinkClickedThunk), this);
    152 
    153     GtkWidget* link_align = gtk_alignment_new(0, 0, 0, 0);
    154     gtk_alignment_set_padding(GTK_ALIGNMENT(link_align),
    155                               0, 0, kNewProfileLinkLeftPadding, 0);
    156     gtk_container_add(GTK_CONTAINER(link_align), new_profile_link_);
    157 
    158     gtk_box_pack_start(GTK_BOX(inner_contents_), link_align, FALSE, FALSE, 0);
    159   }
    160 
    161 }
    162 
    163 void AvatarMenuBubbleGtk::InitManagedUserContents() {
    164   int active_index = avatar_menu_model_->GetActiveProfileIndex();
    165   AvatarMenuModel::Item menu_item =
    166       avatar_menu_model_->GetItemAt(active_index);
    167   AvatarMenuItemGtk* item = new AvatarMenuItemGtk(
    168       this, menu_item, active_index, theme_service_);
    169   items_.push_back(item);
    170 
    171   gtk_box_pack_start(GTK_BOX(inner_contents_), item->widget(), TRUE, TRUE, 0);
    172   gtk_box_pack_start(GTK_BOX(inner_contents_),
    173                      gtk_hseparator_new(), TRUE, TRUE, 0);
    174 
    175   // Add information about managed users within a hbox.
    176   GtkWidget* managed_user_info = gtk_hbox_new(FALSE, 5);
    177   GdkPixbuf* limited_user_pixbuf =
    178       avatar_menu_model_->GetManagedUserIcon().ToGdkPixbuf();
    179   GtkWidget* limited_user_img = gtk_image_new_from_pixbuf(limited_user_pixbuf);
    180   GtkWidget* icon_align = gtk_alignment_new(0, 0, 0, 0);
    181   gtk_container_add(GTK_CONTAINER(icon_align), limited_user_img);
    182   gtk_box_pack_start(GTK_BOX(managed_user_info), icon_align, FALSE, FALSE, 0);
    183   GtkWidget* status_label =
    184       theme_service_->BuildLabel(std::string(), ui::kGdkBlack);
    185   char* markup = g_markup_printf_escaped(
    186       "<span size='small'>%s</span>",
    187       UTF16ToUTF8(avatar_menu_model_->GetManagedUserInformation()).c_str());
    188   const int kLabelWidth = 150;
    189   gtk_widget_set_size_request(status_label, kLabelWidth, -1);
    190   gtk_label_set_markup(GTK_LABEL(status_label), markup);
    191   gtk_label_set_line_wrap(GTK_LABEL(status_label), TRUE);
    192   gtk_misc_set_alignment(GTK_MISC(status_label), 0, 0);
    193   g_free(markup);
    194   gtk_box_pack_start(GTK_BOX(managed_user_info), status_label, FALSE, FALSE, 0);
    195   gtk_box_pack_start(
    196       GTK_BOX(inner_contents_), managed_user_info, FALSE, FALSE, 0);
    197 
    198   gtk_box_pack_start(
    199       GTK_BOX(inner_contents_), gtk_hseparator_new(), TRUE, TRUE, 0);
    200 
    201   // The switch profile link.
    202   GtkWidget* switch_profile_link = theme_service_->BuildChromeLinkButton(
    203       l10n_util::GetStringUTF8(IDS_PROFILES_SWITCH_PROFILE_LINK));
    204   g_signal_connect(switch_profile_link, "clicked",
    205                    G_CALLBACK(OnSwitchProfileLinkClickedThunk), this);
    206 
    207   GtkWidget* link_align = gtk_alignment_new(0.5, 0, 0, 0);
    208   gtk_container_add(GTK_CONTAINER(link_align), switch_profile_link);
    209 
    210   gtk_box_pack_start(GTK_BOX(inner_contents_), link_align, FALSE, FALSE, 0);
    211 }
    212 
    213 void AvatarMenuBubbleGtk::InitContents() {
    214   // Destroy the old inner contents to allow replacing it.
    215   if (inner_contents_)
    216     gtk_widget_destroy(inner_contents_);
    217   inner_contents_ = gtk_vbox_new(FALSE, ui::kControlSpacing);
    218   if (!contents_)
    219     contents_ = gtk_vbox_new(FALSE, 0);
    220   gtk_container_set_border_width(GTK_CONTAINER(inner_contents_),
    221                                  ui::kContentAreaBorder);
    222   g_signal_connect(inner_contents_, "size-request",
    223                    G_CALLBACK(OnSizeRequestThunk), this);
    224 
    225   if (avatar_menu_model_->GetManagedUserInformation().empty() || switching_)
    226     InitMenuContents();
    227   else
    228     InitManagedUserContents();
    229   gtk_box_pack_start(GTK_BOX(contents_), inner_contents_, TRUE, TRUE, 0);
    230   if (bubble_)
    231     gtk_widget_show_all(contents_);
    232 }
    233 
    234 void AvatarMenuBubbleGtk::CloseBubble() {
    235   if (bubble_) {
    236     bubble_->Close();
    237     bubble_ = NULL;
    238   }
    239 }
    240