1// Copyright (c) 2011 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/collected_cookies_win.h"
6
7#include "chrome/browser/cookies_tree_model.h"
8#include "chrome/browser/profiles/profile.h"
9#include "chrome/browser/ui/collected_cookies_infobar_delegate.h"
10#include "chrome/browser/ui/views/cookie_info_view.h"
11#include "content/browser/tab_contents/tab_contents.h"
12#include "content/common/notification_details.h"
13#include "content/common/notification_source.h"
14#include "grit/generated_resources.h"
15#include "grit/locale_settings.h"
16#include "grit/theme_resources.h"
17#include "ui/base/l10n/l10n_util.h"
18#include "ui/base/resource/resource_bundle.h"
19#include "ui/gfx/color_utils.h"
20#include "views/controls/button/native_button.h"
21#include "views/controls/image_view.h"
22#include "views/controls/label.h"
23#include "views/controls/separator.h"
24#include "views/controls/tabbed_pane/tabbed_pane.h"
25#include "views/layout/box_layout.h"
26#include "views/layout/grid_layout.h"
27#include "views/layout/layout_constants.h"
28#include "views/widget/root_view.h"
29#include "views/widget/widget_win.h"
30#include "views/window/window.h"
31
32namespace browser {
33
34// Declared in browser_dialogs.h so others don't have to depend on our header.
35void ShowCollectedCookiesDialog(gfx::NativeWindow parent_window,
36                                TabContents* tab_contents) {
37  // Deletes itself on close.
38  new CollectedCookiesWin(parent_window, tab_contents);
39}
40
41}  // namespace browser
42
43namespace {
44// Spacing between the infobar frame and its contents.
45const int kInfobarVerticalPadding = 3;
46const int kInfobarHorizontalPadding = 8;
47
48// Width of the infobar frame.
49const int kInfobarBorderSize = 1;
50
51// Dimensions of the tree views.
52const int kTreeViewWidth = 400;
53const int kTreeViewHeight = 125;
54
55}  // namespace
56
57// A custom view that conditionally displays an infobar.
58class InfobarView : public views::View {
59 public:
60  InfobarView() {
61    content_ = new views::View;
62    SkColor border_color = color_utils::GetSysSkColor(COLOR_3DSHADOW);
63    views::Border* border = views::Border::CreateSolidBorder(
64        kInfobarBorderSize, border_color);
65    content_->set_border(border);
66
67    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
68    info_image_ = new views::ImageView();
69    info_image_->SetImage(rb.GetBitmapNamed(IDR_INFO));
70    label_ = new views::Label();
71  }
72  virtual ~InfobarView() {}
73
74  // Update the visibility of the infobar. If |is_visible| is true, a rule for
75  // |setting| on |domain_name| was created.
76  void UpdateVisibility(bool is_visible,
77                        ContentSetting setting,
78                        const std::wstring& domain_name) {
79    if (!is_visible) {
80      SetVisible(false);
81      return;
82    }
83
84    std::wstring label;
85    switch (setting) {
86      case CONTENT_SETTING_BLOCK:
87        label = UTF16ToWide(l10n_util::GetStringFUTF16(
88            IDS_COLLECTED_COOKIES_BLOCK_RULE_CREATED,
89            WideToUTF16(domain_name)));
90        break;
91
92      case CONTENT_SETTING_ALLOW:
93        label = UTF16ToWide(l10n_util::GetStringFUTF16(
94            IDS_COLLECTED_COOKIES_ALLOW_RULE_CREATED,
95            WideToUTF16(domain_name)));
96        break;
97
98      case CONTENT_SETTING_SESSION_ONLY:
99        label = UTF16ToWide(l10n_util::GetStringFUTF16(
100            IDS_COLLECTED_COOKIES_SESSION_RULE_CREATED,
101            WideToUTF16(domain_name)));
102        break;
103
104      default:
105        NOTREACHED();
106    }
107    label_->SetText(label);
108    content_->Layout();
109    SetVisible(true);
110  }
111
112 private:
113  // Initialize contents and layout.
114  void Init() {
115    AddChildView(content_);
116    content_->SetLayoutManager(
117        new views::BoxLayout(views::BoxLayout::kHorizontal,
118                             kInfobarHorizontalPadding,
119                             kInfobarVerticalPadding,
120                             views::kRelatedControlSmallHorizontalSpacing));
121    content_->AddChildView(info_image_);
122    content_->AddChildView(label_);
123    UpdateVisibility(false, CONTENT_SETTING_BLOCK, std::wstring());
124  }
125
126  // views::View overrides.
127  virtual gfx::Size GetPreferredSize() {
128    if (!IsVisible())
129      return gfx::Size();
130
131    // Add space around the banner.
132    gfx::Size size(content_->GetPreferredSize());
133    size.Enlarge(0, 2 * views::kRelatedControlVerticalSpacing);
134    return size;
135  }
136
137  virtual void Layout() {
138    content_->SetBounds(
139        0, views::kRelatedControlVerticalSpacing,
140        width(), height() - views::kRelatedControlVerticalSpacing);
141  }
142
143  virtual void ViewHierarchyChanged(bool is_add,
144                                    views::View* parent,
145                                    views::View* child) {
146    if (is_add && child == this)
147      Init();
148  }
149
150  // Holds the info icon image and text label and renders the border.
151  views::View* content_;
152  // Info icon image.
153  views::ImageView* info_image_;
154  // The label responsible for rendering the text.
155  views::Label* label_;
156
157  DISALLOW_COPY_AND_ASSIGN(InfobarView);
158};
159
160///////////////////////////////////////////////////////////////////////////////
161// CollectedCookiesWin, constructor and destructor:
162
163CollectedCookiesWin::CollectedCookiesWin(gfx::NativeWindow parent_window,
164                                         TabContents* tab_contents)
165    : tab_contents_(tab_contents),
166      allowed_label_(NULL),
167      blocked_label_(NULL),
168      allowed_cookies_tree_(NULL),
169      blocked_cookies_tree_(NULL),
170      block_allowed_button_(NULL),
171      allow_blocked_button_(NULL),
172      for_session_blocked_button_(NULL),
173      infobar_(NULL),
174      status_changed_(false) {
175  TabSpecificContentSettings* content_settings =
176      tab_contents->GetTabSpecificContentSettings();
177  registrar_.Add(this, NotificationType::COLLECTED_COOKIES_SHOWN,
178                 Source<TabSpecificContentSettings>(content_settings));
179
180  Init();
181
182  window_ = tab_contents_->CreateConstrainedDialog(this);
183}
184
185CollectedCookiesWin::~CollectedCookiesWin() {
186  allowed_cookies_tree_->SetModel(NULL);
187  blocked_cookies_tree_->SetModel(NULL);
188}
189
190void CollectedCookiesWin::Init() {
191  using views::GridLayout;
192
193  GridLayout* layout = GridLayout::CreatePanel(this);
194  SetLayoutManager(layout);
195
196  const int single_column_layout_id = 0;
197  views::ColumnSet* column_set = layout->AddColumnSet(single_column_layout_id);
198  column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
199                        GridLayout::USE_PREF, 0, 0);
200
201  const int single_column_with_padding_layout_id = 1;
202  views::ColumnSet* column_set_with_padding = layout->AddColumnSet(
203      single_column_with_padding_layout_id);
204  column_set_with_padding->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
205                                     GridLayout::USE_PREF, 0, 0);
206  column_set_with_padding->AddPaddingColumn(0, 2);
207
208  layout->StartRow(0, single_column_layout_id);
209  views::TabbedPane* tabbed_pane = new views::TabbedPane();
210  layout->AddView(tabbed_pane);
211  // NOTE: the panes need to be added after the tabbed_pane has been added to
212  // its parent.
213  std::wstring label_allowed = UTF16ToWide(l10n_util::GetStringUTF16(
214      IDS_COLLECTED_COOKIES_ALLOWED_COOKIES_TAB_LABEL));
215  std::wstring label_blocked = UTF16ToWide(l10n_util::GetStringUTF16(
216      IDS_COLLECTED_COOKIES_BLOCKED_COOKIES_TAB_LABEL));
217  tabbed_pane->AddTab(label_allowed, CreateAllowedPane());
218  tabbed_pane->AddTab(label_blocked, CreateBlockedPane());
219  tabbed_pane->SelectTabAt(0);
220  tabbed_pane->set_listener(this);
221  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
222
223  layout->StartRow(0, single_column_with_padding_layout_id);
224  cookie_info_view_ = new CookieInfoView(false);
225  layout->AddView(cookie_info_view_);
226  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
227
228  layout->StartRow(0, single_column_with_padding_layout_id);
229  infobar_ = new InfobarView();
230  layout->AddView(infobar_);
231
232  EnableControls();
233  ShowCookieInfo();
234}
235
236views::View* CollectedCookiesWin::CreateAllowedPane() {
237  TabSpecificContentSettings* content_settings =
238      tab_contents_->GetTabSpecificContentSettings();
239
240  // Create the controls that go into the pane.
241  allowed_label_ = new views::Label(UTF16ToWide(l10n_util::GetStringUTF16(
242      IDS_COLLECTED_COOKIES_ALLOWED_COOKIES_LABEL)));
243  allowed_cookies_tree_model_.reset(
244      content_settings->GetAllowedCookiesTreeModel());
245  allowed_cookies_tree_ = new views::TreeView();
246  allowed_cookies_tree_->SetModel(allowed_cookies_tree_model_.get());
247  allowed_cookies_tree_->SetController(this);
248  allowed_cookies_tree_->SetRootShown(false);
249  allowed_cookies_tree_->SetEditable(false);
250  allowed_cookies_tree_->set_lines_at_root(true);
251  allowed_cookies_tree_->set_auto_expand_children(true);
252
253  block_allowed_button_ = new views::NativeButton(this, UTF16ToWide(
254      l10n_util::GetStringUTF16(IDS_COLLECTED_COOKIES_BLOCK_BUTTON)));
255
256  // Create the view that holds all the controls together.  This will be the
257  // pane added to the tabbed pane.
258  using views::GridLayout;
259
260  views::View* pane = new views::View();
261  GridLayout* layout = GridLayout::CreatePanel(pane);
262  pane->SetLayoutManager(layout);
263
264  const int single_column_layout_id = 0;
265  views::ColumnSet* column_set = layout->AddColumnSet(single_column_layout_id);
266  column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
267                        GridLayout::USE_PREF, 0, 0);
268
269  layout->StartRow(0, single_column_layout_id);
270  layout->AddView(allowed_label_);
271  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
272
273  layout->StartRow(1, single_column_layout_id);
274  layout->AddView(allowed_cookies_tree_, 1, 1, GridLayout::FILL,
275                  GridLayout::FILL, kTreeViewWidth, kTreeViewHeight);
276  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
277
278  layout->StartRow(0, single_column_layout_id);
279  layout->AddView(block_allowed_button_, 1, 1, GridLayout::LEADING,
280                  GridLayout::CENTER);
281
282  return pane;
283}
284
285views::View* CollectedCookiesWin::CreateBlockedPane() {
286  TabSpecificContentSettings* content_settings =
287      tab_contents_->GetTabSpecificContentSettings();
288
289  HostContentSettingsMap* host_content_settings_map =
290      tab_contents_->profile()->GetHostContentSettingsMap();
291
292  // Create the controls that go into the pane.
293  blocked_label_ = new views::Label(
294      UTF16ToWide(l10n_util::GetStringUTF16(
295          host_content_settings_map->BlockThirdPartyCookies() ?
296              IDS_COLLECTED_COOKIES_BLOCKED_THIRD_PARTY_BLOCKING_ENABLED :
297              IDS_COLLECTED_COOKIES_BLOCKED_COOKIES_LABEL)));
298  blocked_label_->SetMultiLine(true);
299  blocked_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
300  blocked_cookies_tree_model_.reset(
301      content_settings->GetBlockedCookiesTreeModel());
302  blocked_cookies_tree_ = new views::TreeView();
303  blocked_cookies_tree_->SetModel(blocked_cookies_tree_model_.get());
304  blocked_cookies_tree_->SetController(this);
305  blocked_cookies_tree_->SetRootShown(false);
306  blocked_cookies_tree_->SetEditable(false);
307  blocked_cookies_tree_->set_lines_at_root(true);
308  blocked_cookies_tree_->set_auto_expand_children(true);
309
310  allow_blocked_button_ = new views::NativeButton(this, UTF16ToWide(
311      l10n_util::GetStringUTF16(IDS_COLLECTED_COOKIES_ALLOW_BUTTON)));
312  for_session_blocked_button_ = new views::NativeButton(this, UTF16ToWide(
313      l10n_util::GetStringUTF16(IDS_COLLECTED_COOKIES_SESSION_ONLY_BUTTON)));
314
315  // Create the view that holds all the controls together.  This will be the
316  // pane added to the tabbed pane.
317  using views::GridLayout;
318
319  views::View* pane = new views::View();
320  GridLayout* layout = GridLayout::CreatePanel(pane);
321  pane->SetLayoutManager(layout);
322
323  const int single_column_layout_id = 0;
324  views::ColumnSet* column_set = layout->AddColumnSet(single_column_layout_id);
325  column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
326                        GridLayout::USE_PREF, 0, 0);
327
328  const int three_columns_layout_id = 1;
329  column_set = layout->AddColumnSet(three_columns_layout_id);
330  column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
331                        GridLayout::USE_PREF, 0, 0);
332  column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
333  column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
334                        GridLayout::USE_PREF, 0, 0);
335
336  layout->StartRow(0, single_column_layout_id);
337  layout->AddView(blocked_label_, 1, 1, GridLayout::FILL, GridLayout::FILL);
338  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
339
340  layout->StartRow(1, single_column_layout_id);
341  layout->AddView(
342      blocked_cookies_tree_, 1, 1, GridLayout::FILL, GridLayout::FILL,
343      kTreeViewWidth, kTreeViewHeight);
344  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
345
346  layout->StartRow(0, three_columns_layout_id);
347  layout->AddView(allow_blocked_button_);
348  layout->AddView(for_session_blocked_button_);
349
350  return pane;
351}
352
353///////////////////////////////////////////////////////////////////////////////
354// ConstrainedDialogDelegate implementation.
355
356std::wstring CollectedCookiesWin::GetWindowTitle() const {
357  return UTF16ToWide(
358      l10n_util::GetStringUTF16(IDS_COLLECTED_COOKIES_DIALOG_TITLE));
359}
360
361int CollectedCookiesWin::GetDialogButtons() const {
362  return MessageBoxFlags::DIALOGBUTTON_CANCEL;
363}
364
365std::wstring CollectedCookiesWin::GetDialogButtonLabel(
366    MessageBoxFlags::DialogButton button) const {
367  return UTF16ToWide(l10n_util::GetStringUTF16(IDS_CLOSE));
368}
369
370void CollectedCookiesWin::DeleteDelegate() {
371  delete this;
372}
373
374bool CollectedCookiesWin::Cancel() {
375  if (status_changed_) {
376    tab_contents_->AddInfoBar(
377        new CollectedCookiesInfoBarDelegate(tab_contents_));
378  }
379
380  return true;
381}
382
383views::View* CollectedCookiesWin::GetContentsView() {
384  return this;
385}
386
387///////////////////////////////////////////////////////////////////////////////
388// views::ButtonListener implementation.
389
390void CollectedCookiesWin::ButtonPressed(views::Button* sender,
391                                        const views::Event& event) {
392  if (sender == block_allowed_button_)
393    AddContentException(allowed_cookies_tree_, CONTENT_SETTING_BLOCK);
394  else if (sender == allow_blocked_button_)
395    AddContentException(blocked_cookies_tree_, CONTENT_SETTING_ALLOW);
396  else if (sender == for_session_blocked_button_)
397    AddContentException(blocked_cookies_tree_, CONTENT_SETTING_SESSION_ONLY);
398}
399
400///////////////////////////////////////////////////////////////////////////////
401// views::TabbedPaneListener implementation.
402
403void CollectedCookiesWin::TabSelectedAt(int index) {
404  EnableControls();
405  ShowCookieInfo();
406}
407
408///////////////////////////////////////////////////////////////////////////////
409// views::TreeViewController implementation.
410
411void CollectedCookiesWin::OnTreeViewSelectionChanged(
412    views::TreeView* tree_view) {
413  EnableControls();
414  ShowCookieInfo();
415}
416
417///////////////////////////////////////////////////////////////////////////////
418// CollectedCookiesWin, private methods.
419
420void CollectedCookiesWin::EnableControls() {
421  bool enable_allowed_buttons = false;
422  ui::TreeModelNode* node = allowed_cookies_tree_->GetSelectedNode();
423  if (node) {
424    CookieTreeNode* cookie_node = static_cast<CookieTreeNode*>(node);
425    if (cookie_node->GetDetailedInfo().node_type ==
426        CookieTreeNode::DetailedInfo::TYPE_ORIGIN) {
427      enable_allowed_buttons = static_cast<CookieTreeOriginNode*>(
428          cookie_node)->CanCreateContentException();
429    }
430  }
431  block_allowed_button_->SetEnabled(enable_allowed_buttons);
432
433  bool enable_blocked_buttons = false;
434  node = blocked_cookies_tree_->GetSelectedNode();
435  if (node) {
436    CookieTreeNode* cookie_node = static_cast<CookieTreeNode*>(node);
437    if (cookie_node->GetDetailedInfo().node_type ==
438        CookieTreeNode::DetailedInfo::TYPE_ORIGIN) {
439      enable_blocked_buttons = static_cast<CookieTreeOriginNode*>(
440          cookie_node)->CanCreateContentException();
441    }
442  }
443  allow_blocked_button_->SetEnabled(enable_blocked_buttons);
444  for_session_blocked_button_->SetEnabled(enable_blocked_buttons);
445}
446
447void CollectedCookiesWin::ShowCookieInfo() {
448  ui::TreeModelNode* node = allowed_cookies_tree_->GetSelectedNode();
449  if (!node)
450    node = blocked_cookies_tree_->GetSelectedNode();
451
452  if (node) {
453    CookieTreeNode* cookie_node = static_cast<CookieTreeNode*>(node);
454    const CookieTreeNode::DetailedInfo detailed_info =
455        cookie_node->GetDetailedInfo();
456
457    if (detailed_info.node_type == CookieTreeNode::DetailedInfo::TYPE_COOKIE) {
458      CookieTreeCookieNode* cookie_info_node =
459          static_cast<CookieTreeCookieNode*>(cookie_node);
460      cookie_info_view_->SetCookie(detailed_info.cookie->Domain(),
461                                   *detailed_info.cookie);
462    } else {
463      cookie_info_view_->ClearCookieDisplay();
464    }
465  } else {
466    cookie_info_view_->ClearCookieDisplay();
467  }
468}
469
470void CollectedCookiesWin::AddContentException(views::TreeView* tree_view,
471                                              ContentSetting setting) {
472  CookieTreeOriginNode* origin_node =
473      static_cast<CookieTreeOriginNode*>(tree_view->GetSelectedNode());
474  origin_node->CreateContentException(
475      tab_contents_->profile()->GetHostContentSettingsMap(), setting);
476  infobar_->UpdateVisibility(true, setting, origin_node->GetTitle());
477  gfx::Rect bounds = GetWidget()->GetClientAreaScreenBounds();
478  // WidgetWin::GetBounds returns the bounds relative to the parent window,
479  // while WidgetWin::SetBounds wants screen coordinates. Do the translation
480  // here until http://crbug.com/52851 is fixed.
481  POINT topleft = {bounds.x(), bounds.y()};
482  MapWindowPoints(HWND_DESKTOP, tab_contents_->GetNativeView(), &topleft, 1);
483  gfx::Size size = GetRootView()->GetPreferredSize();
484  bounds.SetRect(topleft.x, topleft.y, size.width(), size.height());
485  GetWidget()->SetBounds(bounds);
486  status_changed_ = true;
487}
488
489///////////////////////////////////////////////////////////////////////////////
490// NotificationObserver implementation.
491
492void CollectedCookiesWin::Observe(NotificationType type,
493                                   const NotificationSource& source,
494                                   const NotificationDetails& details) {
495  DCHECK(type == NotificationType::COLLECTED_COOKIES_SHOWN);
496  DCHECK_EQ(Source<TabSpecificContentSettings>(source).ptr(),
497            tab_contents_->GetTabSpecificContentSettings());
498  window_->CloseConstrainedWindow();
499}
500