root/chrome/browser/ui/views/content_setting_bubble_contents.cc

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

DEFINITIONS

This source file includes following definitions.
  1. link_
  2. OnMousePressed
  3. OnMouseReleased
  4. GetCursor
  5. close_button_
  6. GetPreferredSize
  7. UpdateMenuLabel
  8. Init
  9. ButtonPressed
  10. LinkClicked
  11. OnMenuButtonClicked
  12. GetPreferredMediaMenuWidth

// 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/views/content_setting_bubble_contents.h"

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

#include "base/bind.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/ui/content_settings/content_setting_bubble_model.h"
#include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h"
#include "chrome/browser/ui/views/browser_dialogs.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/web_contents.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/font_list.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/button/radio_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/menu/menu.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_constants.h"

#if defined(USE_AURA)
#include "ui/base/cursor/cursor.h"
#endif

namespace {

// If we don't clamp the maximum width, then very long URLs and titles can make
// the bubble arbitrarily wide.
const int kMaxContentsWidth = 500;

// When we have multiline labels, we should set a minimum width lest we get very
// narrow bubbles with lots of line-wrapping.
const int kMinMultiLineContentsWidth = 250;

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

}  // namespace

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


// ContentSettingBubbleContents::Favicon --------------------------------------

class ContentSettingBubbleContents::Favicon : public views::ImageView {
 public:
  Favicon(const gfx::Image& image,
          ContentSettingBubbleContents* parent,
          views::Link* link);
  virtual ~Favicon();

 private:
  // views::View overrides:
  virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
  virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
  virtual gfx::NativeCursor GetCursor(const ui::MouseEvent& event) OVERRIDE;

  ContentSettingBubbleContents* parent_;
  views::Link* link_;
};

ContentSettingBubbleContents::Favicon::Favicon(
    const gfx::Image& image,
    ContentSettingBubbleContents* parent,
    views::Link* link)
    : parent_(parent),
      link_(link) {
  SetImage(image.AsImageSkia());
}

ContentSettingBubbleContents::Favicon::~Favicon() {
}

bool ContentSettingBubbleContents::Favicon::OnMousePressed(
    const ui::MouseEvent& event) {
  return event.IsLeftMouseButton() || event.IsMiddleMouseButton();
}

void ContentSettingBubbleContents::Favicon::OnMouseReleased(
    const ui::MouseEvent& event) {
  if ((event.IsLeftMouseButton() || event.IsMiddleMouseButton()) &&
     HitTestPoint(event.location())) {
    parent_->LinkClicked(link_, event.flags());
  }
}

gfx::NativeCursor ContentSettingBubbleContents::Favicon::GetCursor(
    const ui::MouseEvent& event) {
#if defined(USE_AURA)
  return ui::kCursorHand;
#elif defined(OS_WIN)
  static HCURSOR g_hand_cursor = LoadCursor(NULL, IDC_HAND);
  return g_hand_cursor;
#endif
}


// ContentSettingBubbleContents::MediaMenuParts -------------------------------

struct ContentSettingBubbleContents::MediaMenuParts {
  explicit MediaMenuParts(content::MediaStreamType type);
  ~MediaMenuParts();

  content::MediaStreamType type;
  scoped_ptr<ui::SimpleMenuModel> menu_model;

 private:
  DISALLOW_COPY_AND_ASSIGN(MediaMenuParts);
};

ContentSettingBubbleContents::MediaMenuParts::MediaMenuParts(
    content::MediaStreamType type)
    : type(type) {}

ContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {}

// ContentSettingBubbleContents -----------------------------------------------

ContentSettingBubbleContents::ContentSettingBubbleContents(
    ContentSettingBubbleModel* content_setting_bubble_model,
    views::View* anchor_view,
    views::BubbleBorder::Arrow arrow)
    : BubbleDelegateView(anchor_view, arrow),
      content_setting_bubble_model_(content_setting_bubble_model),
      custom_link_(NULL),
      manage_link_(NULL),
      close_button_(NULL) {
  // Compensate for built-in vertical padding in the anchor view's image.
  set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
}

ContentSettingBubbleContents::~ContentSettingBubbleContents() {
  STLDeleteValues(&media_menus_);
}

gfx::Size ContentSettingBubbleContents::GetPreferredSize() {
  gfx::Size preferred_size(views::View::GetPreferredSize());
  int preferred_width =
      (!content_setting_bubble_model_->bubble_content().domain_lists.empty() &&
       (kMinMultiLineContentsWidth > preferred_size.width())) ?
      kMinMultiLineContentsWidth : preferred_size.width();
  preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth));
  return preferred_size;
}

void ContentSettingBubbleContents::UpdateMenuLabel(
    content::MediaStreamType type,
    const std::string& label) {
  for (MediaMenuPartsMap::const_iterator it = media_menus_.begin();
       it != media_menus_.end(); ++it) {
    if (it->second->type == type) {
      it->first->SetText(base::UTF8ToUTF16(label));
      return;
    }
  }
  NOTREACHED();
}

void ContentSettingBubbleContents::Init() {
  using views::GridLayout;

  GridLayout* layout = new views::GridLayout(this);
  SetLayoutManager(layout);

  const int kSingleColumnSetId = 0;
  views::ColumnSet* column_set = layout->AddColumnSet(kSingleColumnSetId);
  column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
                        GridLayout::USE_PREF, 0, 0);

  const ContentSettingBubbleModel::BubbleContent& bubble_content =
      content_setting_bubble_model_->bubble_content();
  bool bubble_content_empty = true;

  if (!bubble_content.title.empty()) {
    views::Label* title_label = new views::Label(base::UTF8ToUTF16(
        bubble_content.title));
    title_label->SetMultiLine(true);
    title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    layout->StartRow(0, kSingleColumnSetId);
    layout->AddView(title_label);
    bubble_content_empty = false;
  }

  if (content_setting_bubble_model_->content_type() ==
      CONTENT_SETTINGS_TYPE_POPUPS) {
    const int kPopupColumnSetId = 2;
    views::ColumnSet* popup_column_set =
        layout->AddColumnSet(kPopupColumnSetId);
    popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
                                GridLayout::USE_PREF, 0, 0);
    popup_column_set->AddPaddingColumn(
        0, views::kRelatedControlHorizontalSpacing);
    popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
                                GridLayout::USE_PREF, 0, 0);

    for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
         i(bubble_content.popup_items.begin());
         i != bubble_content.popup_items.end(); ++i) {
      if (!bubble_content_empty)
        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
      layout->StartRow(0, kPopupColumnSetId);

      views::Link* link = new views::Link(base::UTF8ToUTF16(i->title));
      link->set_listener(this);
      link->SetElideBehavior(views::Label::ELIDE_IN_MIDDLE);
      popup_links_[link] = i - bubble_content.popup_items.begin();
      layout->AddView(new Favicon(i->image, this, link));
      layout->AddView(link);
      bubble_content_empty = false;
    }
  }

  const int indented_kSingleColumnSetId = 3;
  // Insert a column set with greater indent.
  views::ColumnSet* indented_single_column_set =
      layout->AddColumnSet(indented_kSingleColumnSetId);
  indented_single_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
  indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL,
                                        1, GridLayout::USE_PREF, 0, 0);

  const ContentSettingBubbleModel::RadioGroup& radio_group =
      bubble_content.radio_group;
  if (!radio_group.radio_items.empty()) {
    if (!bubble_content_empty)
      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    for (ContentSettingBubbleModel::RadioItems::const_iterator i(
         radio_group.radio_items.begin());
         i != radio_group.radio_items.end(); ++i) {
      views::RadioButton* radio =
          new views::RadioButton(base::UTF8ToUTF16(*i), 0);
      radio->SetEnabled(bubble_content.radio_group_enabled);
      radio->set_listener(this);
      radio_group_.push_back(radio);
      layout->StartRow(0, indented_kSingleColumnSetId);
      layout->AddView(radio);
      bubble_content_empty = false;
    }
    DCHECK(!radio_group_.empty());
    // Now that the buttons have been added to the view hierarchy, it's safe
    // to call SetChecked() on them.
    radio_group_[radio_group.default_item]->SetChecked(true);
  }

  // Layout code for the media device menus.
  if (content_setting_bubble_model_->content_type() ==
      CONTENT_SETTINGS_TYPE_MEDIASTREAM) {
    const int kMediaMenuColumnSetId = 2;
    views::ColumnSet* menu_column_set =
        layout->AddColumnSet(kMediaMenuColumnSetId);
    menu_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
    menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
                               GridLayout::USE_PREF, 0, 0);
    menu_column_set->AddPaddingColumn(
        0, views::kRelatedControlHorizontalSpacing);
    menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
                               GridLayout::USE_PREF, 0, 0);

    int menu_width = 0;
    for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i(
         bubble_content.media_menus.begin());
         i != bubble_content.media_menus.end(); ++i) {
      if (!bubble_content_empty)
        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
      layout->StartRow(0, kMediaMenuColumnSetId);

      views::Label* label =
          new views::Label(base::UTF8ToUTF16(i->second.label));
      label->SetHorizontalAlignment(gfx::ALIGN_LEFT);

      views::MenuButton* menu_button = new views::MenuButton(
          NULL, base::UTF8ToUTF16((i->second.selected_device.name)),
          this, true);
      menu_button->set_alignment(views::TextButton::ALIGN_LEFT);
      menu_button->SetBorder(scoped_ptr<views::Border>(
          new views::TextButtonNativeThemeBorder(menu_button)));
      menu_button->set_animate_on_state_change(false);

      MediaMenuParts* menu_view = new MediaMenuParts(i->first);
      menu_view->menu_model.reset(new ContentSettingMediaMenuModel(
          i->first,
          content_setting_bubble_model_.get(),
          base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel,
                     base::Unretained(this))));
      media_menus_[menu_button] = menu_view;

      if (!menu_view->menu_model->GetItemCount()) {
        // Show a "None available" title and grey out the menu when there are
        // no available devices.
        menu_button->SetText(
            l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
        menu_button->SetEnabled(false);
      }

      // Disable the device selection when the website is managing the devices
      // itself.
      if (i->second.disabled)
        menu_button->SetEnabled(false);

      // Use the longest width of the menus as the width of the menu buttons.
      menu_width = std::max(menu_width,
                            GetPreferredMediaMenuWidth(
                                menu_button, menu_view->menu_model.get()));

      layout->AddView(label);
      layout->AddView(menu_button);

      bubble_content_empty = false;
    }

    // Make sure the width is at least kMinMediaMenuButtonWidth. The
    // maximum width will be clamped by kMaxContentsWidth of the view.
    menu_width = std::max(kMinMediaMenuButtonWidth, menu_width);

    // Set all the menu buttons to the width we calculated above.
    for (MediaMenuPartsMap::const_iterator i = media_menus_.begin();
         i != media_menus_.end(); ++i) {
      i->first->set_min_width(menu_width);
      i->first->set_max_width(menu_width);
    }
  }

  const gfx::FontList& domain_font =
      ui::ResourceBundle::GetSharedInstance().GetFontList(
          ui::ResourceBundle::BoldFont);
  for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i(
           bubble_content.domain_lists.begin());
       i != bubble_content.domain_lists.end(); ++i) {
    layout->StartRow(0, kSingleColumnSetId);
    views::Label* section_title = new views::Label(base::UTF8ToUTF16(i->title));
    section_title->SetMultiLine(true);
    section_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    layout->AddView(section_title, 1, 1, GridLayout::FILL, GridLayout::LEADING);
    for (std::set<std::string>::const_iterator j = i->hosts.begin();
         j != i->hosts.end(); ++j) {
      layout->StartRow(0, indented_kSingleColumnSetId);
      layout->AddView(new views::Label(base::UTF8ToUTF16(*j), domain_font));
    }
    bubble_content_empty = false;
  }

  if (!bubble_content.custom_link.empty()) {
    custom_link_ =
        new views::Link(base::UTF8ToUTF16(bubble_content.custom_link));
    custom_link_->SetEnabled(bubble_content.custom_link_enabled);
    custom_link_->set_listener(this);
    if (!bubble_content_empty)
      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    layout->StartRow(0, kSingleColumnSetId);
    layout->AddView(custom_link_);
    bubble_content_empty = false;
  }

  const int kDoubleColumnSetId = 1;
  views::ColumnSet* double_column_set =
      layout->AddColumnSet(kDoubleColumnSetId);
  if (!bubble_content_empty) {
      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
      layout->StartRow(0, kSingleColumnSetId);
      layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 1, 1,
                      GridLayout::FILL, GridLayout::FILL);
      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    }

    double_column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
                                 GridLayout::USE_PREF, 0, 0);
    double_column_set->AddPaddingColumn(
        0, views::kUnrelatedControlHorizontalSpacing);
    double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
                                 GridLayout::USE_PREF, 0, 0);

    layout->StartRow(0, kDoubleColumnSetId);
    manage_link_ =
        new views::Link(base::UTF8ToUTF16(bubble_content.manage_link));
    manage_link_->set_listener(this);
    layout->AddView(manage_link_);

    close_button_ =
        new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
    close_button_->SetStyle(views::Button::STYLE_BUTTON);
    layout->AddView(close_button_);
}

void ContentSettingBubbleContents::ButtonPressed(views::Button* sender,
                                                 const ui::Event& event) {
  RadioGroup::const_iterator i(
      std::find(radio_group_.begin(), radio_group_.end(), sender));
  if (i != radio_group_.end()) {
    content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin());
    return;
  }
  DCHECK_EQ(sender, close_button_);
  content_setting_bubble_model_->OnDoneClicked();
  GetWidget()->Close();
}

void ContentSettingBubbleContents::LinkClicked(views::Link* source,
                                               int event_flags) {
  if (source == custom_link_) {
    content_setting_bubble_model_->OnCustomLinkClicked();
    GetWidget()->Close();
    return;
  }
  if (source == manage_link_) {
    GetWidget()->Close();
    content_setting_bubble_model_->OnManageLinkClicked();
    // CAREFUL: Showing the settings window activates it, which deactivates the
    // info bubble, which causes it to close, which deletes us.
    return;
  }

  PopupLinks::const_iterator i(popup_links_.find(source));
  DCHECK(i != popup_links_.end());
  content_setting_bubble_model_->OnPopupClicked(i->second);
}

void ContentSettingBubbleContents::OnMenuButtonClicked(
    views::View* source,
    const gfx::Point& point) {
    MediaMenuPartsMap::iterator j(media_menus_.find(
        static_cast<views::MenuButton*>(source)));
    DCHECK(j != media_menus_.end());
    menu_runner_.reset(new views::MenuRunner(j->second->menu_model.get()));

    gfx::Point screen_location;
    views::View::ConvertPointToScreen(j->first, &screen_location);
    ignore_result(menu_runner_->RunMenuAt(
        source->GetWidget(),
        j->first,
        gfx::Rect(screen_location, j->first->size()),
        views::MenuItemView::TOPLEFT,
        ui::MENU_SOURCE_NONE,
        views::MenuRunner::HAS_MNEMONICS));
}

int ContentSettingBubbleContents::GetPreferredMediaMenuWidth(
    views::MenuButton* button,
    ui::SimpleMenuModel* menu_model) {
  base::string16 title = button->text();

  int width = button->GetPreferredSize().width();
  for (int i = 0; i < menu_model->GetItemCount(); ++i) {
    button->SetText(menu_model->GetLabelAt(i));
    width = std::max(width, button->GetPreferredSize().width());
  }

  // Recover the title for the menu button.
  button->SetText(title);
  return width;
}

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