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 <gtk/gtk.h>
6
7#include "build/build_config.h"
8
9#include "base/i18n/rtl.h"
10#include "base/utf_string_conversions.h"
11#include "chrome/browser/google/google_util.h"
12#include "chrome/browser/page_info_model.h"
13#include "chrome/browser/page_info_window.h"
14#include "chrome/browser/ui/browser_list.h"
15#include "chrome/browser/ui/gtk/browser_toolbar_gtk.h"
16#include "chrome/browser/ui/gtk/browser_window_gtk.h"
17#include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
18#include "chrome/browser/ui/gtk/gtk_theme_service.h"
19#include "chrome/browser/ui/gtk/gtk_util.h"
20#include "chrome/browser/ui/gtk/info_bubble_gtk.h"
21#include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
22#include "chrome/common/url_constants.h"
23#include "content/browser/certificate_viewer.h"
24#include "content/common/notification_observer.h"
25#include "content/common/notification_registrar.h"
26#include "content/common/notification_service.h"
27#include "googleurl/src/gurl.h"
28#include "grit/generated_resources.h"
29#include "grit/locale_settings.h"
30#include "ui/base/l10n/l10n_util.h"
31
32class Profile;
33
34namespace {
35
36class PageInfoBubbleGtk : public PageInfoModel::PageInfoModelObserver,
37                          public InfoBubbleGtkDelegate,
38                          public NotificationObserver {
39 public:
40  PageInfoBubbleGtk(gfx::NativeWindow parent,
41                    Profile* profile,
42                    const GURL& url,
43                    const NavigationEntry::SSLStatus& ssl,
44                    bool show_history);
45  virtual ~PageInfoBubbleGtk();
46
47  // PageInfoModelObserver implementation:
48  virtual void ModelChanged();
49
50  // NotificationObserver implementation:
51  virtual void Observe(NotificationType type,
52                       const NotificationSource& source,
53                       const NotificationDetails& details);
54
55  // InfoBubbleGtkDelegate implementation:
56  virtual void InfoBubbleClosing(InfoBubbleGtk* info_bubble,
57                                 bool closed_by_escape);
58
59 private:
60  // Layouts the different sections retrieved from the model.
61  void InitContents();
62
63  // Returns a widget that contains the UI for the passed |section|.
64  GtkWidget* CreateSection(const PageInfoModel::SectionInfo& section);
65
66  // Link button callbacks.
67  CHROMEGTK_CALLBACK_0(PageInfoBubbleGtk, void, OnViewCertLinkClicked);
68  CHROMEGTK_CALLBACK_0(PageInfoBubbleGtk, void, OnHelpLinkClicked);
69
70  // The model containing the different sections to display.
71  PageInfoModel model_;
72
73  // The url for this dialog. Should be unique among active dialogs.
74  GURL url_;
75
76  // The id of the certificate for this page.
77  int cert_id_;
78
79  // Parent window.
80  GtkWindow* parent_;
81
82  // The virtual box containing the sections.
83  GtkWidget* contents_;
84
85  // The widget relative to which we are positioned.
86  GtkWidget* anchor_;
87
88  // Provides colors and stuff.
89  GtkThemeService* theme_service_;
90
91  // The various elements in the interface we keep track of for theme changes.
92  std::vector<GtkWidget*> labels_;
93  std::vector<GtkWidget*> links_;
94
95  InfoBubbleGtk* bubble_;
96
97  NotificationRegistrar registrar_;
98
99  DISALLOW_COPY_AND_ASSIGN(PageInfoBubbleGtk);
100};
101
102PageInfoBubbleGtk::PageInfoBubbleGtk(gfx::NativeWindow parent,
103                                     Profile* profile,
104                                     const GURL& url,
105                                     const NavigationEntry::SSLStatus& ssl,
106                                     bool show_history)
107    : ALLOW_THIS_IN_INITIALIZER_LIST(model_(profile, url, ssl,
108                                            show_history, this)),
109      url_(url),
110      cert_id_(ssl.cert_id()),
111      parent_(parent),
112      contents_(NULL),
113      theme_service_(GtkThemeService::GetFrom(profile)) {
114  BrowserWindowGtk* browser_window =
115      BrowserWindowGtk::GetBrowserWindowForNativeWindow(parent);
116
117  anchor_ = browser_window->
118      GetToolbar()->GetLocationBarView()->location_icon_widget();
119
120  registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
121                 NotificationService::AllSources());
122
123  InitContents();
124
125  InfoBubbleGtk::ArrowLocationGtk arrow_location = base::i18n::IsRTL() ?
126      InfoBubbleGtk::ARROW_LOCATION_TOP_RIGHT :
127      InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT;
128  bubble_ = InfoBubbleGtk::Show(anchor_,
129                                NULL,  // |rect|
130                                contents_,
131                                arrow_location,
132                                true,  // |match_system_theme|
133                                true,  // |grab_input|
134                                theme_service_,
135                                this);  // |delegate|
136  if (!bubble_) {
137    NOTREACHED();
138    return;
139  }
140}
141
142PageInfoBubbleGtk::~PageInfoBubbleGtk() {
143}
144
145void PageInfoBubbleGtk::ModelChanged() {
146  InitContents();
147}
148
149void PageInfoBubbleGtk::Observe(NotificationType type,
150                                const NotificationSource& source,
151                                const NotificationDetails& details) {
152  DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
153
154  for (std::vector<GtkWidget*>::iterator it = links_.begin();
155       it != links_.end(); ++it) {
156    gtk_chrome_link_button_set_use_gtk_theme(
157        GTK_CHROME_LINK_BUTTON(*it),
158        theme_service_->UseGtkTheme());
159  }
160
161  if (theme_service_->UseGtkTheme()) {
162    for (std::vector<GtkWidget*>::iterator it = labels_.begin();
163         it != labels_.end(); ++it) {
164      gtk_widget_modify_fg(*it, GTK_STATE_NORMAL, NULL);
165    }
166  } else {
167    for (std::vector<GtkWidget*>::iterator it = labels_.begin();
168         it != labels_.end(); ++it) {
169      gtk_widget_modify_fg(*it, GTK_STATE_NORMAL, &gtk_util::kGdkBlack);
170    }
171  }
172}
173
174void PageInfoBubbleGtk::InfoBubbleClosing(InfoBubbleGtk* info_bubble,
175                                          bool closed_by_escape) {
176  delete this;
177}
178
179void PageInfoBubbleGtk::InitContents() {
180  if (!contents_) {
181    contents_ = gtk_vbox_new(FALSE, gtk_util::kContentAreaSpacing);
182    gtk_container_set_border_width(GTK_CONTAINER(contents_),
183                                   gtk_util::kContentAreaBorder);
184  } else {
185    labels_.clear();
186    links_.clear();
187    gtk_util::RemoveAllChildren(contents_);
188  }
189
190  for (int i = 0; i < model_.GetSectionCount(); i++) {
191    gtk_box_pack_start(GTK_BOX(contents_),
192                       CreateSection(model_.GetSectionInfo(i)),
193                       FALSE, FALSE, 0);
194    gtk_box_pack_start(GTK_BOX(contents_),
195                       gtk_hseparator_new(),
196                       FALSE, FALSE, 0);
197  }
198
199  GtkWidget* help_link = gtk_chrome_link_button_new(
200      l10n_util::GetStringUTF8(IDS_PAGE_INFO_HELP_CENTER_LINK).c_str());
201  links_.push_back(help_link);
202  GtkWidget* help_link_hbox = gtk_hbox_new(FALSE, 0);
203  // Stick it in an hbox so it doesn't expand to the whole width.
204  gtk_box_pack_start(GTK_BOX(help_link_hbox), help_link, FALSE, FALSE, 0);
205  gtk_box_pack_start(GTK_BOX(contents_), help_link_hbox, FALSE, FALSE, 0);
206  g_signal_connect(help_link, "clicked",
207                   G_CALLBACK(OnHelpLinkClickedThunk), this);
208
209  theme_service_->InitThemesFor(this);
210  gtk_widget_show_all(contents_);
211}
212
213GtkWidget* PageInfoBubbleGtk::CreateSection(
214    const PageInfoModel::SectionInfo& section) {
215  GtkWidget* section_box = gtk_hbox_new(FALSE, gtk_util::kControlSpacing);
216
217  GdkPixbuf* pixbuf = *model_.GetIconImage(section.icon_id);
218  if (pixbuf) {
219    GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf);
220    gtk_box_pack_start(GTK_BOX(section_box), image, FALSE, FALSE, 0);
221    gtk_misc_set_alignment(GTK_MISC(image), 0, 0);
222  }
223
224  GtkWidget* vbox = gtk_vbox_new(FALSE, gtk_util::kControlSpacing);
225  gtk_box_pack_start(GTK_BOX(section_box), vbox, TRUE, TRUE, 0);
226
227  if (!section.headline.empty()) {
228    GtkWidget* label = gtk_label_new(UTF16ToUTF8(section.headline).c_str());
229    gtk_label_set_selectable(GTK_LABEL(label), TRUE);
230    labels_.push_back(label);
231    PangoAttrList* attributes = pango_attr_list_new();
232    pango_attr_list_insert(attributes,
233                           pango_attr_weight_new(PANGO_WEIGHT_BOLD));
234    gtk_label_set_attributes(GTK_LABEL(label), attributes);
235    pango_attr_list_unref(attributes);
236    gtk_util::SetLabelWidth(label, 400);
237    // Allow linebreaking in the middle of words if necessary, so that extremely
238    // long hostnames (longer than one line) will still be completely shown.
239    gtk_label_set_line_wrap_mode(GTK_LABEL(label), PANGO_WRAP_WORD_CHAR);
240    gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
241  }
242  GtkWidget* label = gtk_label_new(UTF16ToUTF8(section.description).c_str());
243  gtk_label_set_selectable(GTK_LABEL(label), TRUE);
244  labels_.push_back(label);
245  gtk_util::SetLabelWidth(label, 400);
246  // Allow linebreaking in the middle of words if necessary, so that extremely
247  // long hostnames (longer than one line) will still be completely shown.
248  gtk_label_set_line_wrap_mode(GTK_LABEL(label), PANGO_WRAP_WORD_CHAR);
249  gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
250
251  if (section.type == PageInfoModel::SECTION_INFO_IDENTITY && cert_id_ > 0) {
252    GtkWidget* view_cert_link = gtk_chrome_link_button_new(
253        l10n_util::GetStringUTF8(IDS_PAGEINFO_CERT_INFO_BUTTON).c_str());
254    links_.push_back(view_cert_link);
255    GtkWidget* cert_link_hbox = gtk_hbox_new(FALSE, 0);
256    // Stick it in an hbox so it doesn't expand to the whole width.
257    gtk_box_pack_start(GTK_BOX(cert_link_hbox), view_cert_link,
258                       FALSE, FALSE, 0);
259    gtk_box_pack_start(GTK_BOX(vbox), cert_link_hbox, FALSE, FALSE, 0);
260    g_signal_connect(view_cert_link, "clicked",
261                     G_CALLBACK(OnViewCertLinkClickedThunk), this);
262  }
263
264  return section_box;
265}
266
267void PageInfoBubbleGtk::OnViewCertLinkClicked(GtkWidget* widget) {
268  ShowCertificateViewerByID(GTK_WINDOW(parent_), cert_id_);
269  bubble_->Close();
270}
271
272void PageInfoBubbleGtk::OnHelpLinkClicked(GtkWidget* widget) {
273  GURL url = google_util::AppendGoogleLocaleParam(
274      GURL(chrome::kPageInfoHelpCenterURL));
275  Browser* browser = BrowserList::GetLastActive();
276  browser->OpenURL(url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK);
277  bubble_->Close();
278}
279
280}  // namespace
281
282namespace browser {
283
284void ShowPageInfoBubble(gfx::NativeWindow parent,
285                        Profile* profile,
286                        const GURL& url,
287                        const NavigationEntry::SSLStatus& ssl,
288                        bool show_history) {
289  new PageInfoBubbleGtk(parent, profile, url, ssl, show_history);
290}
291
292}  // namespace browser
293