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/infobars/extension_infobar_gtk.h"
6
7#include "base/debug/trace_event.h"
8#include "chrome/browser/extensions/extension_context_menu_model.h"
9#include "chrome/browser/extensions/extension_host.h"
10#include "chrome/browser/extensions/image_loader.h"
11#include "chrome/browser/platform_util.h"
12#include "chrome/browser/ui/gtk/browser_window_gtk.h"
13#include "chrome/browser/ui/gtk/custom_button.h"
14#include "chrome/browser/ui/gtk/gtk_chrome_button.h"
15#include "chrome/browser/ui/gtk/gtk_util.h"
16#include "chrome/browser/ui/gtk/infobars/infobar_container_gtk.h"
17#include "chrome/common/extensions/extension.h"
18#include "chrome/common/extensions/extension_constants.h"
19#include "chrome/common/extensions/extension_icon_set.h"
20#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
21#include "content/public/browser/render_view_host.h"
22#include "content/public/browser/render_widget_host_view.h"
23#include "extensions/common/extension_resource.h"
24#include "grit/theme_resources.h"
25#include "ui/base/gtk/gtk_signal_registrar.h"
26#include "ui/base/resource/resource_bundle.h"
27#include "ui/gfx/canvas.h"
28#include "ui/gfx/gtk_util.h"
29#include "ui/gfx/image/image.h"
30
31
32// ExtensionInfoBarDelegate ---------------------------------------------------
33
34InfoBar* ExtensionInfoBarDelegate::CreateInfoBar(InfoBarService* owner) {
35  return new ExtensionInfoBarGtk(owner, this);
36}
37
38
39// ExtensionInfoBarGtk --------------------------------------------------------
40
41ExtensionInfoBarGtk::ExtensionInfoBarGtk(InfoBarService* owner,
42                                         ExtensionInfoBarDelegate* delegate)
43    : InfoBarGtk(owner, delegate),
44      delegate_(delegate),
45      view_(NULL),
46      button_(NULL),
47      icon_(NULL),
48      alignment_(NULL),
49      weak_ptr_factory_(this) {
50  GetDelegate()->set_observer(this);
51
52  int height = GetDelegate()->height();
53  SetBarTargetHeight((height > 0) ? (height + kSeparatorLineHeight) : 0);
54}
55
56ExtensionInfoBarGtk::~ExtensionInfoBarGtk() {
57  if (GetDelegate())
58    GetDelegate()->set_observer(NULL);
59}
60
61void ExtensionInfoBarGtk::PlatformSpecificHide(bool animate) {
62  DCHECK(view_);
63  DCHECK(alignment_);
64  gtk_util::RemoveAllChildren(alignment_);
65}
66
67void ExtensionInfoBarGtk::GetTopColor(InfoBarDelegate::Type type,
68                                      double* r, double* g, double* b) {
69  // Extension infobars are always drawn with chrome-theme colors.
70  *r = *g = *b = 233.0 / 255.0;
71}
72
73void ExtensionInfoBarGtk::GetBottomColor(InfoBarDelegate::Type type,
74                                         double* r, double* g, double* b) {
75  *r = *g = *b = 218.0 / 255.0;
76}
77
78void ExtensionInfoBarGtk::InitWidgets() {
79  InfoBarGtk::InitWidgets();
80
81  // Always render the close button as if we were doing chrome style widget
82  // rendering. For extension infobars, we force chrome style rendering because
83  // extension authors are going to expect to match the declared gradient in
84  // extensions_infobar.css, and the close button provided by some GTK+ themes
85  // won't look good on this background.
86  ForceCloseButtonToUseChromeTheme();
87
88  icon_ = gtk_image_new();
89  gtk_misc_set_alignment(GTK_MISC(icon_), 0.5, 0.5);
90
91  extensions::ExtensionHost* extension_host = GetDelegate()->extension_host();
92  const extensions::Extension* extension = extension_host->extension();
93
94  if (extension->ShowConfigureContextMenus()) {
95    button_ = gtk_chrome_button_new();
96    gtk_chrome_button_set_use_gtk_rendering(GTK_CHROME_BUTTON(button_), FALSE);
97    g_object_set_data(G_OBJECT(button_), "left-align-popup",
98                      reinterpret_cast<void*>(true));
99
100    gtk_button_set_image(GTK_BUTTON(button_), icon_);
101    gtk_util::CenterWidgetInHBox(hbox(), button_, false, 0);
102  } else {
103    gtk_util::CenterWidgetInHBox(hbox(), icon_, false, 0);
104  }
105
106  // Start loading the image for the menu button.
107  extensions::ExtensionResource icon_resource =
108      extensions::IconsInfo::GetIconResource(
109          extension,
110          extension_misc::EXTENSION_ICON_BITTY,
111          ExtensionIconSet::MATCH_EXACTLY);
112  // Load image asynchronously, calling back OnImageLoaded.
113  extensions::ImageLoader* loader =
114      extensions::ImageLoader::Get(extension_host->profile());
115  loader->LoadImageAsync(extension, icon_resource,
116                         gfx::Size(extension_misc::EXTENSION_ICON_BITTY,
117                                   extension_misc::EXTENSION_ICON_BITTY),
118                         base::Bind(&ExtensionInfoBarGtk::OnImageLoaded,
119                                    weak_ptr_factory_.GetWeakPtr()));
120
121  // Pad the bottom of the infobar by one pixel for the border.
122  alignment_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
123  gtk_alignment_set_padding(GTK_ALIGNMENT(alignment_), 0, 1, 0, 0);
124  gtk_box_pack_start(GTK_BOX(hbox()), alignment_, TRUE, TRUE, 0);
125
126  view_ = extension_host->view();
127
128  if (gtk_widget_get_parent(view_->native_view())) {
129    gtk_widget_reparent(view_->native_view(), alignment_);
130  } else {
131    gtk_container_add(GTK_CONTAINER(alignment_), view_->native_view());
132  }
133
134  if (button_) {
135    signals()->Connect(button_, "button-press-event",
136                       G_CALLBACK(&OnButtonPressThunk), this);
137  }
138  signals()->Connect(view_->native_view(), "expose-event",
139                     G_CALLBACK(&OnExposeThunk), this);
140  signals()->Connect(view_->native_view(), "size_allocate",
141                     G_CALLBACK(&OnSizeAllocateThunk), this);
142}
143
144void ExtensionInfoBarGtk::StoppedShowing() {
145  if (button_)
146    gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(button_));
147}
148
149void ExtensionInfoBarGtk::OnDelegateDeleted() {
150  delegate_ = NULL;
151}
152
153void ExtensionInfoBarGtk::OnImageLoaded(const gfx::Image& image) {
154
155  DCHECK(icon_);
156  // TODO(erg): IDR_EXTENSIONS_SECTION should have an IDR_INFOBAR_EXTENSIONS
157  // icon of the correct size with real subpixel shading and such.
158  const gfx::ImageSkia* icon = NULL;
159  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
160  if (image.IsEmpty())
161    icon = rb.GetImageSkiaNamed(IDR_EXTENSIONS_SECTION);
162  else
163    icon = image.ToImageSkia();
164
165  SkBitmap bitmap;
166  if (button_) {
167    gfx::ImageSkia* drop_image = rb.GetImageSkiaNamed(IDR_APP_DROPARROW);
168
169    int image_size = extension_misc::EXTENSION_ICON_BITTY;
170    // The margin between the extension icon and the drop-down arrow bitmap.
171    static const int kDropArrowLeftMargin = 3;
172    scoped_ptr<gfx::Canvas> canvas(new gfx::Canvas(
173        gfx::Size(image_size + kDropArrowLeftMargin + drop_image->width(),
174                  image_size), ui::SCALE_FACTOR_100P, false));
175    canvas->DrawImageInt(*icon, 0, 0, icon->width(), icon->height(), 0, 0,
176                         image_size, image_size, false);
177    canvas->DrawImageInt(*drop_image, image_size + kDropArrowLeftMargin,
178                         image_size / 2);
179    bitmap = canvas->ExtractImageRep().sk_bitmap();
180  } else {
181    bitmap = *icon->bitmap();
182  }
183
184  GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(bitmap);
185  gtk_image_set_from_pixbuf(GTK_IMAGE(icon_), pixbuf);
186  g_object_unref(pixbuf);
187}
188
189ExtensionInfoBarDelegate* ExtensionInfoBarGtk::GetDelegate() {
190  return delegate_ ? delegate_->AsExtensionInfoBarDelegate() : NULL;
191}
192
193Browser* ExtensionInfoBarGtk::GetBrowser() {
194  DCHECK(icon_);
195  // Get the Browser object this infobar is attached to.
196  GtkWindow* parent = platform_util::GetTopLevel(icon_);
197  return parent ?
198      BrowserWindowGtk::GetBrowserWindowForNativeWindow(parent)->browser() :
199      NULL;
200}
201
202ExtensionContextMenuModel* ExtensionInfoBarGtk::BuildMenuModel() {
203  const extensions::Extension* extension = GetDelegate()->extension();
204  if (!extension->ShowConfigureContextMenus())
205    return NULL;
206
207  Browser* browser = GetBrowser();
208  if (!browser)
209    return NULL;
210
211  return new ExtensionContextMenuModel(extension, browser);
212}
213
214void ExtensionInfoBarGtk::OnSizeAllocate(GtkWidget* widget,
215                                         GtkAllocation* allocation) {
216  gfx::Size new_size(allocation->width, allocation->height);
217
218  GetDelegate()->extension_host()->view()->render_view_host()->GetView()->
219      SetSize(new_size);
220}
221
222gboolean ExtensionInfoBarGtk::OnButtonPress(GtkWidget* widget,
223                                            GdkEventButton* event) {
224  if (event->button != 1)
225    return FALSE;
226
227  DCHECK(button_);
228
229  context_menu_model_ = BuildMenuModel();
230  if (!context_menu_model_.get())
231    return FALSE;
232
233  gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget),
234                                    GTK_STATE_ACTIVE);
235  ShowMenuWithModel(widget, this, context_menu_model_.get());
236
237  return TRUE;
238}
239
240gboolean ExtensionInfoBarGtk::OnExpose(GtkWidget* sender,
241                                       GdkEventExpose* event) {
242  TRACE_EVENT0("ui::gtk", "ExtensionInfoBarGtk::OnExpose");
243
244  // We also need to draw our infobar arrows over the renderer.
245  static_cast<InfoBarContainerGtk*>(container())->
246      PaintInfobarBitsOn(sender, event, this);
247
248  return FALSE;
249}
250