summaryrefslogtreecommitdiffstats
path: root/chrome/browser/gtk
diff options
context:
space:
mode:
authorestade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-28 01:18:29 +0000
committerestade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-28 01:18:29 +0000
commit2ca77d23336c7ff61b7856aed33197df1e851edb (patch)
tree8b8ad47ff6eafc1ab017e843e595d6e18f62ab97 /chrome/browser/gtk
parent1f7b4176c3b8db732c8cc1842283d78908aa9cf6 (diff)
downloadchromium_src-2ca77d23336c7ff61b7856aed33197df1e851edb.zip
chromium_src-2ca77d23336c7ff61b7856aed33197df1e851edb.tar.gz
chromium_src-2ca77d23336c7ff61b7856aed33197df1e851edb.tar.bz2
GTK: throb a bookmark folder button when a URL is added to it.
BUG=16782 TEST=add bookmark as a descendant of a bookmark bar folder node, watch it throb. Clicking it should make the throbbing stop. Review URL: http://codereview.chromium.org/543201 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37362 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/gtk')
-rw-r--r--chrome/browser/gtk/bookmark_bar_gtk.cc80
-rw-r--r--chrome/browser/gtk/bookmark_bar_gtk.h21
-rw-r--r--chrome/browser/gtk/gtk_chrome_button.cc41
-rw-r--r--chrome/browser/gtk/gtk_chrome_button.h8
-rw-r--r--chrome/browser/gtk/nine_box.cc62
-rw-r--r--chrome/browser/gtk/nine_box.h5
-rw-r--r--chrome/browser/gtk/throb_controller_gtk.cc95
-rw-r--r--chrome/browser/gtk/throb_controller_gtk.h67
8 files changed, 337 insertions, 42 deletions
diff --git a/chrome/browser/gtk/bookmark_bar_gtk.cc b/chrome/browser/gtk/bookmark_bar_gtk.cc
index b8c3c20..ad75450 100644
--- a/chrome/browser/gtk/bookmark_bar_gtk.cc
+++ b/chrome/browser/gtk/bookmark_bar_gtk.cc
@@ -30,6 +30,7 @@
#include "chrome/browser/gtk/rounded_window.h"
#include "chrome/browser/gtk/tabstrip_origin_provider.h"
#include "chrome/browser/gtk/tabs/tab_strip_gtk.h"
+#include "chrome/browser/gtk/throb_controller_gtk.h"
#include "chrome/browser/gtk/view_id_util.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/ntp_background_util.h"
@@ -133,7 +134,7 @@ BookmarkBarGtk::BookmarkBarGtk(BrowserWindowGtk* window,
menu_bar_helper_(this),
floating_(false),
last_allocation_width_(-1),
- event_box_paint_factory_(this) {
+ method_factory_(this) {
if (profile->GetProfileSyncService()) {
// Obtain a pointer to the profile sync service and add our instance as an
// observer.
@@ -403,13 +404,13 @@ void BookmarkBarGtk::BookmarkNodeMoved(BookmarkModel* model,
void BookmarkBarGtk::BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent,
int index) {
+ const BookmarkNode* node = parent->GetChild(index);
if (parent != model_->GetBookmarkBarNode()) {
- // We only care about nodes on the bookmark bar.
+ StartThrobbing(node);
return;
}
DCHECK(index >= 0 && index <= GetBookmarkButtonCount());
- const BookmarkNode* node = parent->GetChild(index);
GtkToolItem* item = CreateBookmarkToolItem(node);
gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_.get()),
item, index);
@@ -418,6 +419,10 @@ void BookmarkBarGtk::BookmarkNodeAdded(BookmarkModel* model,
SetInstructionState();
SetChevronState();
+
+ MessageLoop::current()->PostTask(FROM_HERE,
+ method_factory_.NewRunnableMethod(
+ &BookmarkBarGtk::StartThrobbing, node));
}
void BookmarkBarGtk::BookmarkNodeRemoved(BookmarkModel* model,
@@ -686,6 +691,61 @@ bool BookmarkBarGtk::GetTabContentsSize(gfx::Size* size) {
return true;
}
+void BookmarkBarGtk::StartThrobbing(const BookmarkNode* node) {
+ const BookmarkNode* parent_on_bb = NULL;
+ for (const BookmarkNode* parent = node; parent;
+ parent = parent->GetParent()) {
+ if (parent->GetParent() == model_->GetBookmarkBarNode()) {
+ parent_on_bb = parent;
+ break;
+ }
+ }
+
+ // Descendant of "Other Bookmarks".
+ if (!parent_on_bb)
+ return;
+
+ int hidden = GetFirstHiddenBookmark(0, NULL);
+ int idx = model_->GetBookmarkBarNode()->IndexOfChild(parent_on_bb);
+ GtkWidget* widget_to_throb = NULL;
+
+ if (hidden >= idx) {
+ widget_to_throb = overflow_button_;
+ } else {
+ if (parent_on_bb->is_url())
+ return;
+ widget_to_throb = gtk_bin_get_child(GTK_BIN(gtk_toolbar_get_nth_item(
+ GTK_TOOLBAR(bookmark_toolbar_.get()), idx)));
+ }
+
+ SetThrobbingWidget(widget_to_throb);
+}
+
+void BookmarkBarGtk::SetThrobbingWidget(GtkWidget* widget) {
+ if (throbbing_widget_) {
+ ThrobControllerGtk* throbber =
+ ThrobControllerGtk::GetThrobControllerGtk(throbbing_widget_);
+ if (throbber)
+ throbber->Destroy();
+
+ g_signal_handlers_disconnect_by_func(
+ throbbing_widget_,
+ reinterpret_cast<gpointer>(OnThrobbingWidgetDestroy),
+ this);
+ g_object_unref(throbbing_widget_);
+ throbbing_widget_ = NULL;
+ }
+
+ if (widget) {
+ throbbing_widget_ = widget;
+ g_object_ref(throbbing_widget_);
+ g_signal_connect(throbbing_widget_, "destroy",
+ G_CALLBACK(OnThrobbingWidgetDestroy), this);
+
+ ThrobControllerGtk::ThrobFor(throbbing_widget_);
+ }
+}
+
bool BookmarkBarGtk::IsAlwaysShown() {
return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar);
}
@@ -962,6 +1022,12 @@ void BookmarkBarGtk::OnButtonDragGet(GtkWidget* widget, GdkDragContext* context,
// static
void BookmarkBarGtk::OnFolderClicked(GtkWidget* sender,
BookmarkBarGtk* bar) {
+ // Stop its throbbing, if any.
+ ThrobControllerGtk* throbber =
+ ThrobControllerGtk::GetThrobControllerGtk(sender);
+ if (throbber)
+ throbber->Destroy();
+
bar->PopupForButton(sender);
}
@@ -1164,7 +1230,7 @@ void BookmarkBarGtk::OnParentSizeAllocate(GtkWidget* widget,
// be asynchronous.
if (bar->floating_) {
MessageLoop::current()->PostTask(FROM_HERE,
- bar->event_box_paint_factory_.NewRunnableMethod(
+ bar->method_factory_.NewRunnableMethod(
&BookmarkBarGtk::PaintEventBox));
}
}
@@ -1216,6 +1282,12 @@ gboolean BookmarkBarGtk::OnSeparatorExpose(GtkWidget* widget,
return TRUE;
}
+// static
+void BookmarkBarGtk::OnThrobbingWidgetDestroy(GtkWidget* widget,
+ BookmarkBarGtk* bar) {
+ bar->SetThrobbingWidget(NULL);
+}
+
// MenuBarHelper::Delegate implementation --------------------------------------
void BookmarkBarGtk::PopupForButton(GtkWidget* button) {
const BookmarkNode* node = GetNodeForToolButton(button);
diff --git a/chrome/browser/gtk/bookmark_bar_gtk.h b/chrome/browser/gtk/bookmark_bar_gtk.h
index 98e958e..837905a 100644
--- a/chrome/browser/gtk/bookmark_bar_gtk.h
+++ b/chrome/browser/gtk/bookmark_bar_gtk.h
@@ -133,7 +133,7 @@ class BookmarkBarGtk : public AnimationDelegate,
// |extra_space| is how much extra space to give the toolbar during the
// calculation (for the purposes of determining if ditching the chevron
// would be a good idea).
- // If non-NULL, |showing_folders| is packed with all the folders that are
+ // If non-NULL, |showing_folders| will be packed with all the folders that are
// showing on the bar.
int GetFirstHiddenBookmark(int extra_space,
std::vector<GtkWidget*>* showing_folders);
@@ -155,6 +155,14 @@ class BookmarkBarGtk : public AnimationDelegate,
// DCHECKs and returns false. Otherwise, sets |size| to the correct value.
bool GetTabContentsSize(gfx::Size* size);
+ // Makes the appropriate widget on the bookmark bar stop throbbing
+ // (a folder, the overflow chevron, or nothing).
+ void StartThrobbing(const BookmarkNode* node);
+
+ // Set |throbbing_widget_| to |widget|. Also makes sure that
+ // |throbbing_widget_| doesn't become stale.
+ void SetThrobbingWidget(GtkWidget* widget);
+
// Overridden from BookmarkModelObserver:
// Invoked when the bookmark model has finished loading. Creates a button
@@ -262,6 +270,10 @@ class BookmarkBarGtk : public AnimationDelegate,
GtkAllocation* allocation,
BookmarkBarGtk* bar);
+ // |throbbing_widget_| callback.
+ static void OnThrobbingWidgetDestroy(GtkWidget* widget,
+ BookmarkBarGtk* bar);
+
// ProfileSyncServiceObserver method.
virtual void OnStateChanged();
@@ -360,8 +372,11 @@ class BookmarkBarGtk : public AnimationDelegate,
// of this so we don't force too many paints.
gfx::Size last_tab_contents_size_;
- // We post delayed paints using this factory.
- ScopedRunnableMethodFactory<BookmarkBarGtk> event_box_paint_factory_;
+ // The currently throbbing widget. This is NULL if no widget is throbbing.
+ // We track it because we only want to allow one widget to throb at a time.
+ GtkWidget* throbbing_widget_;
+
+ ScopedRunnableMethodFactory<BookmarkBarGtk> method_factory_;
};
#endif // CHROME_BROWSER_GTK_BOOKMARK_BAR_GTK_H_
diff --git a/chrome/browser/gtk/gtk_chrome_button.cc b/chrome/browser/gtk/gtk_chrome_button.cc
index ef3c75b..0be3f18 100644
--- a/chrome/browser/gtk/gtk_chrome_button.cc
+++ b/chrome/browser/gtk/gtk_chrome_button.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -32,6 +32,8 @@ struct _GtkChromeButtonPrivate {
// If true, we use images provided by the theme instead of GTK's default
// button rendering.
gboolean use_gtk_rendering;
+
+ gdouble hover_state;
};
G_DEFINE_TYPE(GtkChromeButton, gtk_chrome_button, GTK_TYPE_BUTTON)
@@ -46,9 +48,8 @@ static void gtk_chrome_button_class_init(GtkChromeButtonClass* button_class) {
"}"
"widget_class \"*.<GtkChromeButton>\" style \"chrome-button\"");
- GObjectClass* gobject_class = G_OBJECT_CLASS(button_class);
- GtkWidgetClass* widget_class = reinterpret_cast<GtkWidgetClass*>(
- button_class);
+ GtkWidgetClass* widget_class =
+ reinterpret_cast<GtkWidgetClass*>(button_class);
widget_class->expose_event = gtk_chrome_button_expose;
g_nine_box_prelight = new NineBox(
@@ -73,6 +74,7 @@ static void gtk_chrome_button_class_init(GtkChromeButtonClass* button_class) {
IDR_TEXTBUTTON_BOTTOM_P,
IDR_TEXTBUTTON_BOTTOM_RIGHT_P);
+ GObjectClass* gobject_class = G_OBJECT_CLASS(button_class);
g_type_class_add_private(gobject_class, sizeof(GtkChromeButtonPrivate));
}
@@ -80,6 +82,7 @@ static void gtk_chrome_button_init(GtkChromeButton* button) {
GtkChromeButtonPrivate* priv = GTK_CHROME_BUTTON_GET_PRIVATE(button);
priv->paint_state = -1;
priv->use_gtk_rendering = FALSE;
+ priv->hover_state = -1.0;
GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
}
@@ -102,15 +105,17 @@ static gboolean gtk_chrome_button_expose(GtkWidget* widget,
(widget, event);
}
} else {
- NineBox* nine_box = NULL;
- if (paint_state == GTK_STATE_PRELIGHT)
- nine_box = g_nine_box_prelight;
- else if (paint_state == GTK_STATE_ACTIVE)
- nine_box = g_nine_box_active;
-
- // Only draw theme graphics if we have some.
- if (nine_box)
- nine_box->RenderToWidget(widget);
+ double effective_hover_state = paint_state == GTK_STATE_PRELIGHT ?
+ 1.0 : 0.0;
+ if (priv->hover_state >= 0.0)
+ effective_hover_state = priv->hover_state;
+
+ if (paint_state == GTK_STATE_ACTIVE) {
+ g_nine_box_active->RenderToWidget(widget);
+ } else {
+ g_nine_box_prelight->RenderToWidgetWithOpacity(widget,
+ effective_hover_state);
+ }
}
// If we have a child widget, draw it.
@@ -153,4 +158,14 @@ void gtk_chrome_button_set_use_gtk_rendering(GtkChromeButton* button,
priv->use_gtk_rendering = value;
}
+void gtk_chrome_button_set_hover_state(GtkChromeButton* button,
+ gdouble state) {
+ GtkChromeButtonPrivate* priv = GTK_CHROME_BUTTON_GET_PRIVATE(button);
+ if (state >= 0.0 && state <= 1.0)
+ priv->hover_state = state;
+ else
+ priv->hover_state = -1.0;
+ gtk_widget_queue_draw(GTK_WIDGET(button));
+}
+
G_END_DECLS
diff --git a/chrome/browser/gtk/gtk_chrome_button.h b/chrome/browser/gtk/gtk_chrome_button.h
index 921e1e2..56ef67c 100644
--- a/chrome/browser/gtk/gtk_chrome_button.h
+++ b/chrome/browser/gtk/gtk_chrome_button.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -49,6 +49,12 @@ void gtk_chrome_button_unset_paint_state(GtkChromeButton* button);
void gtk_chrome_button_set_use_gtk_rendering(GtkChromeButton* button,
gboolean value);
+// Sets the partial hover state of the button. The acceptable range is 0.0 to
+// 1.0. If |state| is outside of that range, then revert the button to normal
+// hovering.
+void gtk_chrome_button_set_hover_state(GtkChromeButton* button,
+ gdouble state);
+
G_END_DECLS
#endif // CHROME_BROWSER_GTK_GTK_CHROME_BUTTON_H_
diff --git a/chrome/browser/gtk/nine_box.cc b/chrome/browser/gtk/nine_box.cc
index ddad809..572c1b5 100644
--- a/chrome/browser/gtk/nine_box.cc
+++ b/chrome/browser/gtk/nine_box.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -15,18 +15,36 @@
namespace {
// Draw pixbuf |src| into |dst| at position (x, y).
-void DrawPixbuf(cairo_t* cr, GdkPixbuf* src, int x, int y) {
+void DrawPixbuf(cairo_t* cr, GdkPixbuf* src, int x, int y, double alpha) {
gdk_cairo_set_source_pixbuf(cr, src, x, y);
- cairo_paint(cr);
+ cairo_paint_with_alpha(cr, alpha);
}
// Tile pixbuf |src| across |cr| at |x|, |y| for |width| and |height|.
void TileImage(cairo_t* cr, GdkPixbuf* src,
- int x, int y, int width, int height) {
- gdk_cairo_set_source_pixbuf(cr, src, x, y);
- cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
- cairo_rectangle(cr, x, y, width, height);
- cairo_fill(cr);
+ int x, int y, int width, int height, double alpha) {
+ if (alpha == 1.0) {
+ gdk_cairo_set_source_pixbuf(cr, src, x, y);
+ cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
+ cairo_rectangle(cr, x, y, width, height);
+ cairo_fill(cr);
+ } else {
+ // Since there is no easy way to apply a mask to a fill operation, we create
+ // a secondary surface and tile into that, then paint it with |alpha|.
+ cairo_surface_t* surface = cairo_image_surface_create(
+ CAIRO_FORMAT_ARGB32, width, height);
+ cairo_t* tiled = cairo_create(surface);
+ gdk_cairo_set_source_pixbuf(tiled, src, 0, 0);
+ cairo_pattern_set_extend(cairo_get_source(tiled), CAIRO_EXTEND_REPEAT);
+ cairo_rectangle(tiled, 0, 0, width, height);
+ cairo_fill(tiled);
+
+ cairo_set_source_surface(cr, surface, x, y);
+ cairo_paint_with_alpha(cr, alpha);
+
+ cairo_destroy(tiled);
+ cairo_surface_destroy(surface);
+ }
}
} // namespace
@@ -88,6 +106,10 @@ NineBox::~NineBox() {
}
void NineBox::RenderToWidget(GtkWidget* dst) const {
+ RenderToWidgetWithOpacity(dst, 1.0);
+}
+
+void NineBox::RenderToWidgetWithOpacity(GtkWidget* dst, double opacity) const {
int dst_width = dst->allocation.width;
int dst_height = dst->allocation.height;
@@ -117,35 +139,35 @@ void NineBox::RenderToWidget(GtkWidget* dst) const {
// Top row, center image is horizontally tiled.
if (images_[0])
- DrawPixbuf(cr, images_[0], 0, 0);
+ DrawPixbuf(cr, images_[0], 0, 0, opacity);
if (images_[1])
- RenderTopCenterStrip(cr, x1, 0, x2 - x1);
+ TileImage(cr, images_[1], x1, 0, x2 - x1, y1, opacity);
if (images_[2])
- DrawPixbuf(cr, images_[2], x2, 0);
+ DrawPixbuf(cr, images_[2], x2, 0, opacity);
// Center row, all images are vertically tiled, center is horizontally tiled.
if (images_[3])
- TileImage(cr, images_[3], 0, y1, x1, y2 - y1);
+ TileImage(cr, images_[3], 0, y1, x1, y2 - y1, opacity);
if (images_[4])
- TileImage(cr, images_[4], x1, y1, x2 - x1, y2 - y1);
+ TileImage(cr, images_[4], x1, y1, x2 - x1, y2 - y1, opacity);
if (images_[5])
- TileImage(cr, images_[5], x2, y1, dst_width - x2, y2 - y1);
+ TileImage(cr, images_[5], x2, y1, dst_width - x2, y2 - y1, opacity);
// Bottom row, center image is horizontally tiled.
if (images_[6])
- DrawPixbuf(cr, images_[6], 0, y2);
+ DrawPixbuf(cr, images_[6], 0, y2, opacity);
if (images_[7])
- TileImage(cr, images_[7], x1, y2, x2 - x1, dst_height - y2);
+ TileImage(cr, images_[7], x1, y2, x2 - x1, dst_height - y2, opacity);
if (images_[8])
- DrawPixbuf(cr, images_[8], x2, y2);
+ DrawPixbuf(cr, images_[8], x2, y2, opacity);
cairo_destroy(cr);
}
-void NineBox::RenderTopCenterStrip(cairo_t* cr,
- int x, int y, int width) const {
+void NineBox::RenderTopCenterStrip(cairo_t* cr, int x, int y,
+ int width) const {
const int height = gdk_pixbuf_get_height(images_[1]);
- TileImage(cr, images_[1], x, y, width, height);
+ TileImage(cr, images_[1], x, y, width, height, 1.0);
}
void NineBox::ChangeWhiteToTransparent() {
diff --git a/chrome/browser/gtk/nine_box.h b/chrome/browser/gtk/nine_box.h
index bd3dc1e..c00a99b 100644
--- a/chrome/browser/gtk/nine_box.h
+++ b/chrome/browser/gtk/nine_box.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -35,6 +35,9 @@ class NineBox {
// The images will be tiled to fit into the widget.
void RenderToWidget(GtkWidget* dst) const;
+ // As above, but rendered partially transparent.
+ void RenderToWidgetWithOpacity(GtkWidget* dst, double opacity) const;
+
// Render the top row of images to |dst| between |x1| and |x1| + |width|.
// This is split from RenderToWidget so the toolbar can use it.
void RenderTopCenterStrip(cairo_t* cr, int x, int y, int width) const;
diff --git a/chrome/browser/gtk/throb_controller_gtk.cc b/chrome/browser/gtk/throb_controller_gtk.cc
new file mode 100644
index 0000000..422d388
--- /dev/null
+++ b/chrome/browser/gtk/throb_controller_gtk.cc
@@ -0,0 +1,95 @@
+// 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/throb_controller_gtk.h"
+
+#include "base/message_loop.h"
+#include "chrome/browser/gtk/gtk_chrome_button.h"
+
+static const gchar* kThrobControllerGtkKey = "__THROB_CONTROLLER_GTK__";
+
+ThrobControllerGtk::ThrobControllerGtk(GtkWidget* button)
+ : animation_(this),
+ button_(button) {
+ g_object_ref(button_);
+ g_signal_connect(button_, "destroy", G_CALLBACK(OnButtonDestroy), this);
+
+#ifndef NDEBUG
+ if (g_object_get_data(G_OBJECT(button_), kThrobControllerGtkKey))
+ NOTREACHED();
+#endif // !NDEBUG
+
+ g_object_set_data(G_OBJECT(button), kThrobControllerGtkKey, this);
+}
+
+ThrobControllerGtk::~ThrobControllerGtk() {
+}
+
+void ThrobControllerGtk::StartThrobbing(int cycles) {
+ animation_.StartThrobbing(cycles);
+}
+
+// static
+ThrobControllerGtk* ThrobControllerGtk::GetThrobControllerGtk(
+ GtkWidget* button) {
+ return reinterpret_cast<ThrobControllerGtk*>(
+ g_object_get_data(G_OBJECT(button), kThrobControllerGtkKey));
+}
+
+// static
+void ThrobControllerGtk::ThrobFor(GtkWidget* button) {
+ if (!GTK_IS_CHROME_BUTTON(button)) {
+ NOTREACHED();
+ return;
+ }
+
+ (new ThrobControllerGtk(button))->
+ StartThrobbing(std::numeric_limits<int>::max());
+}
+
+void ThrobControllerGtk::Destroy() {
+ gtk_chrome_button_set_hover_state(GTK_CHROME_BUTTON(button_), -1.0);
+ g_signal_handlers_disconnect_by_func(
+ button_,
+ reinterpret_cast<gpointer>(OnButtonDestroy),
+ this);
+ g_object_set_data(G_OBJECT(button_), kThrobControllerGtkKey, NULL);
+ g_object_unref(button_);
+ button_ = NULL;
+
+ // Since this can be called from within AnimationEnded(), which is called
+ // while ThrobAnimation is still doing work, we need to let the stack unwind
+ // before |animation_| gets deleted.
+ MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+}
+
+void ThrobControllerGtk::AnimationProgressed(const Animation* animation) {
+ if (!button_)
+ return;
+
+ gtk_chrome_button_set_hover_state(GTK_CHROME_BUTTON(button_),
+ animation->GetCurrentValue());
+}
+
+void ThrobControllerGtk::AnimationEnded(const Animation* animation) {
+ if (!button_)
+ return;
+
+ if (animation_.cycles_remaining() <= 1)
+ Destroy();
+}
+
+void ThrobControllerGtk::AnimationCanceled(const Animation* animation) {
+ if (!button_)
+ return;
+
+ if (animation_.cycles_remaining() <= 1)
+ Destroy();
+}
+
+// static
+void ThrobControllerGtk::OnButtonDestroy(GtkWidget* widget,
+ ThrobControllerGtk* button) {
+ button->Destroy();
+}
diff --git a/chrome/browser/gtk/throb_controller_gtk.h b/chrome/browser/gtk/throb_controller_gtk.h
new file mode 100644
index 0000000..9b596cc
--- /dev/null
+++ b/chrome/browser/gtk/throb_controller_gtk.h
@@ -0,0 +1,67 @@
+// 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.
+
+#ifndef CHROME_BROWSER_GTK_THROB_CONTROLLER_GTK_H_
+#define CHROME_BROWSER_GTK_THROB_CONTROLLER_GTK_H_
+
+#include "app/throb_animation.h"
+#include "base/scoped_ptr.h"
+
+typedef struct _GtkWidget GtkWidget;
+
+// This class handles the "throbbing" of a GtkChromeButton. The visual effect
+// of throbbing is created by painting partially transparent hover effects. It
+// only works in non-gtk theme mode. This class mainly exists to glue an
+// AnimationDelegate (C++ class) to a GtkChromeButton* (GTK/c object). Usage is
+// as follows:
+//
+// GtkWidget* button = gtk_chrome_button_new();
+// ThrobControllerGtk::ThrobFor(button);
+//
+// The ThrobFor() function will handle creation of the ThrobControllerGtk
+// object, which will delete itself automatically when the throbbing is done,
+// or when the widget is destroyed. It may also be canceled prematurely with
+//
+// ThrobControllerGtk* throbber =
+// ThrobControllerGtk::GetThrobControllerGtk(button);
+// throbber->Destroy();
+class ThrobControllerGtk : public AnimationDelegate {
+ public:
+ virtual ~ThrobControllerGtk();
+
+ GtkWidget* button() { return button_; }
+
+ // Throb for |cycles| cycles. This will override the current remaining
+ // number of cycles.
+ void StartThrobbing(int cycles);
+
+ // Get the ThrobControllerGtk for a given GtkChromeButton*. It is an error
+ // to call this on a widget that is not a GtkChromeButton*.
+ static ThrobControllerGtk* GetThrobControllerGtk(GtkWidget* button);
+
+ // Make |button| throb. It is an error to try to have two ThrobControllerGtk
+ // instances for one GtkChromeButton*.
+ static void ThrobFor(GtkWidget* button);
+
+ // Stop throbbing and delete |this|.
+ void Destroy();
+
+ private:
+ explicit ThrobControllerGtk(GtkWidget* button);
+
+ // Overridden from AnimationDelegate.
+ virtual void AnimationProgressed(const Animation* animation);
+ virtual void AnimationEnded(const Animation* animation);
+ virtual void AnimationCanceled(const Animation* animation);
+
+ static void OnButtonDestroy(GtkWidget* widget,
+ ThrobControllerGtk* button);
+
+ ThrobAnimation animation_;
+ GtkWidget* button_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThrobControllerGtk);
+};
+
+#endif // CHROME_BROWSER_GTK_THROB_CONTROLLER_GTK_H_