// Copyright (c) 2012 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 CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_
#define CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_

#include <gdk/gdk.h>
#include <pango/pango-attributes.h>
#include <vector>

#include "base/basictypes.h"
#include "base/gtest_prod_util.h"
#include "base/strings/string16.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
#include "ui/base/ime/composition_text.h"
#include "ui/base/ime/text_input_type.h"

typedef struct _GtkIMContext GtkIMContext;
typedef struct _GtkWidget GtkWidget;

namespace gfx {
class Rect;
}

namespace content {
class RenderWidgetHostViewGtk;
struct NativeWebKeyboardEvent;

// This class is a convenience wrapper for GtkIMContext.
// It creates and manages two GtkIMContext instances, one is GtkIMMulticontext,
// for plain text input box, another is GtkIMContextSimple, for password input
// box.
//
// This class is in charge of dispatching key events to these two GtkIMContext
// instances and handling signals emitted by them. Key events then will be
// forwarded to renderer along with input method results via corresponding host
// view.
//
// This class is used solely by RenderWidgetHostViewGtk.
class GtkIMContextWrapper {
 public:
  explicit GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view);
  ~GtkIMContextWrapper();

  // Processes a gdk key event received by |host_view|.
  void ProcessKeyEvent(GdkEventKey* event);

  void UpdateInputMethodState(ui::TextInputType type,
                              bool can_compose_inline);
  void UpdateCaretBounds(const gfx::Rect& caret_bounds);
  void OnFocusIn();
  void OnFocusOut();
  bool is_focused() const { return is_focused_; }

  GtkWidget* BuildInputMethodsGtkMenu();

  void CancelComposition();

  void ConfirmComposition();

 private:
  // Check if a text needs commit by forwarding a char event instead of
  // by confirming as a composition text.
  bool NeedCommitByForwardingCharEvent() const;

  // Check if the input method returned any result, eg. preedit and commit text.
  bool HasInputMethodResult() const;

  void ProcessFilteredKeyPressEvent(NativeWebKeyboardEvent* wke);
  void ProcessUnfilteredKeyPressEvent(NativeWebKeyboardEvent* wke);

  // Processes result returned from input method after filtering a key event.
  // |filtered| indicates if the key event was filtered by the input method.
  void ProcessInputMethodResult(const GdkEventKey* event, bool filtered);

  // Real code of "commit" signal handler.
  void HandleCommit(const base::string16& text);

  // Real code of "preedit-start" signal handler.
  void HandlePreeditStart();

  // Real code of "preedit-changed" signal handler.
  void HandlePreeditChanged(const gchar* text,
                            PangoAttrList* attrs,
                            int cursor_position);

  // Real code of "preedit-end" signal handler.
  void HandlePreeditEnd();

  // Real code of "retrieve-surrounding" signal handler.
  gboolean HandleRetrieveSurrounding(GtkIMContext* context);

  // Real code of "realize" signal handler, used for setting im context's client
  // window.
  void HandleHostViewRealize(GtkWidget* widget);

  // Real code of "unrealize" signal handler, used for unsetting im context's
  // client window.
  void HandleHostViewUnrealize();

  // Sends a fake composition key event with specified event type. A composition
  // key event is a key event with special key code 229.
  void SendFakeCompositionKeyEvent(blink::WebInputEvent::Type type);

  // Signal handlers of GtkIMContext object.
  static void HandleCommitThunk(GtkIMContext* context, gchar* text,
                                GtkIMContextWrapper* self);
  static void HandlePreeditStartThunk(GtkIMContext* context,
                                      GtkIMContextWrapper* self);
  static void HandlePreeditChangedThunk(GtkIMContext* context,
                                        GtkIMContextWrapper* self);
  static void HandlePreeditEndThunk(GtkIMContext* context,
                                    GtkIMContextWrapper* self);
  static gboolean HandleRetrieveSurroundingThunk(GtkIMContext* context,
                                                 GtkIMContextWrapper* self);

  // Signal handlers connecting to |host_view_|'s native view widget.
  static void HandleHostViewRealizeThunk(GtkWidget* widget,
                                         GtkIMContextWrapper* self);
  static void HandleHostViewUnrealizeThunk(GtkWidget* widget,
                                           GtkIMContextWrapper* self);

  // The parent object.
  RenderWidgetHostViewGtk* host_view_;

  // The GtkIMContext object.
  // In terms of the DOM event specification Appendix A
  //   <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html>,
  // GTK uses a GtkIMContext object for the following two purposes:
  //  1. Composing Latin characters (A.1.2), and;
  //  2. Composing CJK characters with an IME (A.1.3).
  // Many JavaScript pages assume composed Latin characters are dispatched to
  // their onkeypress() handlers but not dispatched CJK characters composed
  // with an IME. To emulate this behavior, we should monitor the status of
  // this GtkIMContext object and prevent sending Char events when a
  // GtkIMContext object sends a "commit" signal with the CJK characters
  // composed by an IME.
  GtkIMContext* context_;

  // A GtkIMContextSimple object, for supporting dead/compose keys when input
  // method is disabled, eg. in password input box.
  GtkIMContext* context_simple_;

  // Whether or not this widget is focused.
  bool is_focused_;

  // Whether or not the above GtkIMContext is composing a text with an IME.
  // This flag is used in "commit" signal handler of the GtkIMContext object,
  // which determines how to submit the result text to WebKit according to this
  // flag.
  // If this flag is true or there are more than one characters in the result,
  // then the result text will be committed to WebKit as a confirmed
  // composition. Otherwise, it'll be forwarded as a key event.
  //
  // The GtkIMContext object sends a "preedit_start" before it starts composing
  // a text and a "preedit_end" signal after it finishes composing it.
  // "preedit_start" signal is monitored to turn it on.
  // We don't monitor "preedit_end" signal to turn it off, because an input
  // method may fire "preedit_end" signal before "commit" signal.
  // A buggy input method may not fire "preedit_start" and/or "preedit_end"
  // at all, so this flag will also be set to true when "preedit_changed" signal
  // is fired with non-empty preedit text.
  bool is_composing_text_;

  // Whether or not the IME is enabled.
  bool is_enabled_;

  // Whether or not it's currently running inside key event handler.
  // If it's true, then preedit-changed and commit handler will backup the
  // preedit or commit text instead of sending them down to webkit.
  // key event handler will send them later.
  bool is_in_key_event_handler_;

  // The most recent composition text information retrieved from context_;
  ui::CompositionText composition_;

  // Whether or not the composition has been changed since last key event.
  bool is_composition_changed_;

  // Stores a copy of the most recent commit text received by commit signal
  // handler.
  base::string16 commit_text_;

  // If it's true then the next "commit" signal will be suppressed.
  // It's only used to workaround http://crbug.com/50485.
  // TODO(suzhe): Remove it after input methods get fixed.
  bool suppress_next_commit_;

  // Information of the last key event, for working around
  // http://crosbug.com/6582
  int last_key_code_;
  bool last_key_was_up_;
  bool last_key_filtered_no_result_;

  DISALLOW_COPY_AND_ASSIGN(GtkIMContextWrapper);
};

}  // namespace content

#endif  // CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_
