// 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.

// 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_GTK_INFO_BUBBLE_GTK_H_
#define CHROME_BROWSER_GTK_INFO_BUBBLE_GTK_H_

#include <gtk/gtk.h>

#include "base/basictypes.h"
#include "base/gfx/point.h"
#include "base/gfx/rect.h"
#include "chrome/common/notification_registrar.h"

class GtkThemeProvider;
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.
};

class InfoBubbleGtk : public NotificationObserver {
 public:
  // Show an InfoBubble, pointing at the area |rect| (in coordinates relative to
  // |toplevel_window|'s origin).  An info bubble will try to fit on the screen,
  // so it can point to any edge of |rect|.  The bubble will host the |content|
  // widget.  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.
  // TODO(derat): This implementation doesn't try to position itself onscreen.
  static InfoBubbleGtk* Show(GtkWindow* toplevel_window,
                             const gfx::Rect& rect,
                             GtkWidget* content,
                             GtkThemeProvider* 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() { CloseInternal(false); }

  // 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:
  explicit InfoBubbleGtk(GtkThemeProvider* provider);
  virtual ~InfoBubbleGtk();

  // Creates the InfoBubble.
  void Init(GtkWindow* toplevel_window,
            const gfx::Rect& rect,
            GtkWidget* content);

  // 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; }

  // Closes the window and notifies the delegate. |closed_by_escape| is true if
  // the close is the result of pressing escape.
  void CloseInternal(bool closed_by_escape);

  // Grab (in the X sense) the pointer and keyboard.  This is needed to make
  // sure that we have the input focus.
  void GrabPointerAndKeyboard();

  static gboolean HandleEscapeThunk(GtkAccelGroup* group,
                                    GObject* acceleratable,
                                    guint keyval,
                                    GdkModifierType modifier,
                                    gpointer user_data) {
    return reinterpret_cast<InfoBubbleGtk*>(user_data)->HandleEscape();
  }
  gboolean HandleEscape();

  static void HandleSizeAllocateThunk(GtkWidget* widget,
                                      GtkAllocation* allocation,
                                      gpointer user_data) {
    reinterpret_cast<InfoBubbleGtk*>(user_data)->HandleSizeAllocate();
  }
  void HandleSizeAllocate();

  static gboolean HandleButtonPressThunk(GtkWidget* widget,
                                         GdkEventButton* event,
                                         gpointer user_data) {
    return reinterpret_cast<InfoBubbleGtk*>(user_data)->
        HandleButtonPress(event);
  }
  gboolean HandleButtonPress(GdkEventButton* event);

  static gboolean HandleButtonReleaseThunk(GtkWidget* widget,
                                           GdkEventButton* event,
                                           gpointer user_data) {
    return reinterpret_cast<InfoBubbleGtk*>(user_data)->
        HandleButtonRelease(event);
  }
  gboolean HandleButtonRelease(GdkEventButton* event);

  static gboolean HandleDestroyThunk(GtkWidget* widget,
                                     gpointer user_data) {
    return reinterpret_cast<InfoBubbleGtk*>(user_data)->HandleDestroy();
  }
  gboolean HandleDestroy();

  static gboolean HandleToplevelConfigureThunk(GtkWidget* widget,
                                               GdkEventConfigure* event,
                                               gpointer user_data) {
    return reinterpret_cast<InfoBubbleGtk*>(user_data)->
        HandleToplevelConfigure(event);
  }
  gboolean HandleToplevelConfigure(GdkEventConfigure* event);

  static gboolean HandleToplevelUnmapThunk(GtkWidget* widget,
                                           GdkEvent* event,
                                           gpointer user_data) {
    return reinterpret_cast<InfoBubbleGtk*>(user_data)->HandleToplevelUnmap();
  }
  gboolean HandleToplevelUnmap();

  // 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.
  GtkThemeProvider* theme_provider_;

  // 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).
  GtkWindow* toplevel_window_;

  // Provides an offset from |toplevel_window_|'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_;

  NotificationRegistrar registrar_;

  DISALLOW_COPY_AND_ASSIGN(InfoBubbleGtk);
};

#endif  // CHROME_BROWSER_GTK_INFO_BUBBLE_GTK_H_