root/chrome/browser/ui/gtk/content_setting_bubble_gtk.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. BuildElidedText
  2. bubble_
  3. Close
  4. UpdateMenuLabel
  5. BubbleClosing
  6. BuildBubble
  7. OnPopupIconButtonPress
  8. OnPopupLinkClicked
  9. OnRadioToggled
  10. OnCloseButtonClicked
  11. OnCustomLinkClicked
  12. OnManageLinkClicked
  13. OnMenuButtonClicked

// Copyright (c) 2012 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 "chrome/browser/ui/gtk/content_setting_bubble_gtk.h"

#include <set>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/i18n/rtl.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/content_settings/host_content_settings_map.h"
#include "chrome/browser/plugins/plugin_finder.h"
#include "chrome/browser/plugins/plugin_metadata.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
#include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h"
#include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/common/content_settings.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/web_contents.h"
#include "grit/generated_resources.h"
#include "grit/ui_resources.h"
#include "ui/base/gtk/gtk_hig_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/text_elider.h"

using content::PluginService;
using content::WebContents;

namespace {

// The maximum width of a title entry in the content box. We elide anything
// longer than this.
const int kMaxLinkPixelSize = 500;

// The minimum and maximum width of the media menu buttons.
const int kMinMediaMenuButtonWidth = 100;
const int kMaxMediaMenuButtonWidth = 600;

std::string BuildElidedText(const std::string& input) {
  return base::UTF16ToUTF8(gfx::ElideText(
      base::UTF8ToUTF16(input),
      gfx::FontList(),
      kMaxLinkPixelSize,
      gfx::ELIDE_AT_END));
}

}  // namespace

ContentSettingBubbleGtk::ContentSettingBubbleGtk(
    GtkWidget* anchor,
    BubbleDelegateGtk* delegate,
    ContentSettingBubbleModel* content_setting_bubble_model,
    Profile* profile)
    : anchor_(anchor),
      profile_(profile),
      delegate_(delegate),
      content_setting_bubble_model_(content_setting_bubble_model),
      bubble_(NULL) {
  BuildBubble();
}

ContentSettingBubbleGtk::~ContentSettingBubbleGtk() {
}

void ContentSettingBubbleGtk::Close() {
  STLDeleteValues(&media_menus_);

  if (bubble_)
    bubble_->Close();
}

void ContentSettingBubbleGtk::UpdateMenuLabel(content::MediaStreamType type,
                                              const std::string& label) {
  GtkMediaMenuMap::const_iterator it = media_menus_.begin();
  for (; it != media_menus_.end(); ++it) {
    if (it->second->type == type) {
      gtk_label_set_text(GTK_LABEL(it->second->label.get()), label.c_str());
      return;
    }
  }
  NOTREACHED();
}

void ContentSettingBubbleGtk::BubbleClosing(BubbleGtk* bubble,
                                            bool closed_by_escape) {
  delegate_->BubbleClosing(bubble, closed_by_escape);
  delete this;
}

void ContentSettingBubbleGtk::BuildBubble() {
  GtkThemeService* theme_provider = GtkThemeService::GetFrom(profile_);

  GtkWidget* bubble_content = gtk_vbox_new(FALSE, ui::kControlSpacing);
  gtk_container_set_border_width(GTK_CONTAINER(bubble_content),
                                 ui::kContentAreaBorder);

  const ContentSettingBubbleModel::BubbleContent& content =
      content_setting_bubble_model_->bubble_content();
  if (!content.title.empty()) {
    // Add the content label.
    GtkWidget* label = theme_provider->BuildLabel(content.title.c_str(),
                                                  ui::kGdkBlack);
    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
    gtk_box_pack_start(GTK_BOX(bubble_content), label, FALSE, FALSE, 0);
  }

  if (content_setting_bubble_model_->content_type() ==
      CONTENT_SETTINGS_TYPE_POPUPS) {
    const std::vector<ContentSettingBubbleModel::PopupItem>& popup_items =
        content.popup_items;
    GtkWidget* table = gtk_table_new(popup_items.size(), 2, FALSE);
    int row = 0;
    for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
         i(popup_items.begin()); i != popup_items.end(); ++i, ++row) {
      GtkWidget* image = gtk_image_new();
      if (!i->image.IsEmpty()) {
        GdkPixbuf* icon_pixbuf = i->image.ToGdkPixbuf();
        gtk_image_set_from_pixbuf(GTK_IMAGE(image), icon_pixbuf);

        // We stuff the image in an event box so we can trap mouse clicks on the
        // image (and launch the popup).
        GtkWidget* event_box = gtk_event_box_new();
        gtk_container_add(GTK_CONTAINER(event_box), image);

        popup_icons_[event_box] = i -popup_items.begin();
        g_signal_connect(event_box, "button_press_event",
                         G_CALLBACK(OnPopupIconButtonPressThunk), this);
        gtk_table_attach(GTK_TABLE(table), event_box, 0, 1, row, row + 1,
                         GTK_FILL, GTK_FILL, ui::kControlSpacing / 2,
                         ui::kControlSpacing / 2);
      }

      GtkWidget* button = gtk_chrome_link_button_new(
          BuildElidedText(i->title).c_str());
      popup_links_[button] = i -popup_items.begin();
      g_signal_connect(button, "clicked", G_CALLBACK(OnPopupLinkClickedThunk),
                       this);
      gtk_table_attach(GTK_TABLE(table), button, 1, 2, row, row + 1,
                       GTK_FILL, GTK_FILL, ui::kControlSpacing / 2,
                       ui::kControlSpacing / 2);
    }

    gtk_box_pack_start(GTK_BOX(bubble_content), table, FALSE, FALSE, 0);
  }

  const ContentSettingBubbleModel::RadioGroup& radio_group =
      content.radio_group;
  for (ContentSettingBubbleModel::RadioItems::const_iterator i =
       radio_group.radio_items.begin();
       i != radio_group.radio_items.end(); ++i) {
    std::string elided = BuildElidedText(*i);
    GtkWidget* radio = radio_group_gtk_.empty() ?
        gtk_radio_button_new(NULL) :
        gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(radio_group_gtk_[0]));
    GtkWidget* label =
        theme_provider->BuildLabel(elided.c_str(), ui::kGdkBlack);
    gtk_container_add(GTK_CONTAINER(radio), label);
    gtk_box_pack_start(GTK_BOX(bubble_content), radio, FALSE, FALSE, 0);
    if (i - radio_group.radio_items.begin() == radio_group.default_item) {
      // We must set the default value before we attach the signal handlers
      // or pain occurs.
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
    }
    if (!content.radio_group_enabled)
      gtk_widget_set_sensitive(radio, FALSE);
    radio_group_gtk_.push_back(radio);
  }
  for (std::vector<GtkWidget*>::const_iterator i = radio_group_gtk_.begin();
       i != radio_group_gtk_.end(); ++i) {
    // We can attach signal handlers now that all defaults are set.
    g_signal_connect(*i, "toggled", G_CALLBACK(OnRadioToggledThunk), this);
  }

  // Layout code for the media device menus.
  if (content_setting_bubble_model_->content_type() ==
          CONTENT_SETTINGS_TYPE_MEDIASTREAM) {
    GtkWidget* table = gtk_table_new(content.media_menus.size(), 2, FALSE);
    int menu_width = 0;
    int row = 0;
    for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i(
             content.media_menus.begin());
         i != content.media_menus.end();
         ++i, ++row) {
      GtkWidget* label = theme_provider->BuildLabel(
          i->second.label.c_str(), ui::kGdkBlack);
      gtk_table_attach(GTK_TABLE(table), gtk_util::LeftAlignMisc(label), 0, 1,
                       row, row + 1, GTK_FILL, GTK_FILL,
                       ui::kControlSpacing / 2, ui::kControlSpacing / 2);

      // Build up the gtk menu button.
      MediaMenuGtk* gtk_menu = new MediaMenuGtk(i->first);
      gtk_menu->label.Own(
          gtk_label_new(i->second.selected_device.name.c_str()));
      GtkWidget* button = gtk_button_new();
      GtkWidget* button_content = gtk_hbox_new(FALSE, 0);
      gtk_box_pack_start(GTK_BOX(button_content),
                         gtk_util::LeftAlignMisc(gtk_menu->label.get()),
                         FALSE, FALSE, 0);
      GtkWidget* arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
      gtk_box_pack_end(GTK_BOX(button_content), arrow, FALSE, FALSE, 0);

      gtk_container_add(GTK_CONTAINER(button), button_content);

      // Store the gtk menu to the map.
      gtk_menu->menu_model.reset(new ContentSettingMediaMenuModel(
          gtk_menu->type,
          content_setting_bubble_model_.get(),
          base::Bind(&ContentSettingBubbleGtk::UpdateMenuLabel,
                     base::Unretained(this))));
      gtk_menu->menu.reset(new MenuGtk(NULL, gtk_menu->menu_model.get()));
      media_menus_[button] = gtk_menu;

      if (!gtk_menu->menu_model->GetItemCount()) {
        // Show a "None available" title and grey out the menu when there is
        // no available device.
        UpdateMenuLabel(
            gtk_menu->type,
            l10n_util::GetStringUTF8(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
        gtk_widget_set_sensitive(button, FALSE);
      }

      // Disable the device selection when the website is managing the devices
      // itself.
      if (i->second.disabled)
        gtk_widget_set_sensitive(button, FALSE);

      // Use the longest width of the menus as the width of the menu buttons.
      GtkRequisition menu_req;
      gtk_widget_size_request(gtk_menu->menu->widget(), &menu_req);
      menu_width = std::max(menu_width, menu_req.width);

      g_signal_connect(button, "clicked",
                       G_CALLBACK(OnMenuButtonClickedThunk), this);
      gtk_table_attach(GTK_TABLE(table), button, 1, 2, row, row + 1,
                       GTK_FILL, GTK_FILL, ui::kControlSpacing * 2,
                       ui::kControlSpacing / 2);
    }

    // Make sure the width is within [kMinMediaMenuButtonWidth,
    // kMaxMediaMenuButtonWidth].
    menu_width = std::max(kMinMediaMenuButtonWidth, menu_width);
    menu_width = std::min(kMaxMediaMenuButtonWidth, menu_width);

    // Set all the menu buttons to the width we calculated above.
    for (GtkMediaMenuMap::const_iterator i = media_menus_.begin();
         i != media_menus_.end(); ++i)
      gtk_widget_set_size_request(i->first, menu_width, -1);

    gtk_box_pack_start(GTK_BOX(bubble_content), table, FALSE, FALSE, 0);
  }

  for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i =
       content.domain_lists.begin();
       i != content.domain_lists.end(); ++i) {
    // Put each list into its own vbox to allow spacing between lists.
    GtkWidget* list_content = gtk_vbox_new(FALSE, ui::kControlSpacing);

    GtkWidget* label = theme_provider->BuildLabel(
        BuildElidedText(i->title).c_str(), ui::kGdkBlack);
    gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
    GtkWidget* label_box = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(label_box), label, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(list_content), label_box, FALSE, FALSE, 0);
    for (std::set<std::string>::const_iterator j = i->hosts.begin();
         j != i->hosts.end(); ++j) {
      gtk_box_pack_start(GTK_BOX(list_content),
                         gtk_util::IndentWidget(gtk_util::CreateBoldLabel(*j)),
                         FALSE, FALSE, 0);
    }
    gtk_box_pack_start(GTK_BOX(bubble_content), list_content, FALSE, FALSE,
                       ui::kControlSpacing);
  }

  if (!content.custom_link.empty()) {
    GtkWidget* custom_link_box = gtk_hbox_new(FALSE, 0);
    GtkWidget* custom_link = NULL;
    if (content.custom_link_enabled) {
      custom_link =
          theme_provider->BuildChromeLinkButton(content.custom_link.c_str());
      g_signal_connect(custom_link, "clicked",
                       G_CALLBACK(OnCustomLinkClickedThunk), this);
    } else {
      custom_link = theme_provider->BuildLabel(content.custom_link.c_str(),
                                               ui::kGdkBlack);
      gtk_misc_set_alignment(GTK_MISC(custom_link), 0, 0.5);
    }
    DCHECK(custom_link);
    gtk_box_pack_start(GTK_BOX(custom_link_box), custom_link, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(bubble_content), custom_link_box,
                       FALSE, FALSE, 0);
  }

  gtk_box_pack_start(GTK_BOX(bubble_content), gtk_hseparator_new(),
                     FALSE, FALSE, 0);

  GtkWidget* bottom_box = gtk_hbox_new(FALSE, 0);

  GtkWidget* manage_link =
      theme_provider->BuildChromeLinkButton(content.manage_link.c_str());
  g_signal_connect(manage_link, "clicked", G_CALLBACK(OnManageLinkClickedThunk),
                   this);
  gtk_box_pack_start(GTK_BOX(bottom_box), manage_link, FALSE, FALSE, 0);

  GtkWidget* button =
      gtk_button_new_with_label(l10n_util::GetStringUTF8(IDS_DONE).c_str());
  g_signal_connect(button, "clicked", G_CALLBACK(OnCloseButtonClickedThunk),
                   this);
  gtk_box_pack_end(GTK_BOX(bottom_box), button, FALSE, FALSE, 0);

  gtk_box_pack_start(GTK_BOX(bubble_content), bottom_box, FALSE, FALSE, 0);
  gtk_widget_grab_focus(button);

  bubble_ = BubbleGtk::Show(anchor_,
                            NULL,
                            bubble_content,
                            BubbleGtk::ANCHOR_TOP_RIGHT,
                            BubbleGtk::MATCH_SYSTEM_THEME |
                                BubbleGtk::POPUP_WINDOW |
                                BubbleGtk::GRAB_INPUT,
                            theme_provider,
                            this);
}

void ContentSettingBubbleGtk::OnPopupIconButtonPress(
    GtkWidget* icon_event_box,
    GdkEventButton* event) {
  PopupMap::iterator i(popup_icons_.find(icon_event_box));
  DCHECK(i != popup_icons_.end());
  content_setting_bubble_model_->OnPopupClicked(i->second);
  // The views interface implicitly closes because of the launching of a new
  // window; we need to do that explicitly.
  Close();
}

void ContentSettingBubbleGtk::OnPopupLinkClicked(GtkWidget* button) {
  PopupMap::iterator i(popup_links_.find(button));
  DCHECK(i != popup_links_.end());
  content_setting_bubble_model_->OnPopupClicked(i->second);
  // The views interface implicitly closes because of the launching of a new
  // window; we need to do that explicitly.
  Close();
}

void ContentSettingBubbleGtk::OnRadioToggled(GtkWidget* widget) {
  for (ContentSettingBubbleGtk::RadioGroupGtk::const_iterator i =
       radio_group_gtk_.begin();
       i != radio_group_gtk_.end(); ++i) {
    if (widget == *i) {
      content_setting_bubble_model_->OnRadioClicked(
          i - radio_group_gtk_.begin());
      return;
    }
  }
  NOTREACHED() << "unknown radio toggled";
}

void ContentSettingBubbleGtk::OnCloseButtonClicked(GtkWidget* button) {
  content_setting_bubble_model_->OnDoneClicked();
  Close();
}

void ContentSettingBubbleGtk::OnCustomLinkClicked(GtkWidget* button) {
  content_setting_bubble_model_->OnCustomLinkClicked();
  Close();
}

void ContentSettingBubbleGtk::OnManageLinkClicked(GtkWidget* button) {
  content_setting_bubble_model_->OnManageLinkClicked();
  Close();
}

void ContentSettingBubbleGtk::OnMenuButtonClicked(GtkWidget* button) {
  GtkMediaMenuMap::iterator i(media_menus_.find(button));
  DCHECK(i != media_menus_.end());
  i->second->menu->PopupForWidget(button, 1, gtk_get_current_event_time());
}

ContentSettingBubbleGtk::MediaMenuGtk::MediaMenuGtk(
    content::MediaStreamType type)
    : type(type) {}

ContentSettingBubbleGtk::MediaMenuGtk::~MediaMenuGtk() {}

/* [<][>][^][v][top][bottom][index][help] */