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
23 namespace {
24 // Markup for the text of the Omnibox search label
25 const char kSearchLabelMarkup[] = "<big><b>%s</b></big>";
26
27 // Padding for the buttons on first run bubble.
28 const int kButtonPadding = 4;
29
30 // Padding between content and edge of info bubble.
31 const int kContentBorder = 7;
32
33 // Vertical spacing between labels.
34 const int kInterLineSpacing = 5;
35
36 } // namespace
37
38 // static
Show(Profile * profile,GtkWidget * anchor,const gfx::Rect & rect,FirstRun::BubbleType bubble_type)39 void 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
InfoBubbleClosing(InfoBubbleGtk * info_bubble,bool closed_by_escape)46 void FirstRunBubble::InfoBubbleClosing(InfoBubbleGtk* info_bubble,
47 bool closed_by_escape) {
48 // TODO(port): Enable parent window
49 }
50
CloseOnEscape()51 bool FirstRunBubble::CloseOnEscape() {
52 return true;
53 }
54
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)55 void 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, >k_util::kGdkBlack);
69 }
70 }
71 }
72
FirstRunBubble(Profile * profile,GtkWidget * anchor,const gfx::Rect & rect,FirstRun::BubbleType bubble_type)73 FirstRunBubble::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
~FirstRunBubble()125 FirstRunBubble::~FirstRunBubble() {
126 }
127
InitializeContentForLarge()128 void 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
InitializeContentForOEM()174 void FirstRunBubble::InitializeContentForOEM() {
175 NOTIMPLEMENTED() << "Falling back to minimal bubble";
176 InitializeContentForMinimal();
177 }
178
InitializeContentForMinimal()179 void 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
InitializeLabels(int width_resource)197 void 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
HandleDestroy(GtkWidget * sender)211 void FirstRunBubble::HandleDestroy(GtkWidget* sender) {
212 content_ = NULL;
213 delete this;
214 }
215
HandleKeepButton(GtkWidget * sender)216 void FirstRunBubble::HandleKeepButton(GtkWidget* sender) {
217 bubble_->Close();
218 }
219
HandleChangeButton(GtkWidget * sender)220 void FirstRunBubble::HandleChangeButton(GtkWidget* sender) {
221 bubble_->Close();
222 Browser* browser = BrowserList::GetLastActive();
223 DCHECK(browser);
224 browser->OpenSearchEngineOptionsDialog();
225 }
226