// Copyright (c) 2011 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.
// This is the GTK implementation of InfoBubbles. InfoBubbles are like
// dialogs, but they point to a given element on the screen. You should call
// InfoBubbleGtk::Show, which will create and display a bubble. The object is
// self deleting, when the bubble is closed, you will be notified via
// InfoBubbleGtkDelegate::InfoBubbleClosing(). Then the widgets and the
// underlying object will be destroyed. You can also close and destroy the
// bubble by calling Close().
#ifndef CHROME_BROWSER_UI_GTK_INFO_BUBBLE_GTK_H_
#define CHROME_BROWSER_UI_GTK_INFO_BUBBLE_GTK_H_
#pragma once
#include <gtk/gtk.h>
#include <vector>
#include "base/basictypes.h"
#include "content/common/notification_observer.h"
#include "content/common/notification_registrar.h"
#include "ui/base/gtk/gtk_signal.h"
#include "ui/base/gtk/gtk_signal_registrar.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
class GtkThemeService;
class InfoBubbleGtk;
namespace gfx {
class Rect;
}
class InfoBubbleGtkDelegate {
public:
// Called when the InfoBubble is closing and is about to be deleted.
// |closed_by_escape| is true if the close is the result of pressing escape.
virtual void InfoBubbleClosing(InfoBubbleGtk* info_bubble,
bool closed_by_escape) = 0;
// NOTE: The Views interface has CloseOnEscape, except I can't find a place
// where it ever returns false, so we always allow you to close via escape.
protected:
virtual ~InfoBubbleGtkDelegate() {}
};
class InfoBubbleGtk : public NotificationObserver {
public:
// Where should the arrow be placed relative to the bubble?
enum ArrowLocationGtk {
// TODO(derat): Support placing arrows on the bottoms of the bubbles.
ARROW_LOCATION_TOP_LEFT,
ARROW_LOCATION_TOP_RIGHT,
};
// Show an InfoBubble, pointing at the area |rect| (in coordinates relative to
// |anchor_widget|'s origin). An info bubble will try to fit on the screen,
// so it can point to any edge of |rect|. If |rect| is NULL, the widget's
// entire area will be used. The bubble will host the |content|
// widget. Its arrow will be drawn at |arrow_location| if possible. The
// |delegate| will be notified when the bubble is closed. The bubble will
// perform an X grab of the pointer and keyboard, and will close itself if a
// click is received outside of the bubble.
static InfoBubbleGtk* Show(GtkWidget* anchor_widget,
const gfx::Rect* rect,
GtkWidget* content,
ArrowLocationGtk arrow_location,
bool match_system_theme,
bool grab_input,
GtkThemeService* provider,
InfoBubbleGtkDelegate* delegate);
// Close the bubble if it's open. This will delete the widgets and object,
// so you shouldn't hold a InfoBubbleGtk pointer after calling Close().
void Close();
// NotificationObserver implementation.
virtual void Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details);
// If the content contains widgets that can steal our pointer and keyboard
// grabs (e.g. GtkComboBox), this method should be called after a widget
// releases the grabs so we can reacquire them. Note that this causes a race
// condition; another client could grab them before we do (ideally, GDK would
// transfer the grabs back to us when the widget releases them). The window
// is small, though, and the worst-case scenario for this seems to just be
// that the content's widgets will appear inactive even after the user clicks
// in them.
void HandlePointerAndKeyboardUngrabbedByContent();
private:
enum FrameType {
FRAME_MASK,
FRAME_STROKE,
};
explicit InfoBubbleGtk(GtkThemeService* provider, bool match_system_theme);
virtual ~InfoBubbleGtk();
// Creates the InfoBubble.
void Init(GtkWidget* anchor_widget,
const gfx::Rect* rect,
GtkWidget* content,
ArrowLocationGtk arrow_location,
bool grab_input);
// Make the points for our polygon frame, either for fill (the mask), or for
// when we stroke the border.
static std::vector<GdkPoint> MakeFramePolygonPoints(
ArrowLocationGtk arrow_location,
int width,
int height,
FrameType type);
// Get the location where the arrow should be placed (which is a function of
// the preferred location and of the direction that the bubble should be
// facing to fit onscreen). |arrow_x| is the X component in screen
// coordinates of the point at which the bubble's arrow should be aimed, and
// |width| is the bubble's width.
static ArrowLocationGtk GetArrowLocation(
ArrowLocationGtk preferred_location, int arrow_x, int width);
// Updates |arrow_location_| based on the toplevel window's current position
// and the bubble's size. If the |force_move_and_reshape| is true or the
// location changes, moves and reshapes the window and returns true.
bool UpdateArrowLocation(bool force_move_and_reshape);
// Reshapes the window and updates |mask_region_|.
void UpdateWindowShape();
// Calculate the current screen position for the bubble's window (per
// |toplevel_window_|'s position as of its most-recent ConfigureNotify event
// and |rect_|) and move it there.
void MoveWindow();
// Restack the bubble's window directly above |toplevel_window_|.
void StackWindow();
// Sets the delegate.
void set_delegate(InfoBubbleGtkDelegate* delegate) { delegate_ = delegate; }
// Grab (in the X sense) the pointer and keyboard. This is needed to make
// sure that we have the input focus.
void GrabPointerAndKeyboard();
CHROMEG_CALLBACK_3(InfoBubbleGtk, gboolean, OnGtkAccelerator, GtkAccelGroup*,
GObject*, guint, GdkModifierType);
CHROMEGTK_CALLBACK_1(InfoBubbleGtk, gboolean, OnExpose, GdkEventExpose*);
CHROMEGTK_CALLBACK_1(InfoBubbleGtk, void, OnSizeAllocate, GtkAllocation*);
CHROMEGTK_CALLBACK_1(InfoBubbleGtk, gboolean, OnButtonPress, GdkEventButton*);
CHROMEGTK_CALLBACK_0(InfoBubbleGtk, gboolean, OnDestroy);
CHROMEGTK_CALLBACK_0(InfoBubbleGtk, void, OnHide);
CHROMEGTK_CALLBACK_1(InfoBubbleGtk, gboolean, OnToplevelConfigure,
GdkEventConfigure*);
CHROMEGTK_CALLBACK_1(InfoBubbleGtk, gboolean, OnToplevelUnmap, GdkEvent*);
CHROMEGTK_CALLBACK_1(InfoBubbleGtk, void, OnAnchorAllocate, GtkAllocation*);
// The caller supplied delegate, can be NULL.
InfoBubbleGtkDelegate* delegate_;
// Our GtkWindow popup window, we don't technically "own" the widget, since
// it deletes us when it is destroyed.
GtkWidget* window_;
// Provides colors and stuff.
GtkThemeService* theme_service_;
// The accel group attached to |window_|, to handle closing with escape.
GtkAccelGroup* accel_group_;
// The window for which we're being shown (and to which |rect_| is relative).
// Note that it's possible for |toplevel_window_| to be NULL if the
// window is destroyed before this object is destroyed, so it's important
// to check for that case.
GtkWindow* toplevel_window_;
// The widget that we use to relatively position the popup window.
GtkWidget* anchor_widget_;
// Provides an offset from |anchor_widget_|'s origin for MoveWindow() to
// use.
gfx::Rect rect_;
// The current shape of |window_| (used to test whether clicks fall in it or
// not).
GdkRegion* mask_region_;
// Where would we prefer for the arrow be drawn relative to the bubble, and
// where is it currently drawn?
ArrowLocationGtk preferred_arrow_location_;
ArrowLocationGtk current_arrow_location_;
// Whether the background should match the system theme, when the system theme
// is being used. For example, the bookmark bubble does, but extension popups
// do not.
bool match_system_theme_;
// If true, the popup owns all X input for the duration of its existence.
// This will usually be true, the exception being when inspecting extension
// popups with dev tools.
bool grab_input_;
bool closed_by_escape_;
NotificationRegistrar registrar_;
ui::GtkSignalRegistrar signals_;
DISALLOW_COPY_AND_ASSIGN(InfoBubbleGtk);
};
#endif // CHROME_BROWSER_UI_GTK_INFO_BUBBLE_GTK_H_