summaryrefslogtreecommitdiffstats
path: root/chrome/browser/gtk/notifications/balloon_view_gtk.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/gtk/notifications/balloon_view_gtk.cc')
-rw-r--r--chrome/browser/gtk/notifications/balloon_view_gtk.cc374
1 files changed, 374 insertions, 0 deletions
diff --git a/chrome/browser/gtk/notifications/balloon_view_gtk.cc b/chrome/browser/gtk/notifications/balloon_view_gtk.cc
new file mode 100644
index 0000000..fb2d436
--- /dev/null
+++ b/chrome/browser/gtk/notifications/balloon_view_gtk.cc
@@ -0,0 +1,374 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/gtk/notifications/balloon_view_gtk.h"
+
+#include <string>
+#include <vector>
+
+#include "app/gfx/canvas.h"
+#include "app/gfx/insets.h"
+#include "app/gfx/native_widget_types.h"
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "app/slide_animation.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "chrome/browser/browser_theme_provider.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/browser_window.h"
+#include "chrome/browser/extensions/extension_host.h"
+#include "chrome/browser/extensions/extension_process_manager.h"
+#include "chrome/browser/gtk/gtk_chrome_button.h"
+#include "chrome/browser/gtk/gtk_theme_provider.h"
+#include "chrome/browser/gtk/info_bubble_gtk.h"
+#include "chrome/browser/gtk/menu_gtk.h"
+#include "chrome/browser/gtk/nine_box.h"
+#include "chrome/browser/gtk/notifications/balloon_view_host_gtk.h"
+#include "chrome/browser/gtk/notifications/notification_options_menu_model.h"
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/notifications/desktop_notification_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/gtk_util.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_source.h"
+#include "chrome/common/notification_type.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+
+namespace {
+
+// Margin, in pixels, between the notification frame and the contents
+// of the notification.
+const int kTopMargin = 1;
+const int kBottomMargin = 1;
+const int kLeftMargin = 1;
+const int kRightMargin = 1;
+
+// How many pixels of overlap there is between the shelf top and the
+// balloon bottom.
+const int kShelfBorderTopOverlap = 3;
+
+// Properties of the dismiss button.
+const int kDismissButtonWidth = 60;
+const int kDismissButtonHeight = 20;
+
+// Properties of the options menu.
+const int kOptionsMenuWidth = 60;
+const int kOptionsMenuHeight = 20;
+
+// Properties of the origin label.
+const int kLeftLabelMargin = 5;
+
+// TODO(johnnyg): Add a shadow for the frame.
+const int kLeftShadowWidth = 0;
+const int kRightShadowWidth = 0;
+const int kTopShadowWidth = 0;
+const int kBottomShadowWidth = 0;
+
+// Space in pixels between text and icon on the buttons.
+const int kButtonIconSpacing = 3;
+
+// Number of characters to show in the origin label before ellipsis.
+const int kOriginLabelCharacters = 18;
+
+// The shelf height for the system default font size. It is scaled
+// with changes in the default font size.
+const int kDefaultShelfHeight = 24;
+
+} // namespace
+
+BalloonViewImpl::BalloonViewImpl()
+ : balloon_(NULL),
+ frame_container_(NULL),
+ html_container_(NULL),
+ html_contents_(NULL),
+ method_factory_(this),
+ close_button_(NULL),
+ animation_(NULL) {
+ // Load the sprites for the frames.
+ // Insets are such because the sprites have 3x3 corners.
+ shelf_background_.reset(new NineBox(IDR_BALLOON_SHELF, 3, 3, 3, 3));
+ balloon_background_.reset(new NineBox(IDR_BALLOON_BORDER, 3, 3, 3, 3));
+}
+
+BalloonViewImpl::~BalloonViewImpl() {
+}
+
+void BalloonViewImpl::Close(bool by_user) {
+ MessageLoop::current()->PostTask(FROM_HERE,
+ method_factory_.NewRunnableMethod(
+ &BalloonViewImpl::DelayedClose, by_user));
+}
+
+gfx::Size BalloonViewImpl::GetSize() const {
+ // BalloonView has no size if it hasn't been shown yet (which is when
+ // balloon_ is set).
+ if (!balloon_)
+ return gfx::Size();
+
+ // Although this may not be the instantaneous size of the balloon if
+ // called in the middle of an animation, it is the effective size that
+ // will result from the animation.
+ return gfx::Size(GetDesiredTotalWidth(), GetDesiredTotalHeight());
+}
+
+void BalloonViewImpl::DelayedClose(bool by_user) {
+ html_contents_->Shutdown();
+ gtk_widget_hide(frame_container_);
+ balloon_->OnClose(by_user);
+}
+
+void BalloonViewImpl::InitToolbarStyle() {
+ // This only needs to happen once.
+ static bool initialized = false;
+ if (!initialized) {
+ gtk_rc_parse_string(
+ "style \"chrome-notification-toolbar\" {"
+ " xthickness = 0\n"
+ " ythickness = 0\n"
+ " GtkWidget::focus-padding = 0\n"
+ " GtkContainer::border-width = 0\n"
+ " GtkToolBar::internal-padding = 2\n"
+ " GtkToolBar::shadow-type = GTK_SHADOW_NONE\n"
+ "}\n"
+ "widget \"*chrome-notification-toolbar\""
+ "style \"chrome-notification-toolbar\"");
+ initialized = true;
+ }
+}
+
+void BalloonViewImpl::RepositionToBalloon() {
+ DCHECK(frame_container_);
+ DCHECK(balloon_);
+
+ // Create an amination from the current position to the desired one.
+ int start_x;
+ int start_y;
+ int start_w;
+ int start_h;
+ gtk_window_get_position(GTK_WINDOW(frame_container_), &start_x, &start_y);
+ gtk_window_get_size(GTK_WINDOW(frame_container_), &start_w, &start_h);
+
+ int end_x = balloon_->position().x();
+ int end_y = balloon_->position().y();
+ int end_w = GetDesiredTotalWidth();
+ int end_h = GetDesiredTotalHeight();
+
+ anim_frame_start_ = gfx::Rect(start_x, start_y, start_w, start_h);
+ anim_frame_end_ = gfx::Rect(end_x, end_y, end_w, end_h);
+ animation_.reset(new SlideAnimation(this));
+ animation_->Show();
+}
+
+void BalloonViewImpl::AnimationProgressed(const Animation* animation) {
+ DCHECK_EQ(animation, animation_.get());
+
+ // Linear interpolation from start to end position.
+ double end = animation->GetCurrentValue();
+ double start = 1.0 - end;
+
+ gfx::Rect frame_position(
+ static_cast<int>(start * anim_frame_start_.x() +
+ end * anim_frame_end_.x()),
+ static_cast<int>(start * anim_frame_start_.y() +
+ end * anim_frame_end_.y()),
+ static_cast<int>(start * anim_frame_start_.width() +
+ end * anim_frame_end_.width()),
+ static_cast<int>(start * anim_frame_start_.height() +
+ end * anim_frame_end_.height()));
+ gtk_window_resize(GTK_WINDOW(frame_container_),
+ frame_position.width(), frame_position.height());
+ gtk_window_move(GTK_WINDOW(frame_container_),
+ frame_position.x(), frame_position.y());
+
+ gfx::Rect contents_rect = GetContentsRectangle();
+ html_contents_->UpdateActualSize(contents_rect.size());
+}
+
+void PrepareButtonWithIcon(GtkWidget* button,
+ const std::string& text_utf8, int icon_id) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ GdkPixbuf* pixbuf = rb.GetPixbufNamed(icon_id);
+ GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+
+ GtkWidget* box = gtk_hbox_new(FALSE, kButtonIconSpacing);
+
+ GtkWidget* label = gtk_label_new(text_utf8.c_str());
+ gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
+
+ GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 1, 1, 1, 1);
+ gtk_container_add(GTK_CONTAINER(alignment), box);
+ gtk_container_add(GTK_CONTAINER(button), alignment);
+
+ gtk_widget_show_all(alignment);
+}
+
+void BalloonViewImpl::Show(Balloon* balloon) {
+ GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom(
+ balloon->profile());
+
+ const std::string source_label_text = l10n_util::GetStringFUTF8(
+ IDS_NOTIFICATION_BALLOON_SOURCE_LABEL,
+ WideToUTF16(balloon->notification().display_source()));
+ const std::string options_text =
+ l10n_util::GetStringUTF8(IDS_NOTIFICATION_OPTIONS_MENU_LABEL);
+ const std::string dismiss_text =
+ l10n_util::GetStringUTF8(IDS_NOTIFICATION_BALLOON_DISMISS_LABEL);
+
+ balloon_ = balloon;
+ frame_container_ = gtk_window_new(GTK_WINDOW_POPUP);
+
+ // Construct the options menu.
+ options_menu_model_.reset(new NotificationOptionsMenuModel(balloon_));
+ options_menu_.reset(new MenuGtk(this, options_menu_model_.get()));
+
+ // Create a BalloonViewHost to host the HTML contents of this balloon.
+ html_contents_ = new BalloonViewHost(balloon);
+ html_contents_->Init();
+ gfx::NativeView contents = html_contents_->native_view();
+
+ gtk_widget_set_app_paintable(frame_container_, TRUE);
+ gtk_widget_realize(frame_container_);
+
+ // Divide the frame vertically into the content area and the shelf.
+ GtkWidget* vbox = gtk_vbox_new(0, 0);
+ gtk_container_add(GTK_CONTAINER(frame_container_), vbox);
+
+ GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding(
+ GTK_ALIGNMENT(alignment),
+ kTopMargin, kBottomMargin, kLeftMargin, kRightMargin);
+ gtk_widget_show_all(alignment);
+ gtk_container_add(GTK_CONTAINER(alignment), contents);
+ gtk_container_add(GTK_CONTAINER(vbox), alignment);
+
+ shelf_ = gtk_hbox_new(0, 0);
+ GtkWidget* alignment2 = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(alignment2), 0, 0, 10, 0);
+ gtk_container_add(GTK_CONTAINER(vbox), shelf_);
+
+ // Create a toolbar and add it to the shelf.
+ toolbar_ = gtk_toolbar_new();
+ gtk_widget_set_name(toolbar_, "chrome-notification-toolbar");
+ gtk_util::SuppressDefaultPainting(toolbar_);
+ gtk_widget_set_size_request(GTK_WIDGET(toolbar_), -1, GetShelfHeight());
+ gtk_container_add(GTK_CONTAINER(alignment2), toolbar_);
+ gtk_container_add(GTK_CONTAINER(shelf_), alignment2);
+ gtk_widget_show_all(vbox);
+
+ InitToolbarStyle();
+
+ g_signal_connect(frame_container_, "expose-event",
+ G_CALLBACK(HandleExposeThunk), this);
+
+ // Create a label for the source of the notification and add it to the
+ // toolbar.
+ GtkWidget* source_label_ = gtk_label_new(source_label_text.c_str());
+ gtk_label_set_max_width_chars(GTK_LABEL(source_label_),
+ kOriginLabelCharacters);
+ gtk_label_set_ellipsize(GTK_LABEL(source_label_), PANGO_ELLIPSIZE_END);
+ GtkToolItem* label_toolitem = gtk_tool_item_new();
+ gtk_container_add(GTK_CONTAINER(label_toolitem), source_label_);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar_), label_toolitem, 0);
+ gtk_widget_show_all(GTK_WIDGET(label_toolitem));
+
+ // Create a button for showing the options menu, and add it to the toolbar.
+ options_menu_button_ = theme_provider->BuildChromeButton();
+ g_signal_connect(G_OBJECT(options_menu_button_), "clicked",
+ G_CALLBACK(HandleOptionsMenuButtonThunk), this);
+ PrepareButtonWithIcon(options_menu_button_, options_text,
+ IDR_BALLOON_OPTIONS_ARROW_HOVER);
+ GtkToolItem* options_menu_toolitem = gtk_tool_item_new();
+ gtk_container_add(GTK_CONTAINER(options_menu_toolitem), options_menu_button_);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar_), options_menu_toolitem, 1);
+ gtk_widget_show_all(GTK_WIDGET(options_menu_toolitem));
+
+ // Create a button to dismiss the balloon and add it to the toolbar.
+ close_button_ = theme_provider->BuildChromeButton();
+ g_signal_connect(G_OBJECT(close_button_), "clicked",
+ G_CALLBACK(HandleCloseButtonThunk), this);
+ PrepareButtonWithIcon(close_button_, dismiss_text, IDR_BALLOON_CLOSE_HOVER);
+ GtkToolItem* close_button_toolitem = gtk_tool_item_new();
+ gtk_container_add(GTK_CONTAINER(close_button_toolitem), close_button_);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar_), close_button_toolitem, 2);
+ gtk_widget_show_all(GTK_WIDGET(close_button_toolitem));
+
+ // Position the view elements according to the balloon position and show.
+ RepositionToBalloon();
+ gtk_widget_show(frame_container_);
+
+ notification_registrar_.Add(this,
+ NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon));
+}
+
+void BalloonViewImpl::RunOptionsMenu() {
+ options_menu_->PopupAsContext(gtk_get_current_event_time());
+}
+
+gfx::Point BalloonViewImpl::GetContentsOffset() const {
+ return gfx::Point(kTopShadowWidth + kTopMargin,
+ kLeftShadowWidth + kLeftMargin);
+}
+
+int BalloonViewImpl::GetShelfHeight() const {
+ // TODO(johnnyg): add scaling here.
+ return kDefaultShelfHeight;
+}
+
+int BalloonViewImpl::GetBalloonFrameHeight() const {
+ return GetDesiredTotalHeight() - GetShelfHeight();
+}
+
+int BalloonViewImpl::GetDesiredTotalWidth() const {
+ return balloon_->content_size().width() +
+ kLeftMargin + kRightMargin + kLeftShadowWidth + kRightShadowWidth;
+}
+
+int BalloonViewImpl::GetDesiredTotalHeight() const {
+ return balloon_->content_size().height() +
+ kTopMargin + kBottomMargin + kTopShadowWidth + kBottomShadowWidth +
+ GetShelfHeight();
+}
+
+gfx::Rect BalloonViewImpl::GetContentsRectangle() const {
+ if (!frame_container_)
+ return gfx::Rect();
+
+ gfx::Size content_size = balloon_->content_size();
+ gfx::Point offset = GetContentsOffset();
+ int x = 0, y = 0;
+ gtk_window_get_position(GTK_WINDOW(frame_container_), &x, &y);
+ return gfx::Rect(x + offset.x(), y + offset.y(),
+ content_size.width(), content_size.height());
+}
+
+gboolean BalloonViewImpl::HandleExpose() {
+ // Draw the background images.
+ balloon_background_->RenderToWidget(frame_container_);
+ shelf_background_->RenderToWidget(shelf_);
+ return FALSE;
+}
+
+void BalloonViewImpl::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type != NotificationType::NOTIFY_BALLOON_DISCONNECTED) {
+ NOTREACHED();
+ return;
+ }
+
+ // If the renderer process attached to this balloon is disconnected
+ // (e.g., because of a crash), we want to close the balloon.
+ notification_registrar_.Remove(this,
+ NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon_));
+ Close(false);
+}