| Index: chrome/browser/gtk/info_bubble_gtk.cc
|
| diff --git a/chrome/browser/gtk/info_bubble_gtk.cc b/chrome/browser/gtk/info_bubble_gtk.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3758cd278c729b698d9d7376ea04e9ffbb808f1b
|
| --- /dev/null
|
| +++ b/chrome/browser/gtk/info_bubble_gtk.cc
|
| @@ -0,0 +1,208 @@
|
| +// Copyright (c) 2009 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/info_bubble_gtk.h"
|
| +
|
| +#include <gtk/gtk.h>
|
| +
|
| +#include "base/basictypes.h"
|
| +#include "base/gfx/gtk_util.h"
|
| +#include "base/gfx/rect.h"
|
| +#include "base/logging.h"
|
| +#include "chrome/common/gfx/path.h"
|
| +
|
| +namespace {
|
| +
|
| +// The height of the arrow, and the width will be about twice the height.
|
| +const int kArrowSize = 5;
|
| +// Number of pixels to the start of the arrow from the edge of the window.
|
| +const int kArrowX = 13;
|
| +// Number of pixels between the tip of the arrow and the region we're
|
| +// pointing to.
|
| +const int kArrowToContentPadding = -6;
|
| +// We draw flat diagonal corners, each corner is an NxN square.
|
| +const int kCornerSize = 3;
|
| +// Margins around the content.
|
| +const int kTopMargin = kArrowSize + kCornerSize + 6;
|
| +const int kBottomMargin = kCornerSize + 6;
|
| +const int kLeftMargin = kCornerSize + 6;
|
| +const int kRightMargin = kCornerSize + 6;
|
| +
|
| +const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff);
|
| +const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63);
|
| +
|
| +// A small convenience since GdkPoint is a POD without a constructor.
|
| +GdkPoint MakeGdkPoint(gint x, gint y) {
|
| + GdkPoint point = {x, y};
|
| + return point;
|
| +}
|
| +
|
| +enum FrameType {
|
| + FRAME_MASK,
|
| + FRAME_STROKE,
|
| +};
|
| +
|
| +// Make the points for our polygon frame, either for fill (the mask), or for
|
| +// when we stroke the border. NOTE: This seems a bit overcomplicated, but it
|
| +// requires a bunch of careful fudging to get the pixels rasterized exactly
|
| +// where we want them, the arrow to have a 1 pixel point, etc.
|
| +// TODO(deanm): Windows draws with Skia and uses some PNG images for the
|
| +// corners. This is a lot more work, but they get anti-aliasing.
|
| +std::vector<GdkPoint> MakeFramePolygonPoints(int width,
|
| + int height,
|
| + FrameType type) {
|
| + std::vector<GdkPoint> points;
|
| +
|
| + // If we have a stroke, we have to offset some of our points by 1 pixel.
|
| + int off = (type == FRAME_MASK) ? 0 : 1;
|
| +
|
| + // Top left corner.
|
| + points.push_back(MakeGdkPoint(0, kArrowSize + kCornerSize - 1));
|
| + points.push_back(MakeGdkPoint(kCornerSize - 1, kArrowSize));
|
| +
|
| + // The arrow.
|
| + points.push_back(MakeGdkPoint(kArrowX - kArrowSize, kArrowSize));
|
| + points.push_back(MakeGdkPoint(kArrowX, 0));
|
| + points.push_back(MakeGdkPoint(kArrowX + 1 - off, 0));
|
| + points.push_back(MakeGdkPoint(kArrowX + kArrowSize + 1 - off, kArrowSize));
|
| +
|
| + // Top right corner.
|
| + points.push_back(MakeGdkPoint(width - kCornerSize + 1 - off, kArrowSize));
|
| + points.push_back(MakeGdkPoint(width - off, kArrowSize + kCornerSize - 1));
|
| +
|
| + // Bottom right corner.
|
| + points.push_back(MakeGdkPoint(width - off, height - kCornerSize));
|
| + points.push_back(MakeGdkPoint(width - kCornerSize, height - off));
|
| +
|
| + // Bottom left corner.
|
| + points.push_back(MakeGdkPoint(kCornerSize - off, height - off));
|
| + points.push_back(MakeGdkPoint(0, height - kCornerSize));
|
| +
|
| + return points;
|
| +}
|
| +
|
| +// When our size is initially allocated or changed, we need to recompute
|
| +// and apply our shape mask region.
|
| +void HandleSizeAllocate(GtkWidget* widget,
|
| + GtkAllocation* allocation,
|
| + gpointer unused) {
|
| + DCHECK(allocation->x == 0 && allocation->y == 0);
|
| + std::vector<GdkPoint> points = MakeFramePolygonPoints(
|
| + allocation->width, allocation->height, FRAME_MASK);
|
| + GdkRegion* mask_region = gdk_region_polygon(&points[0],
|
| + points.size(),
|
| + GDK_EVEN_ODD_RULE);
|
| + gdk_window_shape_combine_region(widget->window, mask_region, 0, 0);
|
| + gdk_region_destroy(mask_region);
|
| +}
|
| +
|
| +gboolean HandleExpose(GtkWidget* widget,
|
| + GdkEventExpose* event,
|
| + gpointer unused) {
|
| + GdkDrawable* drawable = GDK_DRAWABLE(event->window);
|
| + GdkGC* gc = gdk_gc_new(drawable);
|
| + gdk_gc_set_rgb_fg_color(gc, &kFrameColor);
|
| +
|
| + // Stroke the frame border.
|
| + std::vector<GdkPoint> points = MakeFramePolygonPoints(
|
| + widget->allocation.width, widget->allocation.height, FRAME_STROKE);
|
| + gdk_draw_polygon(drawable, gc, FALSE, &points[0], points.size());
|
| +
|
| + g_object_unref(gc);
|
| + return FALSE; // Propagate so our children paint, etc.
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// static
|
| +InfoBubbleGtk* InfoBubbleGtk::Show(const gfx::Rect& rect, GtkWidget* content) {
|
| + InfoBubbleGtk* bubble = new InfoBubbleGtk();
|
| + bubble->Init(rect, content);
|
| + return bubble;
|
| +}
|
| +
|
| +InfoBubbleGtk::InfoBubbleGtk()
|
| + : window_(NULL),
|
| + screen_x_(0),
|
| + screen_y_(0),
|
| + closed_(false) {
|
| +}
|
| +
|
| +InfoBubbleGtk::~InfoBubbleGtk() {
|
| +}
|
| +
|
| +void InfoBubbleGtk::Init(const gfx::Rect& rect, GtkWidget* content) {
|
| + DCHECK(!window_);
|
| + screen_x_ = rect.x() + (rect.width() / 2) - kArrowX;
|
| + screen_y_ = rect.y() + rect.height() + kArrowToContentPadding;
|
| +
|
| + window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
| + gtk_window_set_decorated(GTK_WINDOW(window_), TRUE);
|
| + gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
|
| + gtk_widget_set_app_paintable(window_, TRUE);
|
| + // Have GTK double buffer around the expose signal.
|
| + gtk_widget_set_double_buffered(window_, TRUE);
|
| + // Set the background color, so we don't need to paint it manually.
|
| + gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &kBackgroundColor);
|
| + // Make sure that our window can be focused.
|
| + GTK_WIDGET_SET_FLAGS(window_, GTK_CAN_FOCUS);
|
| +
|
| + 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_container_add(GTK_CONTAINER(alignment), content);
|
| + gtk_container_add(GTK_CONTAINER(window_), alignment);
|
| +
|
| + // GtkWidget only exposes the bitmap mask interface. Use GDK to more
|
| + // efficently mask a GdkRegion. Make sure the window is realized during
|
| + // HandleSizeAllocate, so the mask can be applied to the GdkWindow.
|
| + gtk_widget_realize(window_);
|
| + gtk_window_move(GTK_WINDOW(window_), screen_x_, screen_y_);
|
| +
|
| + gtk_widget_add_events(window_, GDK_BUTTON_PRESS_MASK |
|
| + GDK_BUTTON_RELEASE_MASK);
|
| +
|
| + g_signal_connect(window_, "size-allocate",
|
| + G_CALLBACK(HandleSizeAllocate), NULL);
|
| + g_signal_connect(window_, "expose-event",
|
| + G_CALLBACK(HandleExpose), NULL);
|
| + g_signal_connect(window_, "configure-event",
|
| + G_CALLBACK(&HandleConfigureThunk), this);
|
| + g_signal_connect(window_, "button-press-event",
|
| + G_CALLBACK(&HandleButtonPressThunk), this);
|
| +
|
| + gtk_widget_show_all(window_);
|
| + gtk_window_present(GTK_WINDOW(window_));
|
| + gtk_grab_add(window_);
|
| +}
|
| +
|
| +void InfoBubbleGtk::Close() {
|
| + DCHECK(!closed_);
|
| + DCHECK(window_);
|
| + gtk_widget_destroy(window_);
|
| + window_ = NULL;
|
| + closed_ = true;
|
| +}
|
| +
|
| +gboolean InfoBubbleGtk::HandleConfigure(GdkEventConfigure* event) {
|
| + // If the window is moved someplace besides where we want it, move it back.
|
| + // TODO(deanm): In the end, I will probably remove this code and just let
|
| + // the user move around the bubble like a normal dialog. I want to try
|
| + // this for now and see if it causes problems when any window managers.
|
| + if (event->x != screen_x_ || event->y != screen_y_)
|
| + gtk_window_move(GTK_WINDOW(window_), screen_x_, screen_y_);
|
| + return FALSE;
|
| +}
|
| +
|
| +gboolean InfoBubbleGtk::HandleButtonPress(GdkEventButton* event) {
|
| + // If we got a click in our own window, that's ok.
|
| + if (event->window == window_->window)
|
| + return FALSE; // Propagate.
|
| +
|
| + // Otherwise we had a click outside of our window, close ourself.
|
| + Close();
|
| + return TRUE;
|
| +}
|
|
|