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/gtk/first_run_bubble.h"
6
7#include <gtk/gtk.h>
8
9#include "base/command_line.h"
10#include "base/i18n/rtl.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/search_engines/util.h"
13#include "chrome/browser/ui/browser.h"
14#include "chrome/browser/ui/browser_list.h"
15#include "chrome/browser/ui/gtk/gtk_theme_service.h"
16#include "chrome/browser/ui/gtk/gtk_util.h"
17#include "content/common/notification_service.h"
18#include "grit/chromium_strings.h"
19#include "grit/generated_resources.h"
20#include "grit/locale_settings.h"
21#include "ui/base/l10n/l10n_util.h"
22
23namespace {
24// Markup for the text of the Omnibox search label
25const char kSearchLabelMarkup[] = "<big><b>%s</b></big>";
26
27// Padding for the buttons on first run bubble.
28const int kButtonPadding = 4;
29
30// Padding between content and edge of info bubble.
31const int kContentBorder = 7;
32
33// Vertical spacing between labels.
34const int kInterLineSpacing = 5;
35
36}  // namespace
37
38// static
39void FirstRunBubble::Show(Profile* profile,
40                          GtkWidget* anchor,
41                          const gfx::Rect& rect,
42                          FirstRun::BubbleType bubble_type) {
43  new FirstRunBubble(profile, anchor, rect, bubble_type);
44}
45
46void FirstRunBubble::InfoBubbleClosing(InfoBubbleGtk* info_bubble,
47                                       bool closed_by_escape) {
48  // TODO(port): Enable parent window
49}
50
51bool FirstRunBubble::CloseOnEscape() {
52  return true;
53}
54
55void FirstRunBubble::Observe(NotificationType type,
56                             const NotificationSource& source,
57                             const NotificationDetails& details) {
58  DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
59
60  if (theme_service_->UseGtkTheme()) {
61    for (std::vector<GtkWidget*>::iterator it = labels_.begin();
62         it != labels_.end(); ++it) {
63      gtk_widget_modify_fg(*it, GTK_STATE_NORMAL, NULL);
64    }
65  } else {
66    for (std::vector<GtkWidget*>::iterator it = labels_.begin();
67         it != labels_.end(); ++it) {
68      gtk_widget_modify_fg(*it, GTK_STATE_NORMAL, &gtk_util::kGdkBlack);
69    }
70  }
71}
72
73FirstRunBubble::FirstRunBubble(Profile* profile,
74                               GtkWidget* anchor,
75                               const gfx::Rect& rect,
76                               FirstRun::BubbleType bubble_type)
77    : profile_(profile),
78      theme_service_(GtkThemeService::GetFrom(profile_)),
79      anchor_(anchor),
80      content_(NULL),
81      bubble_(NULL) {
82  content_ = gtk_vbox_new(FALSE, kInterLineSpacing);
83  gtk_container_set_border_width(GTK_CONTAINER(content_), kContentBorder);
84  g_signal_connect(content_, "destroy",
85                   G_CALLBACK(&HandleDestroyThunk), this);
86
87  int width_resource = 0;
88  if (bubble_type == FirstRun::LARGE_BUBBLE) {
89    width_resource = IDS_FIRSTRUNBUBBLE_DIALOG_WIDTH_CHARS;
90    InitializeContentForLarge();
91  } else if (bubble_type == FirstRun::OEM_BUBBLE) {
92    width_resource = IDS_FIRSTRUNOEMBUBBLE_DIALOG_WIDTH_CHARS;
93    InitializeContentForOEM();
94  } else if (bubble_type == FirstRun::MINIMAL_BUBBLE) {
95    width_resource = IDS_FIRSTRUN_MINIMAL_BUBBLE_DIALOG_WIDTH_CHARS;
96    InitializeContentForMinimal();
97  } else {
98    NOTREACHED();
99  }
100
101  InitializeLabels(width_resource);
102
103  InfoBubbleGtk::ArrowLocationGtk arrow_location =
104      !base::i18n::IsRTL() ?
105      InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT :
106      InfoBubbleGtk::ARROW_LOCATION_TOP_RIGHT;
107  bubble_ = InfoBubbleGtk::Show(anchor_,
108                                &rect,
109                                content_,
110                                arrow_location,
111                                true,  // match_system_theme
112                                true,  // grab_input
113                                theme_service_,
114                                this);  // delegate
115  if (!bubble_) {
116    NOTREACHED();
117    return;
118  }
119
120  registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
121                 NotificationService::AllSources());
122  theme_service_->InitThemesFor(this);
123}
124
125FirstRunBubble::~FirstRunBubble() {
126}
127
128void FirstRunBubble::InitializeContentForLarge() {
129  GtkWidget* label1 = gtk_label_new(NULL);
130  labels_.push_back(label1);
131  char* markup = g_markup_printf_escaped(kSearchLabelMarkup,
132      l10n_util::GetStringUTF8(IDS_FR_BUBBLE_TITLE).c_str());
133  gtk_label_set_markup(GTK_LABEL(label1), markup);
134  g_free(markup);
135
136  GtkWidget* label2 = gtk_label_new(
137      l10n_util::GetStringUTF8(IDS_FR_BUBBLE_SUBTEXT).c_str());
138  labels_.push_back(label2);
139
140  string16 search_engine = GetDefaultSearchEngineName(profile_);
141  GtkWidget* label3 = gtk_label_new(
142      l10n_util::GetStringFUTF8(IDS_FR_BUBBLE_QUESTION, search_engine).c_str());
143  labels_.push_back(label3);
144
145  GtkWidget* keep_button = gtk_button_new_with_label(
146      l10n_util::GetStringFUTF8(IDS_FR_BUBBLE_OK, search_engine).c_str());
147  GtkWidget* change_button = gtk_button_new_with_label(
148      l10n_util::GetStringUTF8(IDS_FR_BUBBLE_CHANGE).c_str());
149
150  gtk_box_pack_start(GTK_BOX(content_), label1, FALSE, FALSE, 0);
151  gtk_box_pack_start(GTK_BOX(content_), label2, FALSE, FALSE, 0);
152  // Leave an empty line.
153  gtk_box_pack_start(GTK_BOX(content_), gtk_label_new(NULL), FALSE, FALSE, 0);
154  gtk_box_pack_start(GTK_BOX(content_), label3, FALSE, FALSE, 0);
155
156  GtkWidget* bottom = gtk_hbox_new(FALSE, 0);
157  // We want the buttons on the right, so just use an expanding label to fill
158  // all of the extra space on the left.
159  gtk_box_pack_start(GTK_BOX(bottom), gtk_label_new(NULL), TRUE, TRUE, 0);
160  gtk_box_pack_start(GTK_BOX(bottom), keep_button, FALSE, FALSE,
161                     kButtonPadding);
162  gtk_box_pack_start(GTK_BOX(bottom), change_button, FALSE, FALSE, 0);
163
164  gtk_box_pack_start(GTK_BOX(content_), bottom, FALSE, FALSE, 0);
165  // We want the focus to start on the keep entry, not on the change button.
166  gtk_widget_grab_focus(keep_button);
167
168  g_signal_connect(keep_button, "clicked",
169                   G_CALLBACK(&HandleKeepButtonThunk), this);
170  g_signal_connect(change_button, "clicked",
171                   G_CALLBACK(&HandleChangeButtonThunk), this);
172}
173
174void FirstRunBubble::InitializeContentForOEM() {
175  NOTIMPLEMENTED() << "Falling back to minimal bubble";
176  InitializeContentForMinimal();
177}
178
179void FirstRunBubble::InitializeContentForMinimal() {
180  GtkWidget* label1 = gtk_label_new(NULL);
181  labels_.push_back(label1);
182  char* markup = g_markup_printf_escaped(kSearchLabelMarkup,
183      l10n_util::GetStringFUTF8(
184          IDS_FR_SE_BUBBLE_TITLE,
185          GetDefaultSearchEngineName(profile_)).c_str());
186  gtk_label_set_markup(GTK_LABEL(label1), markup);
187  g_free(markup);
188
189  GtkWidget* label2 =
190      gtk_label_new(l10n_util::GetStringUTF8(IDS_FR_BUBBLE_SUBTEXT).c_str());
191  labels_.push_back(label2);
192
193  gtk_box_pack_start(GTK_BOX(content_), label1, FALSE, FALSE, 0);
194  gtk_box_pack_start(GTK_BOX(content_), label2, FALSE, FALSE, 0);
195}
196
197void FirstRunBubble::InitializeLabels(int width_resource) {
198  int width = -1;
199
200  gtk_util::GetWidgetSizeFromResources(
201      anchor_, width_resource, 0, &width, NULL);
202
203  for (size_t i = 0; i < labels_.size(); ++i) {
204    // Resize the labels so that they don't wrap more than necessary.  We leave
205    // |content_| unsized so that it'll expand as needed to hold the other
206    // widgets -- the buttons may be wider than |width| on high-DPI displays.
207    gtk_util::SetLabelWidth(labels_[i], width);
208  }
209}
210
211void FirstRunBubble::HandleDestroy(GtkWidget* sender) {
212  content_ = NULL;
213  delete this;
214}
215
216void FirstRunBubble::HandleKeepButton(GtkWidget* sender) {
217  bubble_->Close();
218}
219
220void FirstRunBubble::HandleChangeButton(GtkWidget* sender) {
221  bubble_->Close();
222  Browser* browser = BrowserList::GetLastActive();
223  DCHECK(browser);
224  browser->OpenSearchEngineOptionsDialog();
225}
226