OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/web_intent_picker_gtk.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/i18n/rtl.h" | |
10 #include "base/utf_string_conversions.h" | |
11 #include "chrome/browser/favicon/favicon_service.h" | |
12 #include "chrome/browser/profiles/profile.h" | |
13 #include "chrome/browser/tab_contents/tab_util.h" | |
14 #include "chrome/browser/ui/browser_finder.h" | |
15 #include "chrome/browser/ui/browser_window.h" | |
16 #include "chrome/browser/ui/gtk/browser_toolbar_gtk.h" | |
17 #include "chrome/browser/ui/gtk/browser_window_gtk.h" | |
18 #include "chrome/browser/ui/gtk/custom_button.h" | |
19 #include "chrome/browser/ui/gtk/event_utils.h" | |
20 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h" | |
21 #include "chrome/browser/ui/gtk/gtk_theme_service.h" | |
22 #include "chrome/browser/ui/gtk/gtk_util.h" | |
23 #include "chrome/browser/ui/gtk/location_bar_view_gtk.h" | |
24 #include "chrome/browser/ui/gtk/throbber_gtk.h" | |
25 #include "chrome/browser/ui/intents/web_intent_picker_controller.h" | |
26 #include "chrome/browser/ui/intents/web_intent_picker_delegate.h" | |
27 #include "chrome/browser/ui/intents/web_intent_picker_model.h" | |
28 #include "chrome/common/chrome_notification_types.h" | |
29 #include "content/public/browser/notification_source.h" | |
30 #include "content/public/browser/notification_types.h" | |
31 #include "content/public/browser/render_view_host.h" | |
32 #include "content/public/browser/render_widget_host_view.h" | |
33 #include "content/public/browser/web_contents.h" | |
34 #include "content/public/browser/web_contents_view.h" | |
35 #include "googleurl/src/gurl.h" | |
36 #include "grit/chromium_strings.h" | |
37 #include "grit/generated_resources.h" | |
38 #include "grit/google_chrome_strings.h" | |
39 #include "grit/theme_resources.h" | |
40 #include "ui/base/gtk/gtk_hig_constants.h" | |
41 #include "ui/base/gtk/gtk_signal_registrar.h" | |
42 #include "ui/base/l10n/l10n_util.h" | |
43 #include "ui/base/resource/resource_bundle.h" | |
44 #include "ui/base/text/text_elider.h" | |
45 #include "ui/gfx/gtk_util.h" | |
46 #include "ui/gfx/image/image.h" | |
47 | |
48 using content::WebContents; | |
49 | |
50 namespace { | |
51 | |
52 // The pixel size of the header label when using a non-native theme. | |
53 const int kHeaderLabelPixelSize = 15; | |
54 | |
55 // The pixel size of the font of the main content of the dialog. | |
56 const int kMainContentPixelSize = 13; | |
57 | |
58 // Indices of the extension row widgets. | |
59 enum { | |
60 kIconIndex, | |
61 kTitleLinkIndex, | |
62 kStarsIndex, | |
63 kInstallButtonIndex, | |
64 }; | |
65 | |
66 GtkThemeService *GetThemeService(WebContents* web_contents) { | |
67 Profile* profile = | |
68 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | |
69 return GtkThemeService::GetFrom(profile); | |
70 } | |
71 | |
72 // Set the image of |button| to |pixbuf|. | |
73 void SetServiceButtonImage(GtkWidget* button, GdkPixbuf* pixbuf) { | |
74 gtk_button_set_image(GTK_BUTTON(button), gtk_image_new_from_pixbuf(pixbuf)); | |
75 gtk_button_set_image_position(GTK_BUTTON(button), GTK_POS_LEFT); | |
76 } | |
77 | |
78 void SetWidgetFontSizeCallback(GtkWidget* widget, gpointer data) { | |
79 if (GTK_IS_LABEL(widget)) { | |
80 int* size = static_cast<int*>(data); | |
81 gtk_util::ForceFontSizePixels(widget, *size); | |
82 return; | |
83 } | |
84 | |
85 if (GTK_IS_CONTAINER(widget)) | |
86 gtk_container_forall(GTK_CONTAINER(widget), SetWidgetFontSizeCallback, | |
87 data); | |
88 } | |
89 | |
90 void SetWidgetFontSize(GtkWidget* widget, int size) { | |
91 gtk_container_forall(GTK_CONTAINER(widget), SetWidgetFontSizeCallback, &size); | |
92 } | |
93 | |
94 // Get the index of the row containing |widget|. Assume the widget is the child | |
95 // of an hbox, which is a child of a vbox. The hbox represents a row, and the | |
96 // vbox the full table. | |
97 size_t GetExtensionWidgetRow(GtkWidget* widget) { | |
98 GtkWidget* hbox = gtk_widget_get_parent(widget); | |
99 DCHECK(hbox); | |
100 GtkWidget* vbox = gtk_widget_get_parent(hbox); | |
101 DCHECK(vbox); | |
102 GList* hbox_list = gtk_container_get_children(GTK_CONTAINER(vbox)); | |
103 gint index = g_list_index(hbox_list, hbox); | |
104 DCHECK(index != -1); | |
105 g_list_free(hbox_list); | |
106 | |
107 return index; | |
108 } | |
109 | |
110 // A gtk_container_foreach callback to enable/disable a widget. | |
111 void EnableWidgetCallback(GtkWidget* widget, gpointer data) { | |
112 gtk_widget_set_sensitive(widget, *static_cast<gboolean*>(data)); | |
113 } | |
114 | |
115 // Create a new widget displaying |rating| as |kNumStarsPerRating| star images. | |
116 // Rating should be in the range [0, kNumStarsPerRating]. | |
117 GtkWidget* CreateStarsWidget(double rating) { | |
118 const int kNumStarsPerRating = 5; // Number of stars in a rating. | |
119 const int kStarSpacing = 1; // Spacing between stars in pixels. | |
120 GtkWidget* hbox = gtk_hbox_new(FALSE, kStarSpacing); | |
121 | |
122 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
123 for (int i = 0; i < kNumStarsPerRating; ++i) { | |
124 GdkPixbuf* star = rb.GetNativeImageNamed( | |
125 WebIntentPicker::GetNthStarImageIdFromCWSRating(rating, i), | |
126 ui::ResourceBundle::RTL_ENABLED).ToGdkPixbuf(); | |
127 gtk_box_pack_start(GTK_BOX(hbox), gtk_image_new_from_pixbuf(star), | |
128 FALSE, FALSE, 0); | |
129 } | |
130 | |
131 return hbox; | |
132 } | |
133 | |
134 } // namespace | |
135 | |
136 // Create a new Widget for the "waiting for CWS" display. | |
137 | |
138 class WaitingDialog { | |
139 public: | |
140 explicit WaitingDialog(GtkThemeService* theme_service); | |
141 ~WaitingDialog(); | |
142 | |
143 GtkWidget* widget() const { return widget_.get(); } | |
144 private: | |
145 // Initialize the widget. | |
146 void Init(); | |
147 | |
148 // The actual GtkWidget | |
149 ui::OwnedWidgetGtk widget_; | |
150 | |
151 // Waiting throbber. | |
152 scoped_ptr<ThrobberGtk> throbber_; | |
153 | |
154 // Weak pointer to theme service. | |
155 GtkThemeService* theme_service_; | |
156 }; | |
157 | |
158 WaitingDialog::WaitingDialog(GtkThemeService* theme_service) | |
159 : theme_service_(theme_service) { | |
160 DCHECK(theme_service_); | |
161 Init(); | |
162 } | |
163 | |
164 WaitingDialog::~WaitingDialog() { | |
165 widget_.Destroy(); | |
166 } | |
167 | |
168 void WaitingDialog::Init() { | |
169 const int kDialogSpacing = 30; | |
170 | |
171 widget_.Own(gtk_vbox_new(FALSE, 0)); | |
172 GtkWidget* vbox = widget_.get(); | |
173 | |
174 // Create throbber | |
175 ThrobberGtk* throbber = new ThrobberGtk(theme_service_); | |
176 GtkWidget* throbber_alignment = gtk_alignment_new(0.5, 0.5, 0, 0); | |
177 gtk_alignment_set_padding(GTK_ALIGNMENT(throbber_alignment), kDialogSpacing, | |
178 kMainContentPixelSize, 0, 0); | |
179 gtk_container_add(GTK_CONTAINER(throbber_alignment), throbber->widget()); | |
180 gtk_box_pack_start(GTK_BOX(vbox), throbber_alignment, TRUE, TRUE, 0); | |
181 | |
182 // Add the message text. | |
183 GtkWidget* message_label = theme_service_->BuildLabel( | |
184 l10n_util::GetStringUTF8(IDS_INTENT_PICKER_WAIT_FOR_CWS).c_str(), | |
185 ui::kGdkBlack); | |
186 | |
187 GtkWidget* label_alignment = gtk_alignment_new(0.5, 0.5, 0, 0); | |
188 gtk_alignment_set_padding(GTK_ALIGNMENT(label_alignment), | |
189 kMainContentPixelSize, kDialogSpacing, 0, 0); | |
190 gtk_container_add(GTK_CONTAINER(label_alignment), message_label); | |
191 gtk_box_pack_start(GTK_BOX(vbox), label_alignment, TRUE, TRUE, 0); | |
192 | |
193 // TODO(groby): use IDR_SPEECH_INPUT_SPINNER. Pending fix for ThrobberGtk. | |
194 // Animate throbber | |
195 throbber->Start(); | |
196 } | |
197 | |
198 // static | |
199 WebIntentPicker* WebIntentPicker::Create(WebContents* web_contents, | |
200 WebIntentPickerDelegate* delegate, | |
201 WebIntentPickerModel* model) { | |
202 return new WebIntentPickerGtk(web_contents, delegate, model); | |
203 } | |
204 | |
205 WebIntentPickerGtk::WebIntentPickerGtk(WebContents* web_contents, | |
206 WebIntentPickerDelegate* delegate, | |
207 WebIntentPickerModel* model) | |
208 : web_contents_(web_contents), | |
209 delegate_(delegate), | |
210 model_(model), | |
211 contents_(NULL), | |
212 header_label_(NULL), | |
213 button_vbox_(NULL), | |
214 cws_label_(NULL), | |
215 extensions_vbox_(NULL), | |
216 service_hbox_(NULL), | |
217 window_(NULL) { | |
218 DCHECK(delegate_ != NULL); | |
219 | |
220 model_->set_observer(this); | |
221 InitContents(); | |
222 UpdateInstalledServices(); | |
223 UpdateCWSLabel(); | |
224 UpdateSuggestedExtensions(); | |
225 | |
226 GtkThemeService* theme_service = GetThemeService(web_contents); | |
227 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, | |
228 content::Source<ThemeService>(theme_service)); | |
229 theme_service->InitThemesFor(this); | |
230 | |
231 window_ = new ConstrainedWindowGtk(web_contents, this); | |
232 | |
233 if (model_->IsInlineDisposition()) | |
234 OnInlineDisposition(string16(), model_->inline_disposition_url()); | |
235 } | |
236 | |
237 WebIntentPickerGtk::~WebIntentPickerGtk() { | |
238 } | |
239 | |
240 void WebIntentPickerGtk::Close() { | |
241 window_->CloseWebContentsModalDialog(); | |
242 if (inline_disposition_web_contents_.get()) | |
243 inline_disposition_web_contents_->OnCloseStarted(); | |
244 } | |
245 | |
246 void WebIntentPickerGtk::SetActionString(const string16& action) { | |
247 header_label_text_ = action; | |
248 if (header_label_) | |
249 gtk_label_set_text(GTK_LABEL(header_label_), UTF16ToUTF8(action).c_str()); | |
250 } | |
251 | |
252 void WebIntentPickerGtk::OnExtensionInstallSuccess(const std::string& id) { | |
253 RemoveThrobber(); | |
254 } | |
255 | |
256 void WebIntentPickerGtk::OnExtensionInstallFailure(const std::string& id) { | |
257 // The throbber has an alignment as its parent, so it must be used instead of | |
258 // the throbber to find the extension row. | |
259 size_t index = | |
260 GetExtensionWidgetRow(gtk_widget_get_parent(throbber_->widget())); | |
261 GList* vbox_list = | |
262 gtk_container_get_children(GTK_CONTAINER(extensions_vbox_)); | |
263 GtkWidget* hbox = static_cast<GtkWidget*>(g_list_nth_data(vbox_list, index)); | |
264 | |
265 RemoveThrobber(); | |
266 gtk_widget_show_all(hbox); | |
267 g_list_free(vbox_list); | |
268 SetWidgetsEnabled(true); | |
269 } | |
270 | |
271 void WebIntentPickerGtk::OnModelChanged(WebIntentPickerModel* model) { | |
272 if (waiting_dialog_.get() && !model->IsWaitingForSuggestions()) { | |
273 waiting_dialog_.reset(); | |
274 InitMainContents(); | |
275 } | |
276 UpdateInstalledServices(); | |
277 UpdateCWSLabel(); | |
278 UpdateSuggestedExtensions(); | |
279 SetActionString(header_label_text_); | |
280 } | |
281 | |
282 void WebIntentPickerGtk::OnFaviconChanged(WebIntentPickerModel* model, | |
283 size_t index) { | |
284 UpdateInstalledServices(); | |
285 } | |
286 | |
287 void WebIntentPickerGtk::OnExtensionIconChanged( | |
288 WebIntentPickerModel* model, | |
289 const std::string& extension_id) { | |
290 UpdateSuggestedExtensions(); | |
291 } | |
292 | |
293 void WebIntentPickerGtk::OnInlineDisposition(const string16&, | |
294 const GURL& url) { | |
295 DCHECK(delegate_); | |
296 Profile* profile = | |
297 Profile::FromBrowserContext(web_contents_->GetBrowserContext()); | |
298 inline_disposition_web_contents_.reset( | |
299 delegate_->CreateWebContentsForInlineDisposition(profile, url)); | |
300 Browser* browser = chrome::FindBrowserWithWebContents(web_contents_); | |
301 inline_disposition_delegate_.reset( | |
302 new WebIntentInlineDispositionDelegate( | |
303 this, inline_disposition_web_contents_.get(), browser)); | |
304 | |
305 inline_disposition_web_contents_->GetController().LoadURL( | |
306 url, content::Referrer(), content::PAGE_TRANSITION_AUTO_TOPLEVEL, | |
307 std::string()); | |
308 | |
309 // Replace the picker contents with the inline disposition. | |
310 ClearContents(); | |
311 gtk_widget_set_size_request(contents_, -1, -1); | |
312 window_->BackgroundColorChanged(); | |
313 | |
314 GtkWidget* vbox = gtk_vbox_new(FALSE, 0); | |
315 GtkThemeService* theme_service = GetThemeService(web_contents_); | |
316 | |
317 service_hbox_ = gtk_hbox_new(FALSE, ui::kControlSpacing); | |
318 // TODO(gbillock): Eventually get the service icon button here. | |
319 | |
320 // Intent action label. | |
321 const WebIntentPickerModel::InstalledService* service = | |
322 model_->GetInstalledServiceWithURL(url); | |
323 GtkWidget* action_label = gtk_label_new(UTF16ToUTF8(service->title).c_str()); | |
324 gtk_util::ForceFontSizePixels(action_label, kMainContentPixelSize); | |
325 // Hardcode color; don't allow theming. | |
326 gtk_util::SetLabelColor(action_label, &ui::kGdkBlack); | |
327 | |
328 GtkWidget* label_alignment = gtk_alignment_new(0, 0.5f, 0, 0); | |
329 gtk_container_add(GTK_CONTAINER(label_alignment), action_label); | |
330 GtkWidget* indent_label = gtk_util::IndentWidget(label_alignment); | |
331 | |
332 gtk_box_pack_start(GTK_BOX(service_hbox_), indent_label, FALSE, TRUE, 0); | |
333 g_signal_connect(service_hbox_, "destroy", G_CALLBACK(gtk_widget_destroyed), | |
334 &service_hbox_); | |
335 | |
336 // Add link for "choose another service" if other suggestions are available | |
337 // or if more than one (the current) service is installed. | |
338 if (model_->show_use_another_service() && | |
339 (model_->GetInstalledServiceCount() > 1 || | |
340 model_->GetSuggestedExtensionCount())) { | |
341 GtkWidget* use_alternate_link = theme_service->BuildChromeLinkButton( | |
342 l10n_util::GetStringUTF8( | |
343 IDS_INTENT_PICKER_USE_ALTERNATE_SERVICE).c_str()); | |
344 gtk_chrome_link_button_set_use_gtk_theme( | |
345 GTK_CHROME_LINK_BUTTON(use_alternate_link), | |
346 theme_service->UsingNativeTheme()); | |
347 gtk_util::ForceFontSizePixels( | |
348 GTK_CHROME_LINK_BUTTON(use_alternate_link)->label, | |
349 kMainContentPixelSize); | |
350 g_signal_connect(use_alternate_link, "clicked", | |
351 G_CALLBACK(OnChooseAnotherServiceClickThunk), this); | |
352 GtkWidget* link_alignment = gtk_alignment_new(0, 0.5f, 0, 0); | |
353 gtk_container_add(GTK_CONTAINER(link_alignment), use_alternate_link); | |
354 gtk_box_pack_start(GTK_BOX(service_hbox_), link_alignment, TRUE, TRUE, 0); | |
355 } | |
356 AddCloseButton(service_hbox_); | |
357 | |
358 // The header box | |
359 gtk_container_add(GTK_CONTAINER(vbox), service_hbox_); | |
360 | |
361 // hbox for the web contents, so we can have spacing on the borders. | |
362 GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); | |
363 gtk_alignment_set_padding( | |
364 GTK_ALIGNMENT(alignment), 0, ui::kContentAreaBorder, | |
365 ui::kContentAreaBorder, ui::kContentAreaBorder); | |
366 gfx::NativeView web_contents_widget = | |
367 inline_disposition_web_contents_->GetView()->GetNativeView(); | |
368 gtk_container_add(GTK_CONTAINER(alignment), web_contents_widget); | |
369 gtk_container_add(GTK_CONTAINER(vbox), alignment); | |
370 gtk_container_add(GTK_CONTAINER(contents_), vbox); | |
371 | |
372 gfx::Size size = GetMinInlineDispositionSize(); | |
373 gtk_widget_set_size_request(web_contents_widget, | |
374 size.width(), size.height()); | |
375 gtk_widget_show_all(contents_); | |
376 | |
377 inline_disposition_delegate_->SetRenderViewSizeLimits(); | |
378 inline_disposition_web_contents_->GetView()->SetInitialFocus(); | |
379 host_signals_.reset(new ui::GtkSignalRegistrar()); | |
380 host_signals_->Connect( | |
381 web_contents_->GetRenderViewHost()->GetView()->GetNativeView(), | |
382 "size-allocate", | |
383 G_CALLBACK(OnHostContentsSizeAllocateThunk), this); | |
384 } | |
385 | |
386 void WebIntentPickerGtk::OnInlineDispositionAutoResize(const gfx::Size& size) { | |
387 gfx::NativeView web_contents_widget = | |
388 inline_disposition_web_contents_->GetView()->GetNativeView(); | |
389 gtk_widget_set_size_request(web_contents_widget, size.width(), size.height()); | |
390 } | |
391 | |
392 gfx::Size WebIntentPickerGtk::GetMaxInlineDispositionSize() { | |
393 gfx::Rect tab_bounds(web_contents_->GetRenderViewHost()-> | |
394 GetView()->GetNativeView()->allocation); | |
395 GtkRequisition req = {}; | |
396 if (service_hbox_) | |
397 gtk_widget_size_request(service_hbox_, &req); | |
398 | |
399 tab_bounds.Inset(2 * ui::kContentAreaBorder, | |
400 2 * ui::kContentAreaBorder + req.height); | |
401 return tab_bounds.size(); | |
402 } | |
403 | |
404 void WebIntentPickerGtk::OnPendingAsyncCompleted() { | |
405 // Requests to both the WebIntentService and the Chrome Web Store have | |
406 // completed. If there are any services, installed or suggested, there's | |
407 // nothing to do. | |
408 if (model_->GetInstalledServiceCount() || | |
409 model_->GetSuggestedExtensionCount()) | |
410 return; | |
411 | |
412 // If there are no installed or suggested services at this point, | |
413 // inform the user about it. | |
414 | |
415 // Replace the picker contents with dialog box. | |
416 ClearContents(); | |
417 GtkWidget* sub_contents = CreateSubContents(contents_); | |
418 | |
419 AddCloseButton(contents_); | |
420 AddTitle(sub_contents); | |
421 | |
422 // Replace the dialog header. | |
423 DCHECK(header_label_); | |
424 gtk_label_set_text( | |
425 GTK_LABEL(header_label_), | |
426 l10n_util::GetStringUTF8(IDS_INTENT_PICKER_NO_SERVICES_TITLE).c_str()); | |
427 | |
428 // Add the message text. | |
429 GtkWidget* hbox = gtk_hbox_new(FALSE, 0); | |
430 gtk_box_pack_start(GTK_BOX(sub_contents), hbox, TRUE, TRUE, 0); | |
431 GtkThemeService* theme_service = GetThemeService(web_contents_); | |
432 GtkWidget* no_service_label = theme_service->BuildLabel( | |
433 l10n_util::GetStringUTF8(IDS_INTENT_PICKER_NO_SERVICES).c_str(), | |
434 ui::kGdkBlack); | |
435 gtk_label_set_line_wrap(GTK_LABEL(no_service_label), TRUE); | |
436 gtk_misc_set_alignment(GTK_MISC(no_service_label), 0, 0); | |
437 // Set the label width to the size of |sub_contents|, which we don't have | |
438 // access to yet, by calculating the main content width minus borders. | |
439 gtk_util::SetLabelWidth(no_service_label, | |
440 kWindowMinWidth - 2 * ui::kContentAreaBorder); | |
441 gtk_box_pack_start(GTK_BOX(hbox), no_service_label, TRUE, TRUE, 0); | |
442 | |
443 gtk_widget_show_all(contents_); | |
444 } | |
445 | |
446 void WebIntentPickerGtk::InvalidateDelegate() { | |
447 delegate_ = NULL; | |
448 } | |
449 | |
450 GtkWidget* WebIntentPickerGtk::GetWidgetRoot() { | |
451 return contents_; | |
452 } | |
453 | |
454 GtkWidget* WebIntentPickerGtk::GetFocusWidget() { | |
455 return contents_; | |
456 } | |
457 | |
458 void WebIntentPickerGtk::DeleteDelegate() { | |
459 // The delegate is deleted when the contents widget is destroyed. See | |
460 // OnDestroy. | |
461 if (delegate_) | |
462 delegate_->OnClosing(); | |
463 } | |
464 | |
465 bool WebIntentPickerGtk::GetBackgroundColor(GdkColor* color) { | |
466 if (inline_disposition_web_contents_.get()) { | |
467 *color = ui::kGdkWhite; | |
468 return true; | |
469 } | |
470 | |
471 return false; | |
472 } | |
473 | |
474 bool WebIntentPickerGtk::ShouldHaveBorderPadding() const { | |
475 return false; | |
476 } | |
477 | |
478 void WebIntentPickerGtk::Observe(int type, | |
479 const content::NotificationSource& source, | |
480 const content::NotificationDetails& details) { | |
481 DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_THEME_CHANGED); | |
482 if (header_label_) { | |
483 GtkThemeService* theme_service = GetThemeService(web_contents_); | |
484 if (theme_service->UsingNativeTheme()) | |
485 gtk_util::UndoForceFontSize(header_label_); | |
486 else | |
487 gtk_util::ForceFontSizePixels(header_label_, kHeaderLabelPixelSize); | |
488 } | |
489 UpdateInstalledServices(); | |
490 UpdateSuggestedExtensions(); | |
491 } | |
492 | |
493 void WebIntentPickerGtk::OnDestroy(GtkWidget* button) { | |
494 // Destroy this object when the contents widget is destroyed. It can't be | |
495 // "delete this" because this function happens in a callback. | |
496 MessageLoop::current()->DeleteSoon(FROM_HERE, this); | |
497 model_->set_observer(NULL); | |
498 window_ = NULL; | |
499 } | |
500 | |
501 void WebIntentPickerGtk::OnCloseButtonClick(GtkWidget* button) { | |
502 DCHECK(delegate_); | |
503 delegate_->OnUserCancelledPickerDialog(); | |
504 } | |
505 | |
506 void WebIntentPickerGtk::OnExtensionLinkClick(GtkWidget* link) { | |
507 DCHECK(delegate_); | |
508 size_t index = GetExtensionWidgetRow(link); | |
509 const WebIntentPickerModel::SuggestedExtension& extension = | |
510 model_->GetSuggestedExtensionAt(index); | |
511 delegate_->OnExtensionLinkClicked(extension.id, | |
512 event_utils::DispositionForCurrentButtonPressEvent()); | |
513 } | |
514 | |
515 void WebIntentPickerGtk::OnExtensionInstallButtonClick(GtkWidget* button) { | |
516 DCHECK(delegate_); | |
517 size_t index = GetExtensionWidgetRow(button); | |
518 const WebIntentPickerModel::SuggestedExtension& extension = | |
519 model_->GetSuggestedExtensionAt(index); | |
520 | |
521 delegate_->OnExtensionInstallRequested(extension.id); | |
522 SetWidgetsEnabled(false); | |
523 | |
524 // Re-enable the clicked extension row. | |
525 GList* vbox_list = | |
526 gtk_container_get_children(GTK_CONTAINER(extensions_vbox_)); | |
527 GtkWidget* hbox = static_cast<GtkWidget*>(g_list_nth_data(vbox_list, index)); | |
528 gtk_widget_set_sensitive(hbox, TRUE); | |
529 | |
530 // Hide the install button. | |
531 GList* hbox_list = gtk_container_get_children(GTK_CONTAINER(hbox)); | |
532 GtkWidget* install_button = | |
533 static_cast<GtkWidget*>(g_list_nth_data(hbox_list, kInstallButtonIndex)); | |
534 GtkAllocation allocation; | |
535 gtk_widget_get_allocation(install_button, &allocation); | |
536 gtk_widget_hide(install_button); | |
537 g_list_free(hbox_list); | |
538 g_list_free(vbox_list); | |
539 | |
540 // Show the throbber with the same size as the install button. | |
541 GtkWidget* throbber = AddThrobberToExtensionAt(index); | |
542 gtk_widget_set_size_request(throbber, allocation.width, allocation.height); | |
543 gtk_widget_show_all(throbber); | |
544 } | |
545 | |
546 void WebIntentPickerGtk::OnMoreSuggestionsLinkClick(GtkWidget* link) { | |
547 DCHECK(delegate_); | |
548 delegate_->OnSuggestionsLinkClicked( | |
549 event_utils::DispositionForCurrentButtonPressEvent()); | |
550 } | |
551 | |
552 void WebIntentPickerGtk::OnChooseAnotherServiceClick(GtkWidget* link) { | |
553 DCHECK(delegate_); | |
554 delegate_->OnChooseAnotherService(); | |
555 ResetContents(); | |
556 } | |
557 | |
558 void WebIntentPickerGtk::OnHostContentsSizeAllocate(GtkWidget* widget, | |
559 GdkRectangle* rectangle) { | |
560 if (!inline_disposition_delegate_.get()) | |
561 return; | |
562 inline_disposition_delegate_->SetRenderViewSizeLimits(); | |
563 } | |
564 | |
565 void WebIntentPickerGtk::OnServiceButtonClick(GtkWidget* button) { | |
566 DCHECK(delegate_); | |
567 GList* button_list = gtk_container_get_children(GTK_CONTAINER(button_vbox_)); | |
568 gint index = g_list_index(button_list, button); | |
569 DCHECK(index != -1); | |
570 g_list_free(button_list); | |
571 | |
572 const WebIntentPickerModel::InstalledService& installed_service = | |
573 model_->GetInstalledServiceAt(index); | |
574 | |
575 delegate_->OnServiceChosen(installed_service.url, | |
576 installed_service.disposition, | |
577 WebIntentPickerDelegate::kEnableDefaults); | |
578 } | |
579 | |
580 void WebIntentPickerGtk::InitContents() { | |
581 GtkThemeService* theme_service = GetThemeService(web_contents_); | |
582 | |
583 // Main contents vbox. | |
584 if (!contents_) { | |
585 contents_ = gtk_vbox_new(FALSE, 0); | |
586 g_signal_connect(contents_, "destroy", G_CALLBACK(&OnDestroyThunk), this); | |
587 } | |
588 | |
589 gtk_widget_set_size_request(contents_, kWindowMinWidth, -1); | |
590 | |
591 if (model_ && model_->IsWaitingForSuggestions()) { | |
592 ClearContents(); | |
593 AddCloseButton(contents_); | |
594 waiting_dialog_.reset(new WaitingDialog(theme_service)); | |
595 gtk_box_pack_start(GTK_BOX(contents_), waiting_dialog_->widget(), | |
596 TRUE, TRUE, 0); | |
597 } else { | |
598 InitMainContents(); | |
599 } | |
600 } | |
601 | |
602 void WebIntentPickerGtk::InitMainContents() { | |
603 GtkThemeService* theme_service = GetThemeService(web_contents_); | |
604 | |
605 ClearContents(); | |
606 | |
607 AddCloseButton(contents_); | |
608 GtkWidget* sub_contents = CreateSubContents(contents_); | |
609 | |
610 AddTitle(sub_contents); | |
611 | |
612 // Add separation between the installed services list and the app suggestions. | |
613 GtkWidget* button_alignment = gtk_alignment_new(0.5, 0, 0, 0); | |
614 gtk_alignment_set_padding(GTK_ALIGNMENT(button_alignment), 0, | |
615 kMainContentPixelSize * 2, 0, 0); | |
616 | |
617 // Vbox containing all service buttons. | |
618 button_vbox_ = gtk_vbox_new(FALSE, ui::kControlSpacing); | |
619 gtk_container_add(GTK_CONTAINER(button_alignment), button_vbox_); | |
620 gtk_box_pack_start(GTK_BOX(sub_contents), button_alignment, TRUE, TRUE, 0); | |
621 | |
622 // Chrome Web Store label. | |
623 cws_label_ = theme_service->BuildLabel( | |
624 l10n_util::GetStringUTF8(IDS_INTENT_PICKER_GET_MORE_SERVICES).c_str(), | |
625 ui::kGdkBlack); | |
626 gtk_box_pack_start(GTK_BOX(sub_contents), cws_label_, TRUE, TRUE, 0); | |
627 gtk_misc_set_alignment(GTK_MISC(cws_label_), 0, 0); | |
628 gtk_widget_set_no_show_all(cws_label_, TRUE); | |
629 | |
630 // Set the label width to the size of |sub_contents|, which we don't have | |
631 // access to yet, by calculating the main content width minus borders. | |
632 gtk_util::SetLabelWidth(cws_label_, | |
633 kWindowMinWidth - 2 * ui::kContentAreaBorder); | |
634 gtk_util::ForceFontSizePixels(cws_label_, kMainContentPixelSize); | |
635 | |
636 // Suggested extensions vbox. | |
637 extensions_vbox_ = gtk_vbox_new(FALSE, ui::kControlSpacing); | |
638 GtkWidget* indent_extensions = gtk_alignment_new(0.0, 0.5, 1.0, 1.0); | |
639 gtk_alignment_set_padding(GTK_ALIGNMENT(indent_extensions), 0, 0, | |
640 ui::kGroupIndent, ui::kGroupIndent); | |
641 gtk_container_add(GTK_CONTAINER(indent_extensions), extensions_vbox_); | |
642 gtk_widget_set_no_show_all(indent_extensions, TRUE); | |
643 gtk_box_pack_start(GTK_BOX(sub_contents), indent_extensions, TRUE, TRUE, 0); | |
644 | |
645 // CWS 'More Suggestions' link. | |
646 GtkWidget* link_alignment = gtk_alignment_new(0, 0.5f, 0, 0); | |
647 GtkWidget* more_hbox = gtk_hbox_new(FALSE, ui::kControlSpacing); | |
648 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
649 GdkPixbuf* cws_icon = | |
650 rb.GetNativeImageNamed(IDR_WEBSTORE_ICON_16).ToGdkPixbuf(); | |
651 gtk_box_pack_start(GTK_BOX(more_hbox), gtk_image_new_from_pixbuf(cws_icon), | |
652 FALSE, FALSE, 0); | |
653 GtkWidget* more_suggestions_link = theme_service->BuildChromeLinkButton( | |
654 l10n_util::GetStringUTF8(IDS_FIND_MORE_INTENT_HANDLER_MESSAGE).c_str()); | |
655 gtk_box_pack_start(GTK_BOX(more_hbox), more_suggestions_link, | |
656 FALSE, FALSE, 0); | |
657 gtk_container_add(GTK_CONTAINER(link_alignment), more_hbox); | |
658 gtk_chrome_link_button_set_use_gtk_theme( | |
659 GTK_CHROME_LINK_BUTTON(more_suggestions_link), | |
660 theme_service->UsingNativeTheme()); | |
661 gtk_util::ForceFontSizePixels( | |
662 GTK_CHROME_LINK_BUTTON(more_suggestions_link)->label, | |
663 kMainContentPixelSize); | |
664 g_signal_connect(more_suggestions_link, "clicked", | |
665 G_CALLBACK(OnMoreSuggestionsLinkClickThunk), this); | |
666 | |
667 GtkWidget* indent_link = gtk_util::IndentWidget(link_alignment); | |
668 gtk_box_pack_start(GTK_BOX(sub_contents), indent_link, TRUE, TRUE, 0); | |
669 | |
670 // Throbber, which will be added to the hierarchy when necessary. | |
671 throbber_.reset(new ThrobberGtk(theme_service)); | |
672 | |
673 gtk_widget_show_all(contents_); | |
674 } | |
675 | |
676 void WebIntentPickerGtk::ClearContents() { | |
677 // Wipe out all currently displayed widgets. | |
678 gtk_util::RemoveAllChildren(contents_); | |
679 header_label_ = NULL; | |
680 button_vbox_ = NULL; | |
681 cws_label_ = NULL; | |
682 extensions_vbox_ = NULL; | |
683 } | |
684 | |
685 void WebIntentPickerGtk::ResetContents() { | |
686 ClearContents(); | |
687 | |
688 // Reset potential inline disposition data. | |
689 inline_disposition_web_contents_.reset(); | |
690 inline_disposition_delegate_.reset(); | |
691 window_->BackgroundColorChanged(); | |
692 | |
693 // Re-initialize picker widgets and data. | |
694 InitMainContents(); | |
695 UpdateInstalledServices(); | |
696 UpdateCWSLabel(); | |
697 UpdateSuggestedExtensions(); | |
698 SetActionString(header_label_text_); | |
699 | |
700 gtk_widget_show_all(contents_); | |
701 } | |
702 | |
703 GtkWidget* WebIntentPickerGtk::CreateSubContents(GtkWidget* box) { | |
704 GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); | |
705 gtk_alignment_set_padding( | |
706 GTK_ALIGNMENT(alignment), 0, ui::kContentAreaBorder, | |
707 ui::kContentAreaBorder, ui::kContentAreaBorder); | |
708 gtk_box_pack_end(GTK_BOX(box), alignment, TRUE, TRUE, 0); | |
709 | |
710 GtkWidget* sub_contents = gtk_vbox_new(FALSE, ui::kContentAreaSpacing); | |
711 gtk_container_add(GTK_CONTAINER(alignment), sub_contents); | |
712 return sub_contents; | |
713 } | |
714 | |
715 void WebIntentPickerGtk::AddCloseButton(GtkWidget* containingBox) { | |
716 | |
717 // Hbox containing the close button. | |
718 GtkWidget* close_hbox = gtk_hbox_new(FALSE, 0); | |
719 gtk_box_pack_start(GTK_BOX(containingBox), close_hbox, TRUE, TRUE, 0); | |
720 | |
721 close_button_.reset( | |
722 CustomDrawButton::CloseButton(GetThemeService(web_contents_))); | |
723 g_signal_connect(close_button_->widget(), "clicked", | |
724 G_CALLBACK(OnCloseButtonClickThunk), this); | |
725 gtk_widget_set_can_focus(close_button_->widget(), FALSE); | |
726 gtk_box_pack_end(GTK_BOX(close_hbox), close_button_->widget(), | |
727 FALSE, FALSE, 0); | |
728 } | |
729 | |
730 void WebIntentPickerGtk::AddTitle(GtkWidget* containingBox) { | |
731 // Hbox containing the header label. | |
732 GtkWidget* header_hbox = gtk_hbox_new(FALSE, 0); | |
733 gtk_box_pack_start(GTK_BOX(containingBox), header_hbox, TRUE, TRUE, 0); | |
734 | |
735 // Label text will be set in the call to SetActionString(). | |
736 header_label_ = GetThemeService(web_contents_)->BuildLabel( | |
737 std::string(), ui::kGdkBlack); | |
738 gtk_util::ForceFontSizePixels(header_label_, kHeaderLabelPixelSize); | |
739 gtk_box_pack_start(GTK_BOX(header_hbox), header_label_, TRUE, TRUE, 0); | |
740 gtk_misc_set_alignment(GTK_MISC(header_label_), 0, 0); | |
741 } | |
742 | |
743 void WebIntentPickerGtk::UpdateInstalledServices() { | |
744 if (!button_vbox_) | |
745 return; | |
746 | |
747 gtk_util::RemoveAllChildren(button_vbox_); | |
748 | |
749 if (model_->GetInstalledServiceCount() == 0) { | |
750 gtk_widget_hide(gtk_widget_get_parent(button_vbox_)); | |
751 return; | |
752 } | |
753 | |
754 for (size_t i = 0; i < model_->GetInstalledServiceCount(); ++i) { | |
755 const WebIntentPickerModel::InstalledService& installed_service = | |
756 model_->GetInstalledServiceAt(i); | |
757 | |
758 GtkWidget* button = gtk_button_new(); | |
759 gtk_widget_set_tooltip_text(button, installed_service.url.spec().c_str()); | |
760 gtk_button_set_label(GTK_BUTTON(button), | |
761 UTF16ToUTF8(installed_service.title).c_str()); | |
762 gtk_button_set_alignment(GTK_BUTTON(button), 0, 0); | |
763 | |
764 gtk_container_add(GTK_CONTAINER(button_vbox_), button); | |
765 g_signal_connect(button, "clicked", G_CALLBACK(OnServiceButtonClickThunk), | |
766 this); | |
767 | |
768 SetServiceButtonImage(button, installed_service.favicon.ToGdkPixbuf()); | |
769 | |
770 // Must be called after SetServiceButtonImage as the internal label widget | |
771 // is replaced in that call. | |
772 SetWidgetFontSize(button, kMainContentPixelSize); | |
773 } | |
774 | |
775 gtk_widget_show_all(button_vbox_); | |
776 gtk_widget_show(gtk_widget_get_parent(button_vbox_)); | |
777 } | |
778 | |
779 void WebIntentPickerGtk::UpdateCWSLabel() { | |
780 if (!button_vbox_) | |
781 return; | |
782 | |
783 gtk_widget_set_visible(gtk_widget_get_parent(button_vbox_), | |
784 model_->GetInstalledServiceCount() != 0); | |
785 | |
786 std::string label_text = UTF16ToUTF8(model_->GetSuggestionsLinkText()); | |
787 gtk_label_set_text(GTK_LABEL(cws_label_), label_text.c_str()); | |
788 gtk_widget_set_visible(cws_label_, !label_text.empty()); | |
789 } | |
790 | |
791 void WebIntentPickerGtk::UpdateSuggestedExtensions() { | |
792 if (!extensions_vbox_) | |
793 return; | |
794 | |
795 GtkThemeService* theme_service = GetThemeService(web_contents_); | |
796 | |
797 gtk_util::RemoveAllChildren(extensions_vbox_); | |
798 | |
799 if (model_->GetSuggestedExtensionCount() == 0) { | |
800 gtk_widget_hide(gtk_widget_get_parent(extensions_vbox_)); | |
801 return; | |
802 } | |
803 | |
804 gtk_widget_show(gtk_widget_get_parent(extensions_vbox_)); | |
805 | |
806 for (size_t i = 0; i < model_->GetSuggestedExtensionCount(); ++i) { | |
807 const WebIntentPickerModel::SuggestedExtension& extension = | |
808 model_->GetSuggestedExtensionAt(i); | |
809 | |
810 GtkWidget* hbox = gtk_hbox_new(FALSE, ui::kControlSpacing); | |
811 gtk_box_pack_start(GTK_BOX(extensions_vbox_), hbox, FALSE, FALSE, 0); | |
812 | |
813 // Icon. | |
814 GtkWidget* icon = gtk_image_new_from_pixbuf(extension.icon.ToGdkPixbuf()); | |
815 gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 0); | |
816 | |
817 // Title link. | |
818 string16 elided_title = ui::ElideText(extension.title, gfx::Font(), | |
819 kTitleLinkMaxWidth, ui::ELIDE_AT_END); | |
820 GtkWidget* title_link = theme_service->BuildChromeLinkButton( | |
821 UTF16ToUTF8(elided_title).c_str()); | |
822 gtk_chrome_link_button_set_use_gtk_theme(GTK_CHROME_LINK_BUTTON(title_link), | |
823 theme_service->UsingNativeTheme()); | |
824 gtk_util::ForceFontSizePixels(GTK_CHROME_LINK_BUTTON(title_link)->label, | |
825 kMainContentPixelSize); | |
826 g_signal_connect(title_link, "clicked", | |
827 G_CALLBACK(OnExtensionLinkClickThunk), this); | |
828 gtk_box_pack_start(GTK_BOX(hbox), title_link, FALSE, FALSE, 0); | |
829 | |
830 // Stars. | |
831 GtkWidget* stars = CreateStarsWidget(extension.average_rating); | |
832 gtk_box_pack_start(GTK_BOX(hbox), stars, FALSE, FALSE, 0); | |
833 | |
834 // Install button. | |
835 GtkWidget* install_button = gtk_button_new(); | |
836 gtk_button_set_label( | |
837 GTK_BUTTON(install_button), | |
838 l10n_util::GetStringUTF8(IDS_INTENT_PICKER_INSTALL_EXTENSION).c_str()); | |
839 GtkWidget* label = gtk_bin_get_child(GTK_BIN(install_button)); | |
840 gtk_util::ForceFontSizePixels(label, kMainContentPixelSize); | |
841 g_signal_connect(install_button, "clicked", | |
842 G_CALLBACK(OnExtensionInstallButtonClickThunk), this); | |
843 gtk_box_pack_end(GTK_BOX(hbox), install_button, FALSE, FALSE, 0); | |
844 } | |
845 | |
846 gtk_widget_show_all(extensions_vbox_); | |
847 gtk_widget_show(gtk_widget_get_parent(extensions_vbox_)); | |
848 } | |
849 | |
850 void WebIntentPickerGtk::SetWidgetsEnabled(bool enabled) { | |
851 gboolean data = enabled; | |
852 if (button_vbox_) | |
853 gtk_container_foreach(GTK_CONTAINER(button_vbox_), EnableWidgetCallback, | |
854 &data); | |
855 if (extensions_vbox_) | |
856 gtk_container_foreach(GTK_CONTAINER(extensions_vbox_), EnableWidgetCallback, | |
857 &data); | |
858 } | |
859 | |
860 GtkWidget* WebIntentPickerGtk::AddThrobberToExtensionAt(size_t index) { | |
861 // The throbber should be unparented. | |
862 DCHECK(!gtk_widget_get_parent(throbber_->widget())); | |
863 GList* vbox_list = | |
864 gtk_container_get_children(GTK_CONTAINER(extensions_vbox_)); | |
865 GtkWidget* hbox = static_cast<GtkWidget*>(g_list_nth_data(vbox_list, index)); | |
866 GtkWidget* alignment = gtk_alignment_new(0.5, 0.5, 0, 0); | |
867 gtk_container_add(GTK_CONTAINER(alignment), throbber_->widget()); | |
868 gtk_box_pack_end(GTK_BOX(hbox), alignment, FALSE, FALSE, 0); | |
869 g_list_free(vbox_list); | |
870 throbber_->Start(); | |
871 return alignment; | |
872 } | |
873 | |
874 void WebIntentPickerGtk::RemoveThrobber() { | |
875 GtkWidget* alignment = gtk_widget_get_parent(throbber_->widget()); | |
876 DCHECK(alignment); | |
877 gtk_container_remove(GTK_CONTAINER(alignment), throbber_->widget()); | |
878 gtk_widget_destroy(alignment); | |
879 throbber_->Stop(); | |
880 } | |
OLD | NEW |