Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(89)

Side by Side Diff: chrome/browser/ui/gtk/notifications/balloon_view_gtk.cc

Issue 231733005: Delete the GTK+ port of Chrome. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remerge to ToT Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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/notifications/balloon_view_gtk.h"
6
7 #include <gtk/gtk.h>
8
9 #include <string>
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/debug/trace_event.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/strings/string_util.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/notifications/balloon.h"
18 #include "chrome/browser/notifications/desktop_notification_service.h"
19 #include "chrome/browser/notifications/notification.h"
20 #include "chrome/browser/notifications/notification_options_menu_model.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/themes/theme_service.h"
23 #include "chrome/browser/ui/browser_list.h"
24 #include "chrome/browser/ui/browser_window.h"
25 #include "chrome/browser/ui/gtk/custom_button.h"
26 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
27 #include "chrome/browser/ui/gtk/gtk_util.h"
28 #include "chrome/browser/ui/gtk/menu_gtk.h"
29 #include "chrome/browser/ui/gtk/notifications/balloon_view_host_gtk.h"
30 #include "chrome/browser/ui/gtk/rounded_window.h"
31 #include "content/public/browser/notification_source.h"
32 #include "content/public/browser/render_view_host.h"
33 #include "content/public/browser/render_widget_host_view.h"
34 #include "content/public/browser/web_contents.h"
35 #include "extensions/browser/extension_host.h"
36 #include "extensions/browser/process_manager.h"
37 #include "extensions/common/extension.h"
38 #include "grit/generated_resources.h"
39 #include "grit/theme_resources.h"
40 #include "ui/base/gtk/gtk_hig_constants.h"
41 #include "ui/base/l10n/l10n_util.h"
42 #include "ui/base/resource/resource_bundle.h"
43 #include "ui/gfx/animation/slide_animation.h"
44 #include "ui/gfx/canvas.h"
45 #include "ui/gfx/insets.h"
46 #include "ui/gfx/native_widget_types.h"
47
48 namespace {
49
50 // Margin, in pixels, between the notification frame and the contents
51 // of the notification.
52 const int kTopMargin = 0;
53 const int kBottomMargin = 1;
54 const int kLeftMargin = 1;
55 const int kRightMargin = 1;
56
57 // Properties of the origin label.
58 const int kLeftLabelMargin = 8;
59
60 // TODO(johnnyg): Add a shadow for the frame.
61 const int kLeftShadowWidth = 0;
62 const int kRightShadowWidth = 0;
63 const int kTopShadowWidth = 0;
64 const int kBottomShadowWidth = 0;
65
66 // Space in pixels between text and icon on the buttons.
67 const int kButtonSpacing = 3;
68
69 // Number of characters to show in the origin label before ellipsis.
70 const int kOriginLabelCharacters = 18;
71
72 // The shelf height for the system default font size. It is scaled
73 // with changes in the default font size.
74 const int kDefaultShelfHeight = 25;
75
76 // The amount that the bubble collections class offsets from the side of the
77 // screen.
78 const int kScreenBorder = 5;
79
80 // Colors specified in various ways for different parts of the UI.
81 // These match the windows colors in balloon_view.cc
82 const char* kLabelColor = "#7D7D7D";
83 const double kShelfBackgroundColorR = 245.0 / 255.0;
84 const double kShelfBackgroundColorG = 245.0 / 255.0;
85 const double kShelfBackgroundColorB = 245.0 / 255.0;
86 const double kDividerLineColorR = 180.0 / 255.0;
87 const double kDividerLineColorG = 180.0 / 255.0;
88 const double kDividerLineColorB = 180.0 / 255.0;
89
90 // Makes the website label relatively smaller to the base text size.
91 const char* kLabelMarkup = "<span size=\"small\" color=\"%s\">%s</span>";
92
93 } // namespace
94
95 BalloonViewImpl::BalloonViewImpl(BalloonCollection* collection)
96 : balloon_(NULL),
97 theme_service_(NULL),
98 frame_container_(NULL),
99 shelf_(NULL),
100 hbox_(NULL),
101 html_container_(NULL),
102 menu_showing_(false),
103 pending_close_(false),
104 weak_factory_(this) {}
105
106 BalloonViewImpl::~BalloonViewImpl() {
107 if (frame_container_) {
108 GtkWidget* widget = frame_container_;
109 frame_container_ = NULL;
110 gtk_widget_hide(widget);
111 }
112 }
113
114 void BalloonViewImpl::Close(bool by_user) {
115 // Delay a system-initiated close if the menu is showing.
116 if (!by_user && menu_showing_) {
117 pending_close_ = true;
118 } else {
119 base::MessageLoop::current()->PostTask(
120 FROM_HERE,
121 base::Bind(&BalloonViewImpl::DelayedClose,
122 weak_factory_.GetWeakPtr(),
123 by_user));
124 }
125 }
126
127 gfx::Size BalloonViewImpl::GetSize() const {
128 // BalloonView has no size if it hasn't been shown yet (which is when
129 // balloon_ is set).
130 if (!balloon_)
131 return gfx::Size();
132
133 // Although this may not be the instantaneous size of the balloon if
134 // called in the middle of an animation, it is the effective size that
135 // will result from the animation.
136 return gfx::Size(GetDesiredTotalWidth(), GetDesiredTotalHeight());
137 }
138
139 BalloonHost* BalloonViewImpl::GetHost() const {
140 return html_contents_.get();
141 }
142
143 void BalloonViewImpl::DelayedClose(bool by_user) {
144 html_contents_->Shutdown();
145 if (frame_container_) {
146 // It's possible that |frame_container_| was destroyed before the
147 // BalloonViewImpl if our related browser window was closed first.
148 gtk_widget_hide(frame_container_);
149 }
150 balloon_->OnClose(by_user);
151 }
152
153 void BalloonViewImpl::RepositionToBalloon() {
154 if (!frame_container_) {
155 // No need to create a slide animation when this balloon is fading out.
156 return;
157 }
158
159 DCHECK(balloon_);
160
161 // Create an amination from the current position to the desired one.
162 int start_x;
163 int start_y;
164 int start_w;
165 int start_h;
166 gtk_window_get_position(GTK_WINDOW(frame_container_), &start_x, &start_y);
167 gtk_window_get_size(GTK_WINDOW(frame_container_), &start_w, &start_h);
168
169 int end_x = balloon_->GetPosition().x();
170 int end_y = balloon_->GetPosition().y();
171 int end_w = GetDesiredTotalWidth();
172 int end_h = GetDesiredTotalHeight();
173
174 anim_frame_start_ = gfx::Rect(start_x, start_y, start_w, start_h);
175 anim_frame_end_ = gfx::Rect(end_x, end_y, end_w, end_h);
176 animation_.reset(new gfx::SlideAnimation(this));
177 animation_->Show();
178 }
179
180 void BalloonViewImpl::AnimationProgressed(const gfx::Animation* animation) {
181 DCHECK_EQ(animation, animation_.get());
182
183 // Linear interpolation from start to end position.
184 double end = animation->GetCurrentValue();
185 double start = 1.0 - end;
186
187 gfx::Rect frame_position(
188 static_cast<int>(start * anim_frame_start_.x() +
189 end * anim_frame_end_.x()),
190 static_cast<int>(start * anim_frame_start_.y() +
191 end * anim_frame_end_.y()),
192 static_cast<int>(start * anim_frame_start_.width() +
193 end * anim_frame_end_.width()),
194 static_cast<int>(start * anim_frame_start_.height() +
195 end * anim_frame_end_.height()));
196 gtk_window_resize(GTK_WINDOW(frame_container_),
197 frame_position.width(), frame_position.height());
198 gtk_window_move(GTK_WINDOW(frame_container_),
199 frame_position.x(), frame_position.y());
200
201 gfx::Rect contents_rect = GetContentsRectangle();
202 html_contents_->UpdateActualSize(contents_rect.size());
203 }
204
205 void BalloonViewImpl::Show(Balloon* balloon) {
206 theme_service_ = GtkThemeService::GetFrom(balloon->profile());
207
208 const std::string source_label_text = l10n_util::GetStringFUTF8(
209 IDS_NOTIFICATION_BALLOON_SOURCE_LABEL,
210 balloon->notification().display_source());
211 const std::string options_text =
212 l10n_util::GetStringUTF8(IDS_NOTIFICATION_OPTIONS_MENU_LABEL);
213 const std::string dismiss_text =
214 l10n_util::GetStringUTF8(IDS_NOTIFICATION_BALLOON_DISMISS_LABEL);
215
216 balloon_ = balloon;
217 frame_container_ = gtk_window_new(GTK_WINDOW_POPUP);
218
219 g_signal_connect(frame_container_, "expose-event",
220 G_CALLBACK(OnExposeThunk), this);
221 g_signal_connect(frame_container_, "destroy",
222 G_CALLBACK(OnDestroyThunk), this);
223
224 // Construct the options menu.
225 options_menu_model_.reset(new NotificationOptionsMenuModel(balloon_));
226 options_menu_.reset(new MenuGtk(this, options_menu_model_.get()));
227
228 // Create a BalloonViewHost to host the HTML contents of this balloon.
229 html_contents_.reset(new BalloonViewHost(balloon));
230 html_contents_->Init();
231 gfx::NativeView contents = html_contents_->native_view();
232 g_signal_connect_after(contents, "expose-event",
233 G_CALLBACK(OnContentsExposeThunk), this);
234
235 // Divide the frame vertically into the shelf and the content area.
236 GtkWidget* vbox = gtk_vbox_new(0, 0);
237 gtk_container_add(GTK_CONTAINER(frame_container_), vbox);
238
239 // Create the toolbar.
240 shelf_ = gtk_hbox_new(FALSE, 0);
241 gtk_widget_set_size_request(GTK_WIDGET(shelf_), -1, GetShelfHeight());
242 gtk_container_add(GTK_CONTAINER(vbox), shelf_);
243
244 // Create a label for the source of the notification and add it to the
245 // toolbar.
246 GtkWidget* source_label_ = gtk_label_new(NULL);
247 char* markup = g_markup_printf_escaped(kLabelMarkup,
248 kLabelColor,
249 source_label_text.c_str());
250 gtk_label_set_markup(GTK_LABEL(source_label_), markup);
251 g_free(markup);
252 gtk_label_set_max_width_chars(GTK_LABEL(source_label_),
253 kOriginLabelCharacters);
254 gtk_label_set_ellipsize(GTK_LABEL(source_label_), PANGO_ELLIPSIZE_END);
255 GtkWidget* label_alignment = gtk_alignment_new(0, 0.5, 0, 0);
256 gtk_alignment_set_padding(GTK_ALIGNMENT(label_alignment),
257 0, 0, kLeftLabelMargin, 0);
258 gtk_container_add(GTK_CONTAINER(label_alignment), source_label_);
259 gtk_box_pack_start(GTK_BOX(shelf_), label_alignment, FALSE, FALSE, 0);
260
261 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
262
263 // Create a button to dismiss the balloon and add it to the toolbar.
264 close_button_.reset(CustomDrawButton::CloseButtonBar(theme_service_));
265 close_button_->SetBackground(
266 SK_ColorBLACK,
267 rb.GetImageNamed(IDR_CLOSE_1).AsBitmap(),
268 rb.GetImageNamed(IDR_CLOSE_1_MASK).AsBitmap());
269 gtk_widget_set_tooltip_text(close_button_->widget(), dismiss_text.c_str());
270 g_signal_connect(close_button_->widget(), "clicked",
271 G_CALLBACK(OnCloseButtonThunk), this);
272 gtk_widget_set_can_focus(close_button_->widget(), FALSE);
273 GtkWidget* close_alignment = gtk_alignment_new(0.0, 0.5, 0, 0);
274 gtk_container_add(GTK_CONTAINER(close_alignment), close_button_->widget());
275 gtk_box_pack_end(GTK_BOX(shelf_), close_alignment, FALSE, FALSE,
276 kButtonSpacing);
277
278 // Create a button for showing the options menu, and add it to the toolbar.
279 options_menu_button_.reset(new CustomDrawButton(IDR_BALLOON_WRENCH,
280 IDR_BALLOON_WRENCH_P,
281 IDR_BALLOON_WRENCH_H,
282 0));
283 gtk_widget_set_tooltip_text(options_menu_button_->widget(),
284 options_text.c_str());
285 g_signal_connect(options_menu_button_->widget(), "button-press-event",
286 G_CALLBACK(OnOptionsMenuButtonThunk), this);
287 gtk_widget_set_can_focus(options_menu_button_->widget(), FALSE);
288 GtkWidget* options_alignment = gtk_alignment_new(0.0, 0.5, 0, 0);
289 gtk_container_add(GTK_CONTAINER(options_alignment),
290 options_menu_button_->widget());
291 gtk_box_pack_end(GTK_BOX(shelf_), options_alignment, FALSE, FALSE, 0);
292
293 // Add main contents to bubble.
294 GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
295 gtk_alignment_set_padding(
296 GTK_ALIGNMENT(alignment),
297 kTopMargin, kBottomMargin, kLeftMargin, kRightMargin);
298 gtk_widget_show_all(alignment);
299 gtk_container_add(GTK_CONTAINER(alignment), contents);
300 gtk_container_add(GTK_CONTAINER(vbox), alignment);
301 gtk_widget_show_all(vbox);
302
303 notification_registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
304 content::Source<ThemeService>(theme_service_));
305
306 // We don't do InitThemesFor() because it just forces a redraw.
307 gtk_util::ActAsRoundedWindow(frame_container_, ui::kGdkBlack, 3,
308 gtk_util::ROUNDED_ALL,
309 gtk_util::BORDER_ALL);
310
311 // Realize the frame container so we can do size calculations.
312 gtk_widget_realize(frame_container_);
313
314 // Update to make sure we have everything sized properly and then move our
315 // window offscreen for its initial animation.
316 html_contents_->UpdateActualSize(balloon_->content_size());
317 int window_width;
318 gtk_window_get_size(GTK_WINDOW(frame_container_), &window_width, NULL);
319
320 int pos_x = gdk_screen_width() - window_width - kScreenBorder;
321 int pos_y = gdk_screen_height();
322 gtk_window_move(GTK_WINDOW(frame_container_), pos_x, pos_y);
323 balloon_->SetPosition(gfx::Point(pos_x, pos_y), false);
324 gtk_widget_show_all(frame_container_);
325
326 notification_registrar_.Add(this,
327 chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED,
328 content::Source<Balloon>(balloon));
329 }
330
331 void BalloonViewImpl::Update() {
332 DCHECK(html_contents_.get()) << "BalloonView::Update called before Show";
333 if (!html_contents_->web_contents())
334 return;
335 html_contents_->web_contents()->GetController().LoadURL(
336 balloon_->notification().content_url(), content::Referrer(),
337 content::PAGE_TRANSITION_LINK, std::string());
338 }
339
340 gfx::Point BalloonViewImpl::GetContentsOffset() const {
341 return gfx::Point(kLeftShadowWidth + kLeftMargin,
342 GetShelfHeight() + kTopShadowWidth + kTopMargin);
343 }
344
345 int BalloonViewImpl::GetShelfHeight() const {
346 // TODO(johnnyg): add scaling here.
347 return kDefaultShelfHeight;
348 }
349
350 int BalloonViewImpl::GetDesiredTotalWidth() const {
351 return balloon_->content_size().width() +
352 kLeftMargin + kRightMargin + kLeftShadowWidth + kRightShadowWidth;
353 }
354
355 int BalloonViewImpl::GetDesiredTotalHeight() const {
356 return balloon_->content_size().height() +
357 kTopMargin + kBottomMargin + kTopShadowWidth + kBottomShadowWidth +
358 GetShelfHeight();
359 }
360
361 gfx::Rect BalloonViewImpl::GetContentsRectangle() const {
362 if (!frame_container_)
363 return gfx::Rect();
364
365 gfx::Size content_size = balloon_->content_size();
366 gfx::Point offset = GetContentsOffset();
367 int x = 0, y = 0;
368 gtk_window_get_position(GTK_WINDOW(frame_container_), &x, &y);
369 return gfx::Rect(x + offset.x(), y + offset.y(),
370 content_size.width(), content_size.height());
371 }
372
373 void BalloonViewImpl::Observe(int type,
374 const content::NotificationSource& source,
375 const content::NotificationDetails& details) {
376 if (type == chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED) {
377 // If the renderer process attached to this balloon is disconnected
378 // (e.g., because of a crash), we want to close the balloon.
379 notification_registrar_.Remove(this,
380 chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED,
381 content::Source<Balloon>(balloon_));
382 Close(false);
383 } else if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) {
384 // Since all the buttons change their own properties, and our expose does
385 // all the real differences, we'll need a redraw.
386 gtk_widget_queue_draw(frame_container_);
387 } else {
388 NOTREACHED();
389 }
390 }
391
392 void BalloonViewImpl::OnCloseButton(GtkWidget* widget) {
393 Close(true);
394 }
395
396 // We draw black dots on the bottom left and right corners to fill in the
397 // border. Otherwise, the border has a gap because the sharp corners of the
398 // HTML view cut off the roundedness of the notification window.
399 gboolean BalloonViewImpl::OnContentsExpose(GtkWidget* sender,
400 GdkEventExpose* event) {
401 TRACE_EVENT0("ui::gtk", "BalloonViewImpl::OnContentsExpose");
402 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(sender));
403 gdk_cairo_rectangle(cr, &event->area);
404 cairo_clip(cr);
405
406 GtkAllocation allocation;
407 gtk_widget_get_allocation(sender, &allocation);
408
409 // According to a discussion on a mailing list I found, these degenerate
410 // paths are the officially supported way to draw points in Cairo.
411 cairo_set_source_rgb(cr, 0, 0, 0);
412 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
413 cairo_set_line_width(cr, 1.0);
414 cairo_move_to(cr, 0.5, allocation.height - 0.5);
415 cairo_close_path(cr);
416 cairo_move_to(cr, allocation.width - 0.5, allocation.height - 0.5);
417 cairo_close_path(cr);
418 cairo_stroke(cr);
419 cairo_destroy(cr);
420
421 return FALSE;
422 }
423
424 gboolean BalloonViewImpl::OnExpose(GtkWidget* sender, GdkEventExpose* event) {
425 TRACE_EVENT0("ui::gtk", "BalloonViewImpl::OnExpose");
426 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(sender));
427 gdk_cairo_rectangle(cr, &event->area);
428 cairo_clip(cr);
429
430 gfx::Size content_size = balloon_->content_size();
431 gfx::Point offset = GetContentsOffset();
432
433 // Draw a background color behind the shelf.
434 cairo_set_source_rgb(cr, kShelfBackgroundColorR,
435 kShelfBackgroundColorG, kShelfBackgroundColorB);
436 cairo_rectangle(cr, kLeftMargin, kTopMargin + 0.5,
437 content_size.width() - 0.5, GetShelfHeight());
438 cairo_fill(cr);
439
440 // Now draw a one pixel line between content and shelf.
441 cairo_move_to(cr, offset.x(), offset.y() - 1);
442 cairo_line_to(cr, offset.x() + content_size.width(), offset.y() - 1);
443 cairo_set_line_width(cr, 0.5);
444 cairo_set_source_rgb(cr, kDividerLineColorR,
445 kDividerLineColorG, kDividerLineColorB);
446 cairo_stroke(cr);
447
448 cairo_destroy(cr);
449
450 return FALSE;
451 }
452
453 void BalloonViewImpl::OnOptionsMenuButton(GtkWidget* widget,
454 GdkEventButton* event) {
455 menu_showing_ = true;
456 options_menu_->PopupForWidget(widget, event->button, event->time);
457 }
458
459 // Called when the menu stops showing.
460 void BalloonViewImpl::StoppedShowing() {
461 menu_showing_ = false;
462 if (pending_close_) {
463 base::MessageLoop::current()->PostTask(
464 FROM_HERE,
465 base::Bind(
466 &BalloonViewImpl::DelayedClose, weak_factory_.GetWeakPtr(), false));
467 }
468 }
469
470 gboolean BalloonViewImpl::OnDestroy(GtkWidget* widget) {
471 frame_container_ = NULL;
472 Close(false);
473 return FALSE; // Propagate.
474 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698