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/theme_install_bubble_view_gtk.h"
6
7#include <math.h>
8
9#include "chrome/browser/ui/gtk/gtk_util.h"
10#include "chrome/browser/ui/gtk/rounded_window.h"
11#include "content/common/notification_service.h"
12#include "content/common/notification_type.h"
13#include "grit/generated_resources.h"
14#include "ui/base/l10n/l10n_util.h"
15
16// Roundedness of bubble.
17static const int kBubbleCornerRadius = 4;
18
19// Padding between border of bubble and text.
20static const int kTextPadding = 8;
21
22// The bubble is partially transparent.
23static const double kBubbleOpacity = static_cast<double>(0xcc) / 0xff;
24
25ThemeInstallBubbleViewGtk* ThemeInstallBubbleViewGtk::instance_ = NULL;
26
27// ThemeInstallBubbleViewGtk, public -------------------------------------------
28
29// static
30void ThemeInstallBubbleViewGtk::Show(GtkWindow* parent) {
31  if (instance_)
32    instance_->increment_num_loading();
33  else
34    instance_ = new ThemeInstallBubbleViewGtk(GTK_WIDGET(parent));
35}
36
37void ThemeInstallBubbleViewGtk::Observe(NotificationType type,
38                                        const NotificationSource& source,
39                                        const NotificationDetails& details) {
40  if (--num_loads_extant_ == 0)
41    delete this;
42}
43
44// ThemeInstallBubbleViewGtk, private ------------------------------------------
45
46ThemeInstallBubbleViewGtk::ThemeInstallBubbleViewGtk(GtkWidget* parent)
47    : widget_(NULL),
48      parent_(parent),
49      num_loads_extant_(1) {
50  InitWidgets();
51
52  // Close when theme has been installed.
53  registrar_.Add(
54      this,
55      NotificationType::BROWSER_THEME_CHANGED,
56      NotificationService::AllSources());
57
58  // Close when we are installing an extension, not a theme.
59  registrar_.Add(
60      this,
61      NotificationType::NO_THEME_DETECTED,
62      NotificationService::AllSources());
63  registrar_.Add(
64      this,
65      NotificationType::EXTENSION_INSTALLED,
66      NotificationService::AllSources());
67  registrar_.Add(
68      this,
69      NotificationType::EXTENSION_INSTALL_ERROR,
70      NotificationService::AllSources());
71
72  // Don't let the bubble overlap the confirm dialog.
73  registrar_.Add(
74      this,
75      NotificationType::EXTENSION_WILL_SHOW_CONFIRM_DIALOG,
76      NotificationService::AllSources());
77}
78
79ThemeInstallBubbleViewGtk::~ThemeInstallBubbleViewGtk() {
80  gtk_widget_destroy(widget_);
81  instance_ = NULL;
82}
83
84void ThemeInstallBubbleViewGtk::InitWidgets() {
85  // Widgematically, the bubble is just a label in a popup window.
86  widget_ = gtk_window_new(GTK_WINDOW_POPUP);
87  gtk_container_set_border_width(GTK_CONTAINER(widget_), kTextPadding);
88  GtkWidget* label = gtk_label_new(NULL);
89
90  gchar* markup = g_markup_printf_escaped(
91      "<span size='xx-large'>%s</span>",
92      l10n_util::GetStringUTF8(IDS_THEME_LOADING_TITLE).c_str());
93  gtk_label_set_markup(GTK_LABEL(label), markup);
94  g_free(markup);
95
96  gtk_widget_modify_fg(label, GTK_STATE_NORMAL, &gtk_util::kGdkWhite);
97  gtk_container_add(GTK_CONTAINER(widget_), label);
98
99  // We need to show the label so we'll know the widget's actual size when we
100  // call MoveWindow().
101  gtk_widget_show_all(label);
102
103  bool composited = false;
104  if (gtk_util::IsScreenComposited()) {
105    composited = true;
106    GdkScreen* screen = gtk_widget_get_screen(widget_);
107    GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen);
108
109    if (colormap)
110      gtk_widget_set_colormap(widget_, colormap);
111    else
112      composited = false;
113  }
114
115  if (composited) {
116    gtk_widget_set_app_paintable(widget_, TRUE);
117    g_signal_connect(widget_, "expose-event",
118                     G_CALLBACK(OnExposeThunk), this);
119    gtk_widget_realize(widget_);
120  } else {
121    gtk_widget_modify_bg(widget_, GTK_STATE_NORMAL, &gtk_util::kGdkBlack);
122    GdkColor color;
123    gtk_util::ActAsRoundedWindow(widget_, color, kBubbleCornerRadius,
124                                 gtk_util::ROUNDED_ALL, gtk_util::BORDER_NONE);
125  }
126
127  MoveWindow();
128
129  g_signal_connect(widget_, "unmap-event",
130                   G_CALLBACK(OnUnmapEventThunk), this);
131
132  gtk_widget_show_all(widget_);
133}
134
135void ThemeInstallBubbleViewGtk::MoveWindow() {
136  GtkRequisition req;
137  gtk_widget_size_request(widget_, &req);
138
139  gint parent_x = 0, parent_y = 0;
140  gdk_window_get_position(parent_->window, &parent_x, &parent_y);
141  gint parent_width = parent_->allocation.width;
142  gint parent_height = parent_->allocation.height;
143
144  gint x = parent_x + parent_width / 2 - req.width / 2;
145  gint y = parent_y + parent_height / 2 - req.height / 2;
146
147  gtk_window_move(GTK_WINDOW(widget_), x, y);
148}
149
150gboolean ThemeInstallBubbleViewGtk::OnUnmapEvent(GtkWidget* widget) {
151  delete this;
152  return FALSE;
153}
154
155gboolean ThemeInstallBubbleViewGtk::OnExpose(GtkWidget* widget,
156                                             GdkEventExpose* event) {
157  cairo_t* cr = gdk_cairo_create(event->window);
158  gdk_cairo_rectangle(cr, &event->area);
159  cairo_clip(cr);
160
161  cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
162  cairo_paint(cr);
163  cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
164
165  // |inner_rect| has its corners at the centerpoints of the corner arcs.
166  gfx::Rect inner_rect(widget_->allocation);
167  int inset = kBubbleCornerRadius;
168  inner_rect.Inset(inset, inset);
169
170  // The positive y axis is down, so M_PI_2 is down.
171  cairo_arc(cr, inner_rect.x(), inner_rect.y(), inset,
172            M_PI, 3 * M_PI_2);
173  cairo_arc(cr, inner_rect.right(), inner_rect.y(), inset,
174            3 * M_PI_2, 0);
175  cairo_arc(cr, inner_rect.right(), inner_rect.bottom(), inset,
176            0, M_PI_2);
177  cairo_arc(cr, inner_rect.x(), inner_rect.bottom(), inset,
178            M_PI_2, M_PI);
179
180  cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, kBubbleOpacity);
181  cairo_fill(cr);
182  cairo_destroy(cr);
183
184  return FALSE;
185}
186