/* Pure Gtk+-3 communication module.

Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2025 Free Software
Foundation, Inc.

This file is part of GNU Emacs.

GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.

GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */

/* This should be the first include, as it may set up #defines affecting
   interpretation of even the system includes. */
#include <config.h>

#include "pgtkterm.h"

static void
im_context_commit_cb (GtkIMContext *imc,
		      gchar *str,
		      gpointer user_data)
{
  struct pgtk_display_info *dpyinfo = user_data;
  struct frame *f = dpyinfo->im.focused_frame;

  if (dpyinfo->im.context == NULL)
    return;
  if (f == NULL)
    return;

  pgtk_enqueue_string (f, str);
}

static gboolean
im_context_retrieve_surrounding_cb (GtkIMContext *imc, gpointer user_data)
{
  gtk_im_context_set_surrounding (imc, "", -1, 0);
  return TRUE;
}

static gboolean
im_context_delete_surrounding_cb (GtkIMContext *imc, int offset, int n_chars,
				  gpointer user_data)
{
  return TRUE;
}

static Lisp_Object
make_color_string (PangoAttrColor *pac)
{
  char buf[256];
  sprintf (buf, "#%02x%02x%02x",
	   pac->color.red >> 8, pac->color.green >> 8, pac->color.blue >> 8);
  return build_string (buf);
}

static void
im_context_preedit_changed_cb (GtkIMContext *imc, gpointer user_data)
{
  struct pgtk_display_info *dpyinfo = user_data;
  struct frame *f = dpyinfo->im.focused_frame;
  char *str;
  PangoAttrList *attrs;
  int pos;

  if (dpyinfo->im.context == NULL)
    return;
  if (f == NULL)
    return;

  gtk_im_context_get_preedit_string (imc, &str, &attrs, &pos);


  /*
   * (
   *   (TEXT (ul . COLOR) (bg . COLOR) (fg . COLOR))
   *   ...
   * )
   */
  Lisp_Object list = Qnil;

  PangoAttrIterator *iter;
  iter = pango_attr_list_get_iterator (attrs);
  do
    {
      int st, ed;
      int has_underline = 0;
      Lisp_Object part = Qnil;

      pango_attr_iterator_range (iter, &st, &ed);

      if (ed > strlen (str))
	ed = strlen (str);
      if (st >= ed)
	continue;

      Lisp_Object text = make_string (str + st, ed - st);
      part = Fcons (text, part);

      PangoAttrInt *ul =
	(PangoAttrInt *) pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE);
      if (ul != NULL)
	{
	  if (ul->value != PANGO_UNDERLINE_NONE)
	    has_underline = 1;
	}

      PangoAttrColor *pac;
      if (has_underline)
	{
	  pac =
	    (PangoAttrColor *) pango_attr_iterator_get (iter,
							PANGO_ATTR_UNDERLINE_COLOR);
	  if (pac != NULL)
	    part = Fcons (Fcons (Qul, make_color_string (pac)), part);
	  else
	    part = Fcons (Fcons (Qul, Qt), part);
	}

      pac =
	(PangoAttrColor *) pango_attr_iterator_get (iter,
						    PANGO_ATTR_FOREGROUND);
      if (pac != NULL)
	part = Fcons (Fcons (Qfg, make_color_string (pac)), part);

      pac =
	(PangoAttrColor *) pango_attr_iterator_get (iter,
						    PANGO_ATTR_BACKGROUND);
      if (pac != NULL)
	part = Fcons (Fcons (Qbg, make_color_string (pac)), part);

      part = Fnreverse (part);
      list = Fcons (part, list);
    }
  while (pango_attr_iterator_next (iter));

  list = Fnreverse (list);
  pgtk_enqueue_preedit (f, list);

  g_free (str);
  pango_attr_list_unref (attrs);
}

static void
im_context_preedit_end_cb (GtkIMContext *imc, gpointer user_data)
{
  struct pgtk_display_info *dpyinfo = user_data;
  struct frame *f = dpyinfo->im.focused_frame;

  if (dpyinfo->im.context == NULL)
    return;
  if (f == NULL)
    return;

  pgtk_enqueue_preedit (f, Qnil);
}

static void
im_context_preedit_start_cb (GtkIMContext *imc, gpointer user_data)
{
}

void
pgtk_im_focus_in (struct frame *f)
{
  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
  if (dpyinfo->im.context != NULL)
    {
      gtk_im_context_reset (dpyinfo->im.context);
      gtk_im_context_set_client_window (dpyinfo->im.context,
					gtk_widget_get_window
					(FRAME_GTK_WIDGET (f)));
      gtk_im_context_focus_in (dpyinfo->im.context);
    }
  dpyinfo->im.focused_frame = f;
}

void
pgtk_im_focus_out (struct frame *f)
{
  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
  if (dpyinfo->im.focused_frame == f)
    {
      if (dpyinfo->im.context != NULL)
	{
	  gtk_im_context_reset (dpyinfo->im.context);
	  gtk_im_context_focus_out (dpyinfo->im.context);
	  gtk_im_context_set_client_window (dpyinfo->im.context, NULL);
	}
      dpyinfo->im.focused_frame = NULL;
    }
}

bool
pgtk_im_filter_keypress (struct frame *f, GdkEventKey * ev)
{
  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
  if (dpyinfo->im.context != NULL)
    {
      if (gtk_im_context_filter_keypress (dpyinfo->im.context, ev))
	return true;
    }
  return false;
}

void
pgtk_im_set_cursor_location (struct frame *f, int x, int y, int width,
			     int height)
{
  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
  if (dpyinfo->im.context != NULL && dpyinfo->im.focused_frame == f)
    {
      GdkRectangle area = { x, y, width, height };
      gtk_im_context_set_cursor_location (dpyinfo->im.context, &area);
    }
}

static void
pgtk_im_use_context (struct pgtk_display_info *dpyinfo, bool use_p)
{
  if (!use_p)
    {
      if (dpyinfo->im.context != NULL)
	{
	  gtk_im_context_reset (dpyinfo->im.context);
	  gtk_im_context_focus_out (dpyinfo->im.context);
	  gtk_im_context_set_client_window (dpyinfo->im.context, NULL);

	  g_object_unref (dpyinfo->im.context);
	  dpyinfo->im.context = NULL;
	}
    }
  else
    {
      if (dpyinfo->im.context == NULL)
	{
	  dpyinfo->im.context = gtk_im_multicontext_new ();
	  g_signal_connect (dpyinfo->im.context, "commit",
			    G_CALLBACK (im_context_commit_cb), dpyinfo);
	  g_signal_connect (dpyinfo->im.context, "retrieve-surrounding",
			    G_CALLBACK (im_context_retrieve_surrounding_cb),
			    dpyinfo);
	  g_signal_connect (dpyinfo->im.context, "delete-surrounding",
			    G_CALLBACK (im_context_delete_surrounding_cb),
			    dpyinfo);
	  g_signal_connect (dpyinfo->im.context, "preedit-changed",
			    G_CALLBACK (im_context_preedit_changed_cb),
			    dpyinfo);
	  g_signal_connect (dpyinfo->im.context, "preedit-end",
			    G_CALLBACK (im_context_preedit_end_cb), dpyinfo);
	  g_signal_connect (dpyinfo->im.context, "preedit-start",
			    G_CALLBACK (im_context_preedit_start_cb),
			    dpyinfo);
	  gtk_im_context_set_use_preedit (dpyinfo->im.context, TRUE);

	  if (dpyinfo->im.focused_frame)
	    pgtk_im_focus_in (dpyinfo->im.focused_frame);
	}
    }
}

void
pgtk_im_init (struct pgtk_display_info *dpyinfo)
{
  dpyinfo->im.context = NULL;

  pgtk_im_use_context (dpyinfo, !NILP (Vpgtk_use_im_context_on_new_connection));
}

void
pgtk_im_finish (struct pgtk_display_info *dpyinfo)
{
  if (dpyinfo->im.context != NULL)
    g_object_unref (dpyinfo->im.context);
  dpyinfo->im.context = NULL;
}

DEFUN ("pgtk-use-im-context", Fpgtk_use_im_context, Spgtk_use_im_context, 1, 2, 0,
       doc: /* Set whether to use GtkIMContext. */)
  (Lisp_Object use_p, Lisp_Object terminal)
{
  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);

  pgtk_im_use_context (dpyinfo, !NILP (use_p));

  return Qnil;
}

void
syms_of_pgtkim (void)
{
  defsubr (&Spgtk_use_im_context);

  DEFSYM (Qpgtk_refresh_preedit, "pgtk-refresh-preedit");
  DEFSYM (Qul, "ul");
  DEFSYM (Qfg, "fg");
  DEFSYM (Qbg, "bg");

  DEFVAR_LISP ("pgtk-use-im-context-on-new-connection", Vpgtk_use_im_context_on_new_connection,
	       doc: /* Whether to use GtkIMContext on a new connection.
If you want to change it after connection, use the `pgtk-use-im-context'
function.  */ );
  Vpgtk_use_im_context_on_new_connection = Qt;
}
