/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * PikaTextTool
 * Copyright (C) 2002-2010  Sven Neumann <sven@gimp.org>
 *                          Daniel Eddeland <danedde@svn.gnome.org>
 *                          Michael Natterer <mitch@gimp.org>
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <gegl.h>
#include <gtk/gtk.h>

#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "libpikawidgets/pikawidgets.h"

#include "tools-types.h"

#include "core/pika.h"
#include "core/pikaasyncset.h"
#include "core/pikacontext.h"
#include "core/pikadatafactory.h"
#include "core/pikaerror.h"
#include "core/pikaimage.h"
#include "core/pika-palettes.h"
#include "core/pikaimage-pick-item.h"
#include "core/pikaimage-undo.h"
#include "core/pikaimage-undo-push.h"
#include "core/pikalayer-floating-selection.h"
#include "core/pikatoolinfo.h"
#include "core/pikaundostack.h"

#include "menus/menus.h"

#include "text/pikatext.h"
#include "text/pikatext-vectors.h"
#include "text/pikatextlayer.h"
#include "text/pikatextlayout.h"
#include "text/pikatextundo.h"

#include "vectors/pikastroke.h"
#include "vectors/pikavectors.h"
#include "vectors/pikavectors-warp.h"

#include "widgets/pikadialogfactory.h"
#include "widgets/pikadockcontainer.h"
#include "widgets/pikahelp-ids.h"
#include "widgets/pikamenufactory.h"
#include "widgets/pikatextbuffer.h"
#include "widgets/pikauimanager.h"
#include "widgets/pikaviewabledialog.h"

#include "display/pikacanvasgroup.h"
#include "display/pikadisplay.h"
#include "display/pikadisplayshell.h"
#include "display/pikatoolrectangle.h"

#include "pikatextoptions.h"
#include "pikatexttool.h"
#include "pikatexttool-editor.h"
#include "pikatoolcontrol.h"

#include "pika-intl.h"


#define TEXT_UNDO_TIMEOUT 3


/*  local function prototypes  */

static void      pika_text_tool_constructed     (GObject           *object);
static void      pika_text_tool_finalize        (GObject           *object);

static void      pika_text_tool_control         (PikaTool          *tool,
                                                 PikaToolAction     action,
                                                 PikaDisplay       *display);
static void      pika_text_tool_button_press    (PikaTool          *tool,
                                                 const PikaCoords  *coords,
                                                 guint32            time,
                                                 GdkModifierType    state,
                                                 PikaButtonPressType  press_type,
                                                 PikaDisplay       *display);
static void      pika_text_tool_button_release  (PikaTool          *tool,
                                                 const PikaCoords  *coords,
                                                 guint32            time,
                                                 GdkModifierType    state,
                                                 PikaButtonReleaseType release_type,
                                                 PikaDisplay       *display);
static void      pika_text_tool_motion          (PikaTool          *tool,
                                                 const PikaCoords  *coords,
                                                 guint32            time,
                                                 GdkModifierType    state,
                                                 PikaDisplay       *display);
static gboolean  pika_text_tool_key_press       (PikaTool          *tool,
                                                 GdkEventKey       *kevent,
                                                 PikaDisplay       *display);
static gboolean  pika_text_tool_key_release     (PikaTool          *tool,
                                                 GdkEventKey       *kevent,
                                                 PikaDisplay       *display);
static void      pika_text_tool_oper_update     (PikaTool          *tool,
                                                 const PikaCoords  *coords,
                                                 GdkModifierType    state,
                                                 gboolean           proximity,
                                                 PikaDisplay       *display);
static void      pika_text_tool_cursor_update   (PikaTool          *tool,
                                                 const PikaCoords  *coords,
                                                 GdkModifierType    state,
                                                 PikaDisplay       *display);
static PikaUIManager * pika_text_tool_get_popup (PikaTool          *tool,
                                                 const PikaCoords  *coords,
                                                 GdkModifierType    state,
                                                 PikaDisplay       *display,
                                                 const gchar      **ui_path);

static void      pika_text_tool_draw            (PikaDrawTool      *draw_tool);
static void      pika_text_tool_draw_selection  (PikaDrawTool      *draw_tool);

static gboolean  pika_text_tool_start           (PikaTextTool      *text_tool,
                                                 PikaDisplay       *display,
                                                 PikaLayer         *layer,
                                                 GError           **error);
static void      pika_text_tool_halt            (PikaTextTool      *text_tool);

static void      pika_text_tool_frame_item      (PikaTextTool      *text_tool);

static void      pika_text_tool_rectangle_response
                                                (PikaToolRectangle *rectangle,
                                                 gint               response_id,
                                                 PikaTextTool      *text_tool);
static void      pika_text_tool_rectangle_change_complete
                                                (PikaToolRectangle *rectangle,
                                                 PikaTextTool      *text_tool);

static void      pika_text_tool_connect         (PikaTextTool      *text_tool,
                                                 PikaTextLayer     *layer,
                                                 PikaText          *text);

static void      pika_text_tool_layer_notify    (PikaTextLayer     *layer,
                                                 const GParamSpec  *pspec,
                                                 PikaTextTool      *text_tool);
static void      pika_text_tool_proxy_notify    (PikaText          *text,
                                                 const GParamSpec  *pspec,
                                                 PikaTextTool      *text_tool);

static void      pika_text_tool_text_notify     (PikaText          *text,
                                                 const GParamSpec  *pspec,
                                                 PikaTextTool      *text_tool);
static void      pika_text_tool_text_changed    (PikaText          *text,
                                                 PikaTextTool      *text_tool);

static void
    pika_text_tool_fonts_async_set_empty_notify (PikaAsyncSet      *async_set,
                                                 GParamSpec        *pspec,
                                                 PikaTextTool      *text_tool);

static void      pika_text_tool_apply_list      (PikaTextTool      *text_tool,
                                                 GList             *pspecs);

static void      pika_text_tool_create_layer    (PikaTextTool      *text_tool,
                                                 PikaText          *text);

static void      pika_text_tool_layer_changed   (PikaImage         *image,
                                                 PikaTextTool      *text_tool);
static void      pika_text_tool_set_image       (PikaTextTool      *text_tool,
                                                 PikaImage         *image);
static gboolean  pika_text_tool_set_drawable    (PikaTextTool      *text_tool,
                                                 PikaDrawable      *drawable,
                                                 gboolean           confirm);

static void      pika_text_tool_block_drawing   (PikaTextTool      *text_tool);
static void      pika_text_tool_unblock_drawing (PikaTextTool      *text_tool);

static void    pika_text_tool_buffer_begin_edit (PikaTextBuffer    *buffer,
                                                 PikaTextTool      *text_tool);
static void    pika_text_tool_buffer_end_edit   (PikaTextBuffer    *buffer,
                                                 PikaTextTool      *text_tool);

static void    pika_text_tool_buffer_color_applied
                                                (PikaTextBuffer    *buffer,
                                                 const PikaRGB     *color,
                                                 PikaTextTool      *text_tool);


G_DEFINE_TYPE (PikaTextTool, pika_text_tool, PIKA_TYPE_DRAW_TOOL)

#define parent_class pika_text_tool_parent_class


void
pika_text_tool_register (PikaToolRegisterCallback  callback,
                         gpointer                  data)
{
  (* callback) (PIKA_TYPE_TEXT_TOOL,
                PIKA_TYPE_TEXT_OPTIONS,
                pika_text_options_gui,
                PIKA_CONTEXT_PROP_MASK_FOREGROUND |
                PIKA_CONTEXT_PROP_MASK_FONT       |
                PIKA_CONTEXT_PROP_MASK_PALETTE /* for the color popup's palette tab */,
                "pika-text-tool",
                _("Text"),
                _("Text Tool: Create or edit text layers"),
                N_("Te_xt"), "T",
                NULL, PIKA_HELP_TOOL_TEXT,
                PIKA_ICON_TOOL_TEXT,
                data);
}

static void
pika_text_tool_class_init (PikaTextToolClass *klass)
{
  GObjectClass      *object_class    = G_OBJECT_CLASS (klass);
  PikaToolClass     *tool_class      = PIKA_TOOL_CLASS (klass);
  PikaDrawToolClass *draw_tool_class = PIKA_DRAW_TOOL_CLASS (klass);

  object_class->constructed    = pika_text_tool_constructed;
  object_class->finalize       = pika_text_tool_finalize;

  tool_class->control          = pika_text_tool_control;
  tool_class->button_press     = pika_text_tool_button_press;
  tool_class->motion           = pika_text_tool_motion;
  tool_class->button_release   = pika_text_tool_button_release;
  tool_class->key_press        = pika_text_tool_key_press;
  tool_class->key_release      = pika_text_tool_key_release;
  tool_class->oper_update      = pika_text_tool_oper_update;
  tool_class->cursor_update    = pika_text_tool_cursor_update;
  tool_class->get_popup        = pika_text_tool_get_popup;

  draw_tool_class->draw        = pika_text_tool_draw;
}

static void
pika_text_tool_init (PikaTextTool *text_tool)
{
  PikaTool *tool = PIKA_TOOL (text_tool);

  text_tool->buffer = pika_text_buffer_new ();

  g_signal_connect (text_tool->buffer, "begin-user-action",
                    G_CALLBACK (pika_text_tool_buffer_begin_edit),
                    text_tool);
  g_signal_connect (text_tool->buffer, "end-user-action",
                    G_CALLBACK (pika_text_tool_buffer_end_edit),
                    text_tool);
  g_signal_connect (text_tool->buffer, "color-applied",
                    G_CALLBACK (pika_text_tool_buffer_color_applied),
                    text_tool);

  text_tool->handle_rectangle_change_complete = TRUE;

  pika_text_tool_editor_init (text_tool);

  pika_tool_control_set_scroll_lock          (tool->control, TRUE);
  pika_tool_control_set_handle_empty_image   (tool->control, TRUE);
  pika_tool_control_set_wants_click          (tool->control, TRUE);
  pika_tool_control_set_wants_double_click   (tool->control, TRUE);
  pika_tool_control_set_wants_triple_click   (tool->control, TRUE);
  pika_tool_control_set_wants_all_key_events (tool->control, TRUE);
  pika_tool_control_set_active_modifiers     (tool->control,
                                              PIKA_TOOL_ACTIVE_MODIFIERS_SEPARATE);
  pika_tool_control_set_precision            (tool->control,
                                              PIKA_CURSOR_PRECISION_PIXEL_BORDER);
  pika_tool_control_set_tool_cursor          (tool->control,
                                              PIKA_TOOL_CURSOR_TEXT);
  pika_tool_control_set_action_object_1      (tool->control,
                                              "context-font-select-set");
}

static void
pika_text_tool_constructed (GObject *object)
{
  PikaTextTool    *text_tool = PIKA_TEXT_TOOL (object);
  PikaTextOptions *options   = PIKA_TEXT_TOOL_GET_OPTIONS (text_tool);
  PikaTool        *tool      = PIKA_TOOL (text_tool);
  PikaAsyncSet    *async_set;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  text_tool->proxy = g_object_new (PIKA_TYPE_TEXT, "pika", tool->tool_info->pika, NULL);

  pika_text_options_connect_text (options, text_tool->proxy);

  g_signal_connect_object (text_tool->proxy, "notify",
                           G_CALLBACK (pika_text_tool_proxy_notify),
                           text_tool, 0);

  async_set =
    pika_data_factory_get_async_set (tool->tool_info->pika->font_factory);

  g_signal_connect_object (async_set,
                           "notify::empty",
                           G_CALLBACK (pika_text_tool_fonts_async_set_empty_notify),
                           text_tool, 0);
}

static void
pika_text_tool_finalize (GObject *object)
{
  PikaTextTool *text_tool = PIKA_TEXT_TOOL (object);

  g_clear_object (&text_tool->proxy);
  g_clear_object (&text_tool->buffer);

  pika_text_tool_editor_finalize (text_tool);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
pika_text_tool_remove_empty_text_layer (PikaTextTool *text_tool)
{
  PikaTextLayer *text_layer = text_tool->layer;

  if (text_layer && text_layer->auto_rename)
    {
      PikaText *text = pika_text_layer_get_text (text_layer);

      if (text && text->box_mode == PIKA_TEXT_BOX_DYNAMIC &&
          (! text->text || text->text[0] == '\0') &&
          (! text->markup || text->markup[0] == '\0'))
        {
          PikaImage *image = pika_item_get_image (PIKA_ITEM (text_layer));

          if (text_tool->image == image)
            g_signal_handlers_block_by_func (image,
                                             pika_text_tool_layer_changed,
                                             text_tool);

          pika_image_remove_layer (image, PIKA_LAYER (text_layer), TRUE, NULL);
          pika_image_flush (image);

          if (text_tool->image == image)
            g_signal_handlers_unblock_by_func (image,
                                               pika_text_tool_layer_changed,
                                               text_tool);
        }
    }
}

static void
pika_text_tool_control (PikaTool       *tool,
                        PikaToolAction  action,
                        PikaDisplay    *display)
{
  PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);

  switch (action)
    {
    case PIKA_TOOL_ACTION_PAUSE:
    case PIKA_TOOL_ACTION_RESUME:
      break;

    case PIKA_TOOL_ACTION_HALT:
      pika_text_tool_halt (text_tool);
      break;

    case PIKA_TOOL_ACTION_COMMIT:
      break;
    }

  PIKA_TOOL_CLASS (parent_class)->control (tool, action, display);
}

static void
pika_text_tool_button_press (PikaTool            *tool,
                             const PikaCoords    *coords,
                             guint32              time,
                             GdkModifierType      state,
                             PikaButtonPressType  press_type,
                             PikaDisplay         *display)
{
  PikaTextTool      *text_tool = PIKA_TEXT_TOOL (tool);
  PikaImage         *image     = pika_display_get_image (display);
  PikaText          *text      = text_tool->text;
  PikaToolRectangle *rectangle;

  pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

  if (tool->display && tool->display != display)
    pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);

  if (! text_tool->widget)
    {
      GError *error = NULL;

      if (! pika_text_tool_start (text_tool, display, NULL, &error))
        {
          pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));

          pika_tool_message_literal (tool, display, error->message);

          g_clear_error (&error);

          return;
        }

      pika_tool_widget_hover (text_tool->widget, coords, state, TRUE);

      /* HACK: force CREATING on a newly created rectangle; otherwise,
       * the above binding of properties would cause the rectangle to
       * start with the size from tool options.
       */
      pika_tool_rectangle_set_function (PIKA_TOOL_RECTANGLE (text_tool->widget),
                                        PIKA_TOOL_RECTANGLE_CREATING);
    }

  rectangle = PIKA_TOOL_RECTANGLE (text_tool->widget);

  if (press_type == PIKA_BUTTON_PRESS_NORMAL)
    {
      pika_tool_control_activate (tool->control);

      /* clicking anywhere while a preedit is going on aborts the
       * preedit, this is ugly but at least leaves everything in
       * a consistent state
       */
      if (text_tool->preedit_active)
        pika_text_tool_abort_im_context (text_tool);
      else
        pika_text_tool_reset_im_context (text_tool);

      text_tool->selecting = FALSE;

      if (pika_tool_rectangle_point_in_rectangle (rectangle,
                                                  coords->x,
                                                  coords->y) &&
          ! text_tool->moving)
        {
          pika_tool_rectangle_set_function (rectangle,
                                            PIKA_TOOL_RECTANGLE_DEAD);
        }
      else if (pika_tool_widget_button_press (text_tool->widget, coords,
                                              time, state, press_type))
        {
          text_tool->grab_widget = text_tool->widget;
        }

      /*  bail out now if the user user clicked on a handle of an
       *  existing rectangle, but not inside an existing framed layer
       */
      if (pika_tool_rectangle_get_function (rectangle) !=
          PIKA_TOOL_RECTANGLE_CREATING)
        {
          if (text_tool->layer)
            {
              PikaItem *item = PIKA_ITEM (text_tool->layer);
              gdouble   x    = coords->x - pika_item_get_offset_x (item);
              gdouble   y    = coords->y - pika_item_get_offset_y (item);

              if (x < 0 || x >= pika_item_get_width  (item) ||
                  y < 0 || y >= pika_item_get_height (item))
                {
                  pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
                  return;
                }
            }
          else
            {
              pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
              return;
            }
        }

      /* if the the click is not related to the currently edited text
       * layer in any way, try to pick a text layer
       */
      if (! text_tool->moving &&
          pika_tool_rectangle_get_function (rectangle) ==
          PIKA_TOOL_RECTANGLE_CREATING)
        {
          PikaTextLayer *text_layer;

          text_layer = pika_image_pick_text_layer (image, coords->x, coords->y);

          if (text_layer && text_layer != text_tool->layer)
            {
              GList *selection = g_list_prepend (NULL, text_layer);

              if (text_tool->image == image)
                g_signal_handlers_block_by_func (image,
                                                 pika_text_tool_layer_changed,
                                                 text_tool);

              pika_image_set_selected_layers (image, selection);
              g_list_free (selection);

              if (text_tool->image == image)
                g_signal_handlers_unblock_by_func (image,
                                                   pika_text_tool_layer_changed,
                                                   text_tool);
            }
        }
    }

  if (pika_image_coords_in_active_pickable (image, coords, FALSE, FALSE, FALSE))
    {
      GList        *drawables = pika_image_get_selected_drawables (image);
      PikaDrawable *drawable  = NULL;
      gdouble       x         = coords->x;
      gdouble       y         = coords->y;

      if (g_list_length (drawables) == 1)
        {
          PikaItem *item = PIKA_ITEM (drawables->data);

          x = coords->x - pika_item_get_offset_x (item);
          y = coords->y - pika_item_get_offset_y (item);

          drawable = drawables->data;
        }
      g_list_free (drawables);

      /*  did the user click on a text layer?  */
      if (drawable &&
          pika_text_tool_set_drawable (text_tool, drawable, TRUE))
        {
          if (press_type == PIKA_BUTTON_PRESS_NORMAL)
            {
              /*  if we clicked on a text layer while the tool was idle
               *  (didn't show a rectangle), frame the layer and switch to
               *  selecting instead of drawing a new rectangle
               */
              if (pika_tool_rectangle_get_function (rectangle) ==
                  PIKA_TOOL_RECTANGLE_CREATING)
                {
                  pika_tool_rectangle_set_function (rectangle,
                                                    PIKA_TOOL_RECTANGLE_DEAD);

                  pika_text_tool_frame_item (text_tool);
                }

              if (text_tool->text && text_tool->text != text)
                {
                  pika_text_tool_editor_start (text_tool);
                }
            }

          if (text_tool->text && ! text_tool->moving)
            {
              text_tool->selecting = TRUE;

              pika_text_tool_editor_button_press (text_tool, x, y, press_type);
            }
          else
            {
              text_tool->selecting = FALSE;
            }

          pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));

          return;
        }
    }

  if (press_type == PIKA_BUTTON_PRESS_NORMAL)
    {
      /*  create a new text layer  */
      text_tool->text_box_fixed = FALSE;

      /* make sure the text tool has an image, even if the user didn't click
       * inside the active drawable, in particular, so that the text style
       * editor picks the correct resolution.
       */
      pika_text_tool_set_image (text_tool, image);

      pika_text_tool_connect (text_tool, NULL, NULL);
      pika_text_tool_editor_start (text_tool);
    }

  pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
}

static void
pika_text_tool_button_release (PikaTool              *tool,
                               const PikaCoords      *coords,
                               guint32                time,
                               GdkModifierType        state,
                               PikaButtonReleaseType  release_type,
                               PikaDisplay           *display)
{
  PikaTextTool      *text_tool = PIKA_TEXT_TOOL (tool);
  PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (text_tool->widget);

  pika_tool_control_halt (tool->control);

  if (text_tool->selecting)
    {
      /*  we are in a selection process (user has initially clicked on
       *  an existing text layer), so finish the selection process and
       *  ignore rectangle-change-complete.
       */

      /*  need to block "end-user-action" on the text buffer, because
       *  GtkTextBuffer considers copying text to the clipboard an
       *  undo-relevant user action, which is clearly a bug, but what
       *  can we do...
       */
      g_signal_handlers_block_by_func (text_tool->buffer,
                                       pika_text_tool_buffer_begin_edit,
                                       text_tool);
      g_signal_handlers_block_by_func (text_tool->buffer,
                                       pika_text_tool_buffer_end_edit,
                                       text_tool);

      pika_text_tool_editor_button_release (text_tool);

      g_signal_handlers_unblock_by_func (text_tool->buffer,
                                         pika_text_tool_buffer_end_edit,
                                         text_tool);
      g_signal_handlers_unblock_by_func (text_tool->buffer,
                                         pika_text_tool_buffer_begin_edit,
                                         text_tool);

      text_tool->selecting = FALSE;

      text_tool->handle_rectangle_change_complete = FALSE;

      /*  there is no cancelling of selections yet  */
      if (release_type == PIKA_BUTTON_RELEASE_CANCEL)
        release_type = PIKA_BUTTON_RELEASE_NORMAL;
    }
  else if (text_tool->moving)
    {
      /*  the user has moved the text layer with Alt-drag, fall
       *  through and let rectangle-change-complete do its job of
       *  setting text layer's new position.
       */
    }
  else if (pika_tool_rectangle_get_function (rectangle) ==
           PIKA_TOOL_RECTANGLE_DEAD)
    {
      /*  the user clicked in dead space (like between the corner and
       *  edge handles, so completely ignore that.
       */

      text_tool->handle_rectangle_change_complete = FALSE;
    }
  else if (release_type == PIKA_BUTTON_RELEASE_CANCEL)
    {
      /*  user has canceled the rectangle resizing, fall through
       *  and let the rectangle handle restoring the previous size
       */
    }
  else
    {
      gdouble x1, y1;
      gdouble x2, y2;

      /*  otherwise the user has clicked outside of any text layer in
       *  order to create a new text, fall through and let
       *  rectangle-change-complete do its job of setting the new text
       *  layer's size.
       */

      g_object_get (rectangle,
                    "x1", &x1,
                    "y1", &y1,
                    "x2", &x2,
                    "y2", &y2,
                    NULL);

      if (release_type == PIKA_BUTTON_RELEASE_CLICK ||
          (x2 - x1) < 3                             ||
          (y2 - y1) < 3)
        {
          /*  unless the rectangle is unreasonably small to hold any
           *  real text (the user has eitherjust clicked or just made
           *  a rectangle of a few pixels), so set the text box to
           *  dynamic and ignore rectangle-change-complete.
           */

          g_object_set (text_tool->proxy,
                        "box-mode", PIKA_TEXT_BOX_DYNAMIC,
                        NULL);

          text_tool->handle_rectangle_change_complete = FALSE;
        }
    }

  if (text_tool->grab_widget)
    {
      pika_tool_widget_button_release (text_tool->grab_widget,
                                       coords, time, state, release_type);
      text_tool->grab_widget = NULL;
    }

  text_tool->handle_rectangle_change_complete = TRUE;
}

static void
pika_text_tool_motion (PikaTool         *tool,
                       const PikaCoords *coords,
                       guint32           time,
                       GdkModifierType   state,
                       PikaDisplay      *display)
{
  PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);

  if (! text_tool->selecting)
    {
      if (text_tool->grab_widget)
        {
          pika_tool_widget_motion (text_tool->grab_widget,
                                   coords, time, state);
        }
    }
  else
    {
      PikaItem *item = PIKA_ITEM (text_tool->layer);
      gdouble   x    = coords->x - pika_item_get_offset_x (item);
      gdouble   y    = coords->y - pika_item_get_offset_y (item);

      pika_text_tool_editor_motion (text_tool, x, y);
    }
}

static gboolean
pika_text_tool_key_press (PikaTool    *tool,
                          GdkEventKey *kevent,
                          PikaDisplay *display)
{
  PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);

  if (display == tool->display)
    return pika_text_tool_editor_key_press (text_tool, kevent);

  return FALSE;
}

static gboolean
pika_text_tool_key_release (PikaTool    *tool,
                            GdkEventKey *kevent,
                            PikaDisplay *display)
{
  PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);

  if (display == tool->display)
    return pika_text_tool_editor_key_release (text_tool, kevent);

  return FALSE;
}

static void
pika_text_tool_oper_update (PikaTool         *tool,
                            const PikaCoords *coords,
                            GdkModifierType   state,
                            gboolean          proximity,
                            PikaDisplay      *display)
{
  PikaTextTool      *text_tool = PIKA_TEXT_TOOL (tool);
  PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (text_tool->widget);

  PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
                                               proximity, display);

  text_tool->moving = (text_tool->widget &&
                       pika_tool_rectangle_get_function (rectangle) ==
                       PIKA_TOOL_RECTANGLE_MOVING &&
                       (state & GDK_MOD1_MASK));
}

static void
pika_text_tool_cursor_update (PikaTool         *tool,
                              const PikaCoords *coords,
                              GdkModifierType   state,
                              PikaDisplay      *display)
{
  PikaTextTool      *text_tool = PIKA_TEXT_TOOL (tool);
  PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (text_tool->widget);

  if (rectangle && tool->display == display)
    {
      if (pika_tool_rectangle_point_in_rectangle (rectangle,
                                                  coords->x,
                                                  coords->y) &&
          ! text_tool->moving)
        {
          pika_tool_set_cursor (tool, display,
                                (PikaCursorType) GDK_XTERM,
                                pika_tool_control_get_tool_cursor (tool->control),
                                PIKA_CURSOR_MODIFIER_NONE);
        }
      else
        {
          PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
                                                         display);
        }
    }
  else
    {
      PikaAsyncSet *async_set;

      async_set =
        pika_data_factory_get_async_set (tool->tool_info->pika->font_factory);

      if (pika_async_set_is_empty (async_set))
        {
          PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
                                                         display);
        }
      else
        {
          pika_tool_set_cursor (tool, display,
                                pika_tool_control_get_cursor (tool->control),
                                pika_tool_control_get_tool_cursor (tool->control),
                                PIKA_CURSOR_MODIFIER_BAD);
        }
    }
}

static PikaUIManager *
pika_text_tool_get_popup (PikaTool         *tool,
                          const PikaCoords *coords,
                          GdkModifierType   state,
                          PikaDisplay      *display,
                          const gchar     **ui_path)
{
  PikaTextTool      *text_tool = PIKA_TEXT_TOOL (tool);
  PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (text_tool->widget);

  if (rectangle &&
      pika_tool_rectangle_point_in_rectangle (rectangle,
                                              coords->x,
                                              coords->y))
    {
      PikaMenuFactory *menu_factory;
      PikaUIManager   *ui_manager;

      menu_factory = menus_get_global_menu_factory (tool->tool_info->pika);
      ui_manager   = pika_menu_factory_get_manager (menu_factory, "<TextTool>", text_tool);

      pika_ui_manager_update (ui_manager, text_tool);

      *ui_path = "/text-tool-popup";

      return ui_manager;
    }

  return NULL;
}

static void
pika_text_tool_draw (PikaDrawTool *draw_tool)
{
  PikaTextTool *text_tool = PIKA_TEXT_TOOL (draw_tool);

  PIKA_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);

  if (! text_tool->text  ||
      ! text_tool->layer ||
      ! text_tool->layer->text)
    {
      pika_text_tool_editor_update_im_cursor (text_tool);

      return;
    }

  pika_text_tool_ensure_layout (text_tool);

  if (gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (text_tool->buffer)))
    {
      /* If the text buffer has a selection, highlight the selected letters */

      pika_text_tool_draw_selection (draw_tool);
    }
  else
    {
      /* If the text buffer has no selection, draw the text cursor */

      PikaCanvasItem   *item;
      PangoRectangle    cursor_rect;
      gint              off_x, off_y;
      gboolean          overwrite;
      PikaTextDirection direction;

      pika_text_tool_editor_get_cursor_rect (text_tool,
                                             text_tool->overwrite_mode,
                                             &cursor_rect);

      pika_item_get_offset (PIKA_ITEM (text_tool->layer), &off_x, &off_y);
      cursor_rect.x += off_x;
      cursor_rect.y += off_y;

      overwrite = text_tool->overwrite_mode && cursor_rect.width != 0;

      direction = pika_text_tool_get_direction (text_tool);

      item = pika_draw_tool_add_text_cursor (draw_tool, &cursor_rect,
                                             overwrite, direction);
      pika_canvas_item_set_highlight (item, TRUE);
    }

  pika_text_tool_editor_update_im_cursor (text_tool);
}

static void
pika_text_tool_draw_selection (PikaDrawTool *draw_tool)
{
  PikaTextTool     *text_tool = PIKA_TEXT_TOOL (draw_tool);
  GtkTextBuffer    *buffer    = GTK_TEXT_BUFFER (text_tool->buffer);
  PikaCanvasGroup  *group;
  PangoLayout      *layout;
  gint              offset_x;
  gint              offset_y;
  gint              width;
  gint              height;
  gint              off_x, off_y;
  PangoLayoutIter  *iter;
  GtkTextIter       sel_start, sel_end;
  gint              min, max;
  gint              i;
  PikaTextDirection direction;

  group = pika_draw_tool_add_stroke_group (draw_tool);
  pika_canvas_item_set_highlight (PIKA_CANVAS_ITEM (group), TRUE);

  gtk_text_buffer_get_selection_bounds (buffer, &sel_start, &sel_end);

  min = pika_text_buffer_get_iter_index (text_tool->buffer, &sel_start, TRUE);
  max = pika_text_buffer_get_iter_index (text_tool->buffer, &sel_end, TRUE);

  layout = pika_text_layout_get_pango_layout (text_tool->layout);

  pika_text_layout_get_offsets (text_tool->layout, &offset_x, &offset_y);

  pika_text_layout_get_size (text_tool->layout, &width, &height);

  pika_item_get_offset (PIKA_ITEM (text_tool->layer), &off_x, &off_y);
  offset_x += off_x;
  offset_y += off_y;

  direction = pika_text_tool_get_direction (text_tool);

  iter = pango_layout_get_iter (layout);

  pika_draw_tool_push_group (draw_tool, group);

  do
    {
      if (! pango_layout_iter_get_run (iter))
        continue;

      i = pango_layout_iter_get_index (iter);

      if (i >= min && i < max)
        {
          PangoRectangle rect;
          gint           ytop, ybottom;

          pango_layout_iter_get_char_extents (iter, &rect);
          pango_layout_iter_get_line_yrange (iter, &ytop, &ybottom);

          rect.y      = ytop;
          rect.height = ybottom - ytop;

          pango_extents_to_pixels (&rect, NULL);

          pika_text_layout_transform_rect (text_tool->layout, &rect);

          switch (direction)
            {
            case PIKA_TEXT_DIRECTION_LTR:
            case PIKA_TEXT_DIRECTION_RTL:
              rect.x += offset_x;
              rect.y += offset_y;
              pika_draw_tool_add_rectangle (draw_tool, FALSE,
                                            rect.x, rect.y,
                                            rect.width, rect.height);
              break;
            case PIKA_TEXT_DIRECTION_TTB_RTL:
            case PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
              rect.y = offset_x - rect.y + width;
              rect.x = offset_y + rect.x;
              pika_draw_tool_add_rectangle (draw_tool, FALSE,
                                            rect.y, rect.x,
                                            -rect.height, rect.width);
              break;
            case PIKA_TEXT_DIRECTION_TTB_LTR:
            case PIKA_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
              rect.y = offset_x + rect.y;
              rect.x = offset_y - rect.x + height;
              pika_draw_tool_add_rectangle (draw_tool, FALSE,
                                            rect.y, rect.x,
                                            rect.height, -rect.width);
              break;
            }
        }
    }
  while (pango_layout_iter_next_char (iter));

  pika_draw_tool_pop_group (draw_tool);

  pango_layout_iter_free (iter);
}

static gboolean
pika_text_tool_start (PikaTextTool  *text_tool,
                      PikaDisplay   *display,
                      PikaLayer     *layer,
                      GError       **error)
{
  PikaTool         *tool  = PIKA_TOOL (text_tool);
  PikaDisplayShell *shell = pika_display_get_shell (display);
  PikaToolWidget   *widget;
  PikaAsyncSet     *async_set;

  async_set =
    pika_data_factory_get_async_set (tool->tool_info->pika->font_factory);

  if (! pika_async_set_is_empty (async_set))
    {
      g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
                           _("Fonts are still loading"));

      return FALSE;
    }

  tool->display = display;

  text_tool->widget = widget = pika_tool_rectangle_new (shell);

  g_object_set (widget,
                "force-narrow-mode", TRUE,
                "status-title",      _("Text box: "),
                NULL);

  pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), widget);

  g_signal_connect (widget, "response",
                    G_CALLBACK (pika_text_tool_rectangle_response),
                    text_tool);
  g_signal_connect (widget, "change-complete",
                    G_CALLBACK (pika_text_tool_rectangle_change_complete),
                    text_tool);

  pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display);

  if (layer)
    {
      pika_text_tool_frame_item (text_tool);
      pika_text_tool_editor_start (text_tool);
      pika_text_tool_editor_position (text_tool);
    }

  return TRUE;
}

static void
pika_text_tool_halt (PikaTextTool *text_tool)
{
  PikaTool *tool = PIKA_TOOL (text_tool);

  pika_text_tool_editor_halt (text_tool);
  pika_text_tool_clear_layout (text_tool);
  pika_text_tool_set_drawable (text_tool, NULL, FALSE);

  if (pika_draw_tool_is_active (PIKA_DRAW_TOOL (tool)))
    pika_draw_tool_stop (PIKA_DRAW_TOOL (tool));

  pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), NULL);
  g_clear_object (&text_tool->widget);

  tool->display   = NULL;
  g_list_free (tool->drawables);
  tool->drawables = NULL;
}

static void
pika_text_tool_frame_item (PikaTextTool *text_tool)
{
  g_return_if_fail (PIKA_IS_LAYER (text_tool->layer));

  text_tool->handle_rectangle_change_complete = FALSE;

  pika_tool_rectangle_frame_item (PIKA_TOOL_RECTANGLE (text_tool->widget),
                                  PIKA_ITEM (text_tool->layer));

  text_tool->handle_rectangle_change_complete = TRUE;
}

static void
pika_text_tool_rectangle_response (PikaToolRectangle *rectangle,
                                   gint               response_id,
                                   PikaTextTool      *text_tool)
{
  PikaTool *tool = PIKA_TOOL (text_tool);

  /* this happens when a newly created rectangle gets canceled,
   * we have to shut down the tool
   */
  if (response_id == PIKA_TOOL_WIDGET_RESPONSE_CANCEL)
    pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
}

static void
pika_text_tool_rectangle_change_complete (PikaToolRectangle *rectangle,
                                          PikaTextTool      *text_tool)
{
  pika_text_tool_editor_position (text_tool);

  if (text_tool->handle_rectangle_change_complete)
    {
      PikaItem *item = PIKA_ITEM (text_tool->layer);
      gdouble   x1, y1;
      gdouble   x2, y2;

      if (! item)
        {
          /* we can't set properties for the text layer, because it
           * isn't created until some text has been inserted, so we
           * need to make a special note that will remind us what to
           * do when we actually create the layer
           */
          text_tool->text_box_fixed = TRUE;

          return;
        }

      g_object_get (rectangle,
                    "x1", &x1,
                    "y1", &y1,
                    "x2", &x2,
                    "y2", &y2,
                    NULL);

      if ((x2 - x1) != pika_item_get_width  (item) ||
          (y2 - y1) != pika_item_get_height (item))
        {
          PikaUnit  box_unit = text_tool->proxy->box_unit;
          gdouble   xres, yres;
          gboolean  push_undo = TRUE;
          PikaUndo *undo;

          pika_image_get_resolution (text_tool->image, &xres, &yres);

          g_object_set (text_tool->proxy,
                        "box-mode",   PIKA_TEXT_BOX_FIXED,
                        "box-width",  pika_pixels_to_units (x2 - x1,
                                                            box_unit, xres),
                        "box-height", pika_pixels_to_units (y2 - y1,
                                                            box_unit, yres),
                        NULL);

          undo = pika_image_undo_can_compress (text_tool->image,
                                               PIKA_TYPE_UNDO_STACK,
                                               PIKA_UNDO_GROUP_TEXT);

          if (undo &&
              pika_undo_get_age (undo) <= TEXT_UNDO_TIMEOUT &&
              g_object_get_data (G_OBJECT (undo), "reshape-text-layer") == (gpointer) item)
            push_undo = FALSE;

          if (push_undo)
            {
              pika_image_undo_group_start (text_tool->image, PIKA_UNDO_GROUP_TEXT,
                                           _("Reshape Text Layer"));

              undo = pika_image_undo_can_compress (text_tool->image, PIKA_TYPE_UNDO_STACK,
                                                   PIKA_UNDO_GROUP_TEXT);

              if (undo)
                g_object_set_data (G_OBJECT (undo), "reshape-text-layer",
                                   (gpointer) item);
            }

          pika_text_tool_block_drawing (text_tool);

          pika_item_translate (item,
                               x1 - pika_item_get_offset_x (item),
                               y1 - pika_item_get_offset_y (item),
                               push_undo);
          pika_text_tool_apply (text_tool, push_undo);

          pika_text_tool_unblock_drawing (text_tool);

          if (push_undo)
            pika_image_undo_group_end (text_tool->image);
        }
      else if (x1 != pika_item_get_offset_x (item) ||
               y1 != pika_item_get_offset_y (item))
        {
          pika_text_tool_block_drawing (text_tool);

          pika_text_tool_apply (text_tool, TRUE);

          pika_item_translate (item,
                               x1 - pika_item_get_offset_x (item),
                               y1 - pika_item_get_offset_y (item),
                               TRUE);

          pika_text_tool_unblock_drawing (text_tool);

          pika_image_flush (text_tool->image);
        }
    }
}

static void
pika_text_tool_connect (PikaTextTool  *text_tool,
                        PikaTextLayer *layer,
                        PikaText      *text)
{
  PikaTool *tool = PIKA_TOOL (text_tool);

  g_return_if_fail (text == NULL || (layer != NULL && layer->text == text));

  if (text_tool->text != text)
    {
      PikaTextOptions *options = PIKA_TEXT_TOOL_GET_OPTIONS (tool);

      g_signal_handlers_block_by_func (text_tool->buffer,
                                       pika_text_tool_buffer_begin_edit,
                                       text_tool);
      g_signal_handlers_block_by_func (text_tool->buffer,
                                       pika_text_tool_buffer_end_edit,
                                       text_tool);

      if (text_tool->text)
        {
          g_signal_handlers_disconnect_by_func (text_tool->text,
                                                pika_text_tool_text_notify,
                                                text_tool);
          g_signal_handlers_disconnect_by_func (text_tool->text,
                                                pika_text_tool_text_changed,
                                                text_tool);

          if (text_tool->pending)
            pika_text_tool_apply (text_tool, TRUE);

          g_clear_object (&text_tool->text);

          g_object_set (text_tool->proxy,
                        "text",   NULL,
                        "markup", NULL,
                        NULL);
          pika_text_buffer_set_text (text_tool->buffer, NULL);

          pika_text_tool_clear_layout (text_tool);
        }

      pika_context_define_property (PIKA_CONTEXT (options),
                                    PIKA_CONTEXT_PROP_FOREGROUND,
                                    text != NULL);

      if (text)
        {
          if (text->unit != text_tool->proxy->unit)
            pika_size_entry_set_unit (PIKA_SIZE_ENTRY (options->size_entry),
                                      text->unit);

          pika_config_sync (G_OBJECT (text), G_OBJECT (text_tool->proxy), 0);

          if (text->markup)
            pika_text_buffer_set_markup (text_tool->buffer, text->markup);
          else
            pika_text_buffer_set_text (text_tool->buffer, text->text);

          pika_text_tool_clear_layout (text_tool);

          text_tool->text = g_object_ref (text);

          g_signal_connect (text, "notify",
                            G_CALLBACK (pika_text_tool_text_notify),
                            text_tool);
          g_signal_connect (text, "changed",
                            G_CALLBACK (pika_text_tool_text_changed),
                            text_tool);
        }

      g_signal_handlers_unblock_by_func (text_tool->buffer,
                                         pika_text_tool_buffer_end_edit,
                                         text_tool);
      g_signal_handlers_unblock_by_func (text_tool->buffer,
                                         pika_text_tool_buffer_begin_edit,
                                         text_tool);
    }

  if (text_tool->layer != layer)
    {
      if (text_tool->layer)
        {
          g_signal_handlers_disconnect_by_func (text_tool->layer,
                                                pika_text_tool_layer_notify,
                                                text_tool);

          /*  don't try to remove the layer if it is not attached,
           *  which can happen if we got here because the layer was
           *  somehow deleted from the image (like by the user in the
           *  layers dialog).
           */
          if (pika_item_is_attached (PIKA_ITEM (text_tool->layer)))
            pika_text_tool_remove_empty_text_layer (text_tool);
        }

      text_tool->layer = layer;

      if (layer)
        {
          g_signal_connect_object (text_tool->layer, "notify",
                                   G_CALLBACK (pika_text_tool_layer_notify),
                                   text_tool, 0);
        }
    }
}

static void
pika_text_tool_layer_notify (PikaTextLayer    *layer,
                             const GParamSpec *pspec,
                             PikaTextTool     *text_tool)
{
  PikaTool *tool = PIKA_TOOL (text_tool);

  if (! strcmp (pspec->name, "modified"))
    {
      if (layer->modified)
        pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
    }
  else if (! strcmp (pspec->name, "text"))
    {
      if (! layer->text)
        pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
    }
  else if (! strcmp (pspec->name, "offset-x") ||
           ! strcmp (pspec->name, "offset-y"))
    {
      if (pika_item_is_attached (PIKA_ITEM (layer)))
        {
          pika_text_tool_block_drawing (text_tool);

          pika_text_tool_frame_item (text_tool);

          pika_text_tool_unblock_drawing (text_tool);
        }
    }
}

static gboolean
pika_text_tool_apply_idle (PikaTextTool *text_tool)
{
  text_tool->idle_id = 0;

  pika_text_tool_apply (text_tool, TRUE);

  pika_text_tool_unblock_drawing (text_tool);

  return G_SOURCE_REMOVE;
}

static void
pika_text_tool_proxy_notify (PikaText         *text,
                             const GParamSpec *pspec,
                             PikaTextTool     *text_tool)
{
  if (! text_tool->text)
    return;

  if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE &&
      pspec->owner_type == PIKA_TYPE_TEXT)
    {
      if (text_tool->preedit_active)
        {
          /* if there is a preedit going on, don't queue pending
           * changes to be idle-applied with undo; instead, flush the
           * pending queue (happens only when preedit starts), and
           * apply the changes to text_tool->text directly. Preedit
           * will *always* end by removing the preedit string, and if
           * the preedit was committed, it will insert the resulting
           * text, which will not trigger this if() any more.
           */

          GList *list = NULL;

         /* if there are pending changes, apply them before applying
           * preedit stuff directly (bypassing undo)
           */
          if (text_tool->pending)
            {
              pika_text_tool_block_drawing (text_tool);
              pika_text_tool_apply (text_tool, TRUE);
              pika_text_tool_unblock_drawing (text_tool);
            }

          pika_text_tool_block_drawing (text_tool);

          list = g_list_append (list, (gpointer) pspec);
          pika_text_tool_apply_list (text_tool, list);
          g_list_free (list);

          pika_text_tool_frame_item (text_tool);

          pika_image_flush (pika_item_get_image (PIKA_ITEM (text_tool->layer)));

          pika_text_tool_unblock_drawing (text_tool);
        }
      else
        {
          /* else queue the property change for normal processing,
           * including undo
           */

          text_tool->pending = g_list_append (text_tool->pending,
                                              (gpointer) pspec);

          if (! text_tool->idle_id)
            {
              pika_text_tool_block_drawing (text_tool);

              text_tool->idle_id =
                g_idle_add_full (G_PRIORITY_LOW,
                                 (GSourceFunc) pika_text_tool_apply_idle,
                                 text_tool,
                                 NULL);
            }
        }
    }
}

static void
pika_text_tool_text_notify (PikaText         *text,
                            const GParamSpec *pspec,
                            PikaTextTool     *text_tool)
{
  g_return_if_fail (text == text_tool->text);

  /* an undo cancels all preedit operations */
  if (text_tool->preedit_active)
    pika_text_tool_abort_im_context (text_tool);

  pika_text_tool_block_drawing (text_tool);

  if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE)
    {
      GValue value = G_VALUE_INIT;

      g_value_init (&value, pspec->value_type);

      g_object_get_property (G_OBJECT (text), pspec->name, &value);

      g_signal_handlers_block_by_func (text_tool->proxy,
                                       pika_text_tool_proxy_notify,
                                       text_tool);

      g_object_set_property (G_OBJECT (text_tool->proxy), pspec->name, &value);

      g_signal_handlers_unblock_by_func (text_tool->proxy,
                                         pika_text_tool_proxy_notify,
                                         text_tool);

      g_value_unset (&value);
    }

  /* if the text has changed, (probably because of an undo), we put
   * the new text into the text buffer
   */
  if (strcmp (pspec->name, "text")   == 0 ||
      strcmp (pspec->name, "markup") == 0)
    {
      g_signal_handlers_block_by_func (text_tool->buffer,
                                       pika_text_tool_buffer_begin_edit,
                                       text_tool);
      g_signal_handlers_block_by_func (text_tool->buffer,
                                       pika_text_tool_buffer_end_edit,
                                       text_tool);

      if (text->markup)
        pika_text_buffer_set_markup (text_tool->buffer, text->markup);
      else
        pika_text_buffer_set_text (text_tool->buffer, text->text);

      g_signal_handlers_unblock_by_func (text_tool->buffer,
                                         pika_text_tool_buffer_end_edit,
                                         text_tool);
      g_signal_handlers_unblock_by_func (text_tool->buffer,
                                         pika_text_tool_buffer_begin_edit,
                                         text_tool);
    }

  pika_text_tool_unblock_drawing (text_tool);
}

static void
pika_text_tool_text_changed (PikaText     *text,
                             PikaTextTool *text_tool)
{
  pika_text_tool_block_drawing (text_tool);

  /* we need to redraw the rectangle in any case because whatever
   * changes to the text can change its size
   */
  pika_text_tool_frame_item (text_tool);

  pika_text_tool_unblock_drawing (text_tool);
}

static void
pika_text_tool_fonts_async_set_empty_notify (PikaAsyncSet *async_set,
                                             GParamSpec   *pspec,
                                             PikaTextTool *text_tool)
{
  PikaTool *tool = PIKA_TOOL (text_tool);

  if (! pika_async_set_is_empty (async_set) && tool->display)
    pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
}

static void
pika_text_tool_apply_list (PikaTextTool *text_tool,
                           GList        *pspecs)
{
  GObject *src  = G_OBJECT (text_tool->proxy);
  GObject *dest = G_OBJECT (text_tool->text);
  GList   *list;

  g_signal_handlers_block_by_func (dest,
                                   pika_text_tool_text_notify,
                                   text_tool);
  g_signal_handlers_block_by_func (dest,
                                   pika_text_tool_text_changed,
                                   text_tool);

  g_object_freeze_notify (dest);

  for (list = pspecs; list; list = g_list_next (list))
    {
      const GParamSpec *pspec;
      GValue            value = G_VALUE_INIT;

      /*  look ahead and compress changes  */
      if (list->next && list->next->data == list->data)
        continue;

      pspec = list->data;

      g_value_init (&value, pspec->value_type);

      g_object_get_property (src,  pspec->name, &value);
      g_object_set_property (dest, pspec->name, &value);

      g_value_unset (&value);
    }

  g_object_thaw_notify (dest);

  g_signal_handlers_unblock_by_func (dest,
                                     pika_text_tool_text_notify,
                                     text_tool);
  g_signal_handlers_unblock_by_func (dest,
                                     pika_text_tool_text_changed,
                                     text_tool);
}

static void
pika_text_tool_create_layer (PikaTextTool *text_tool,
                             PikaText     *text)
{
  PikaTool  *tool  = PIKA_TOOL (text_tool);
  PikaImage *image = pika_display_get_image (tool->display);
  PikaLayer *layer;
  gdouble    x1, y1;
  gdouble    x2, y2;

  pika_text_tool_block_drawing (text_tool);

  if (text)
    {
      text = pika_config_duplicate (PIKA_CONFIG (text));
    }
  else
    {
      gchar *string;

      if (pika_text_buffer_has_markup (text_tool->buffer))
        {
          string = pika_text_buffer_get_markup (text_tool->buffer);

          g_object_set (text_tool->proxy,
                        "markup",   string,
                        "box-mode", PIKA_TEXT_BOX_DYNAMIC,
                        NULL);
        }
      else
        {
          string = pika_text_buffer_get_text (text_tool->buffer);

          g_object_set (text_tool->proxy,
                        "text",     string,
                        "box-mode", PIKA_TEXT_BOX_DYNAMIC,
                        NULL);
        }

      g_free (string);

      text = pika_config_duplicate (PIKA_CONFIG (text_tool->proxy));
    }

  layer = pika_text_layer_new (image, text);

  g_object_unref (text);

  if (! layer)
    {
      pika_text_tool_unblock_drawing (text_tool);
      return;
    }

  pika_text_tool_connect (text_tool, PIKA_TEXT_LAYER (layer), text);

  pika_image_undo_group_start (image, PIKA_UNDO_GROUP_TEXT,
                               _("Add Text Layer"));

  if (pika_image_get_floating_selection (image))
    {
      g_signal_handlers_block_by_func (image,
                                       pika_text_tool_layer_changed,
                                       text_tool);

      floating_sel_anchor (pika_image_get_floating_selection (image));

      g_signal_handlers_unblock_by_func (image,
                                         pika_text_tool_layer_changed,
                                         text_tool);
    }

  g_object_get (text_tool->widget,
                "x1", &x1,
                "y1", &y1,
                "x2", &x2,
                "y2", &y2,
                NULL);

  if (text_tool->text_box_fixed == FALSE)
    {
      if (text_tool->text &&
          (text_tool->text->base_dir == PIKA_TEXT_DIRECTION_TTB_RTL ||
           text_tool->text->base_dir == PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT))
        {
          x1 -= pika_item_get_width (PIKA_ITEM (layer));
        }
    }
  pika_item_set_offset (PIKA_ITEM (layer), x1, y1);

  pika_image_add_layer (image, layer,
                        PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE);

  if (text_tool->text_box_fixed)
    {
      PikaUnit box_unit = text_tool->proxy->box_unit;
      gdouble  xres, yres;

      pika_image_get_resolution (image, &xres, &yres);

      g_object_set (text_tool->proxy,
                    "box-mode",   PIKA_TEXT_BOX_FIXED,
                    "box-width",  pika_pixels_to_units (x2 - x1,
                                                        box_unit, xres),
                    "box-height", pika_pixels_to_units (y2 - y1,
                                                        box_unit, yres),
                    NULL);

      pika_text_tool_apply (text_tool, TRUE); /* unblocks drawing */
    }
  else
    {
      pika_text_tool_frame_item (text_tool);
    }

  pika_image_undo_group_end (image);

  pika_image_flush (image);

  pika_text_tool_set_drawable (text_tool, PIKA_DRAWABLE (layer), FALSE);

  pika_text_tool_unblock_drawing (text_tool);
}

#define  RESPONSE_NEW 1

static void
pika_text_tool_confirm_response (GtkWidget    *widget,
                                 gint          response_id,
                                 PikaTextTool *text_tool)
{
  PikaTextLayer *layer = text_tool->layer;

  gtk_widget_destroy (widget);

  if (layer && layer->text)
    {
      switch (response_id)
        {
        case RESPONSE_NEW:
          pika_text_tool_create_layer (text_tool, layer->text);
          break;

        case GTK_RESPONSE_ACCEPT:
          pika_text_tool_connect (text_tool, layer, layer->text);

          /*  cause the text layer to be rerendered  */
          g_object_notify (G_OBJECT (text_tool->proxy), "markup");

          pika_text_tool_editor_start (text_tool);
          break;

        default:
          break;
        }
    }
}

static void
pika_text_tool_confirm_dialog (PikaTextTool *text_tool)
{
  PikaTool         *tool  = PIKA_TOOL (text_tool);
  PikaDisplayShell *shell = pika_display_get_shell (tool->display);
  GtkWidget        *dialog;
  GtkWidget        *vbox;
  GtkWidget        *label;

  g_return_if_fail (text_tool->layer != NULL);

  if (text_tool->confirm_dialog)
    {
      gtk_window_present (GTK_WINDOW (text_tool->confirm_dialog));
      return;
    }

  dialog = pika_viewable_dialog_new (g_list_prepend (NULL, text_tool->layer),
                                     PIKA_CONTEXT (pika_tool_get_options (tool)),
                                     _("Confirm Text Editing"),
                                     "pika-text-tool-confirm",
                                     PIKA_ICON_LAYER_TEXT_LAYER,
                                     _("Confirm Text Editing"),
                                     GTK_WIDGET (shell),
                                     pika_standard_help_func, NULL,

                                     _("Create _New Layer"), RESPONSE_NEW,
                                     _("_Cancel"),           GTK_RESPONSE_CANCEL,
                                     _("_Edit"),             GTK_RESPONSE_ACCEPT,

                                     NULL);

  pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
                                           RESPONSE_NEW,
                                           GTK_RESPONSE_ACCEPT,
                                           GTK_RESPONSE_CANCEL,
                                           -1);

  gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);

  g_signal_connect (dialog, "response",
                    G_CALLBACK (pika_text_tool_confirm_response),
                    text_tool);

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
                      vbox, FALSE, FALSE, 0);
  gtk_widget_show (vbox);

  label = gtk_label_new (_("The layer you selected is a text layer but "
                           "it has been modified using other tools. "
                           "Editing the layer with the text tool will "
                           "discard these modifications."
                           "\n\n"
                           "You can edit the layer or create a new "
                           "text layer from its text attributes."));
  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  gtk_widget_show (dialog);

  text_tool->confirm_dialog = dialog;
  g_signal_connect_swapped (dialog, "destroy",
                            G_CALLBACK (g_nullify_pointer),
                            &text_tool->confirm_dialog);
}

static void
pika_text_tool_layer_changed (PikaImage    *image,
                              PikaTextTool *text_tool)
{
  GList *layers = pika_image_get_selected_layers (image);

  if (g_list_length (layers) != 1 || layers->data != text_tool->layer)
    {
      PikaTool    *tool    = PIKA_TOOL (text_tool);
      PikaDisplay *display = tool->display;

      if (display)
        {
          PikaLayer *layer = NULL;

          pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);

          if (g_list_length (layers) == 1)
            layer = layers->data;

          /* The tool can only be started when a single layer is
           * selected and this is a text layer.
           */
          if (layer &&
              pika_text_tool_set_drawable (text_tool, PIKA_DRAWABLE (layer),
                                           FALSE) &&
              PIKA_LAYER (text_tool->layer) == layer)
            {
              GError *error = NULL;

              if (! pika_text_tool_start (text_tool, display, layer, &error))
                {
                  pika_text_tool_set_drawable (text_tool, NULL, FALSE);

                  pika_tool_message_literal (tool, display, error->message);

                  g_clear_error (&error);

                  return;
                }
            }
        }
    }
}

static void
pika_text_tool_set_image (PikaTextTool *text_tool,
                          PikaImage    *image)
{
  if (text_tool->image == image)
    return;

  if (text_tool->image)
    {
      g_signal_handlers_disconnect_by_func (text_tool->image,
                                            pika_text_tool_layer_changed,
                                            text_tool);
    }

  g_set_weak_pointer (&text_tool->image, image);

  if (image)
    {
      PikaTextOptions *options = PIKA_TEXT_TOOL_GET_OPTIONS (text_tool);
      gdouble          xres;
      gdouble          yres;

      g_signal_connect_object (text_tool->image, "selected-layers-changed",
                               G_CALLBACK (pika_text_tool_layer_changed),
                               text_tool, 0);

      pika_image_get_resolution (image, &xres, &yres);
      pika_size_entry_set_resolution (PIKA_SIZE_ENTRY (options->size_entry), 0,
                                      yres, FALSE);
    }
}

static gboolean
pika_text_tool_set_drawable (PikaTextTool *text_tool,
                             PikaDrawable *drawable,
                             gboolean      confirm)
{
  PikaImage *image = NULL;

  if (text_tool->confirm_dialog)
    gtk_widget_destroy (text_tool->confirm_dialog);

  if (drawable)
    image = pika_item_get_image (PIKA_ITEM (drawable));

  pika_text_tool_set_image (text_tool, image);

  if (PIKA_IS_TEXT_LAYER (drawable) && PIKA_TEXT_LAYER (drawable)->text)
    {
      PikaTextLayer *layer = PIKA_TEXT_LAYER (drawable);

      if (layer == text_tool->layer && layer->text == text_tool->text)
        return TRUE;

      if (layer->modified)
        {
          if (confirm)
            {
              pika_text_tool_connect (text_tool, layer, NULL);
              pika_text_tool_confirm_dialog (text_tool);
              return TRUE;
            }
        }
      else
        {
          pika_text_tool_connect (text_tool, layer, layer->text);
          return TRUE;
        }
    }

  pika_text_tool_connect (text_tool, NULL, NULL);

  return FALSE;
}

static void
pika_text_tool_block_drawing (PikaTextTool *text_tool)
{
  if (text_tool->drawing_blocked == 0)
    {
      pika_draw_tool_pause (PIKA_DRAW_TOOL (text_tool));

      pika_text_tool_clear_layout (text_tool);
    }

  text_tool->drawing_blocked++;
}

static void
pika_text_tool_unblock_drawing (PikaTextTool *text_tool)
{
  g_return_if_fail (text_tool->drawing_blocked > 0);

  text_tool->drawing_blocked--;

  if (text_tool->drawing_blocked == 0)
    pika_draw_tool_resume (PIKA_DRAW_TOOL (text_tool));
}

static void
pika_text_tool_buffer_begin_edit (PikaTextBuffer *buffer,
                                  PikaTextTool   *text_tool)
{
  pika_text_tool_block_drawing (text_tool);
}

static void
pika_text_tool_buffer_end_edit (PikaTextBuffer *buffer,
                                PikaTextTool   *text_tool)
{
  if (text_tool->text)
    {
      gchar *string;

      if (pika_text_buffer_has_markup (buffer))
        {
          string = pika_text_buffer_get_markup (buffer);

          g_object_set (text_tool->proxy,
                        "markup", string,
                        NULL);
        }
      else
        {
          string = pika_text_buffer_get_text (buffer);

          g_object_set (text_tool->proxy,
                        "text", string,
                        NULL);
        }

      g_free (string);
    }
  else
    {
      pika_text_tool_create_layer (text_tool, NULL);
    }

  pika_text_tool_unblock_drawing (text_tool);
}

static void
pika_text_tool_buffer_color_applied (PikaTextBuffer *buffer,
                                     const PikaRGB  *color,
                                     PikaTextTool   *text_tool)
{
  pika_palettes_add_color_history (PIKA_TOOL (text_tool)->tool_info->pika,
                                   color);
}


/*  public functions  */

void
pika_text_tool_clear_layout (PikaTextTool *text_tool)
{
  g_clear_object (&text_tool->layout);
}

gboolean
pika_text_tool_ensure_layout (PikaTextTool *text_tool)
{
  if (! text_tool->layout && text_tool->text)
    {
      PikaImage *image = pika_item_get_image (PIKA_ITEM (text_tool->layer));
      gdouble    xres;
      gdouble    yres;
      GError    *error = NULL;

      pika_image_get_resolution (image, &xres, &yres);

      text_tool->layout = pika_text_layout_new (text_tool->layer->text,
                                                xres, yres, &error);
      if (error)
        {
          pika_message_literal (image->pika, NULL, PIKA_MESSAGE_ERROR, error->message);
          g_error_free (error);
        }
    }

  return text_tool->layout != NULL;
}

void
pika_text_tool_apply (PikaTextTool *text_tool,
                      gboolean      push_undo)
{
  const GParamSpec *pspec = NULL;
  PikaImage        *image;
  PikaTextLayer    *layer;
  GList            *list;
  gboolean          undo_group = FALSE;

  if (text_tool->idle_id)
    {
      g_source_remove (text_tool->idle_id);
      text_tool->idle_id = 0;

      pika_text_tool_unblock_drawing (text_tool);
    }

  g_return_if_fail (text_tool->text != NULL);
  g_return_if_fail (text_tool->layer != NULL);

  layer = text_tool->layer;
  image = pika_item_get_image (PIKA_ITEM (layer));

  g_return_if_fail (layer->text == text_tool->text);

  /*  Walk over the list of changes and figure out if we are changing
   *  a single property or need to push a full text undo.
   */
  for (list = text_tool->pending;
       list && list->next && list->next->data == list->data;
       list = list->next)
    /* do nothing */;

  if (g_list_length (list) == 1)
    pspec = list->data;

  /*  If we are changing a single property, we don't need to push
   *  an undo if all of the following is true:
   *   - the redo stack is empty
   *   - the last item on the undo stack is a text undo
   *   - the last undo changed the same text property on the same layer
   *   - the last undo happened less than TEXT_UNDO_TIMEOUT seconds ago
   */
  if (pspec)
    {
      PikaUndo *undo = pika_image_undo_can_compress (image, PIKA_TYPE_TEXT_UNDO,
                                                     PIKA_UNDO_TEXT_LAYER);

      if (undo && PIKA_ITEM_UNDO (undo)->item == PIKA_ITEM (layer))
        {
          PikaTextUndo *text_undo = PIKA_TEXT_UNDO (undo);

          if (text_undo->pspec == pspec)
            {
              if (pika_undo_get_age (undo) < TEXT_UNDO_TIMEOUT)
                {
                  PikaTool    *tool = PIKA_TOOL (text_tool);
                  PikaContext *context;

                  context = PIKA_CONTEXT (pika_tool_get_options (tool));

                  push_undo = FALSE;
                  pika_undo_reset_age (undo);
                  pika_undo_refresh_preview (undo, context);
                }
            }
        }
    }

  if (push_undo)
    {
      if (layer->modified)
        {
          undo_group = TRUE;
          pika_image_undo_group_start (image, PIKA_UNDO_GROUP_TEXT, NULL);

          pika_image_undo_push_text_layer_modified (image, NULL, layer);

          /*  see comment in pika_text_layer_set()  */
          pika_image_undo_push_drawable_mod (image, NULL,
                                             PIKA_DRAWABLE (layer), TRUE);
        }

      if (pspec)
        pika_image_undo_push_text_layer (image, NULL, layer, pspec);
    }

  pika_text_tool_apply_list (text_tool, list);

  g_list_free (text_tool->pending);
  text_tool->pending = NULL;

  if (push_undo)
    {
      g_object_set (layer, "modified", FALSE, NULL);

      if (undo_group)
        pika_image_undo_group_end (image);
    }

  pika_text_tool_frame_item (text_tool);

  pika_image_flush (image);
}

gboolean
pika_text_tool_set_layer (PikaTextTool *text_tool,
                          PikaLayer    *layer)
{
  g_return_val_if_fail (PIKA_IS_TEXT_TOOL (text_tool), FALSE);
  g_return_val_if_fail (layer == NULL || PIKA_IS_LAYER (layer), FALSE);

  if (layer == PIKA_LAYER (text_tool->layer))
    return TRUE;

  /*  FIXME this function works, and I have no clue why: first we set
   *  the drawable, then we HALT the tool and start() it without
   *  re-setting the drawable. Why this works perfectly anyway when
   *  double clicking a text layer in the layers dialog... no idea.
   */
  if (pika_text_tool_set_drawable (text_tool, PIKA_DRAWABLE (layer), TRUE))
    {
      PikaTool    *tool = PIKA_TOOL (text_tool);
      PikaItem    *item = PIKA_ITEM (layer);
      PikaContext *context;
      PikaDisplay *display;

      context = pika_get_user_context (tool->tool_info->pika);
      display = pika_context_get_display (context);

      if (! display ||
          pika_display_get_image (display) != pika_item_get_image (item))
        {
          GList *list;

          display = NULL;

          for (list = pika_get_display_iter (tool->tool_info->pika);
               list;
               list = g_list_next (list))
            {
              display = list->data;

              if (pika_display_get_image (display) == pika_item_get_image (item))
                {
                  pika_context_set_display (context, display);
                  break;
                }

              display = NULL;
            }
        }

      if (tool->display)
        pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);

      if (display)
        {
          GError *error = NULL;

          if (! pika_text_tool_start (text_tool, display, layer, &error))
            {
              pika_text_tool_set_drawable (text_tool, NULL, FALSE);

              pika_tool_message_literal (tool, display, error->message);

              g_clear_error (&error);

              return FALSE;
            }

          g_list_free (tool->drawables);
          tool->drawables = g_list_prepend (NULL, PIKA_DRAWABLE (layer));
        }
    }

  return TRUE;
}

gboolean
pika_text_tool_get_has_text_selection (PikaTextTool *text_tool)
{
  GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);

  return gtk_text_buffer_get_has_selection (buffer);
}

void
pika_text_tool_delete_selection (PikaTextTool *text_tool)
{
  GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);

  if (gtk_text_buffer_get_has_selection (buffer))
    {
      gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
    }
}

void
pika_text_tool_cut_clipboard (PikaTextTool *text_tool)
{
  PikaDisplayShell *shell;
  GtkClipboard     *clipboard;

  g_return_if_fail (PIKA_IS_TEXT_TOOL (text_tool));

  shell = pika_display_get_shell (PIKA_TOOL (text_tool)->display);

  clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
                                        GDK_SELECTION_CLIPBOARD);

  gtk_text_buffer_cut_clipboard (GTK_TEXT_BUFFER (text_tool->buffer),
                                 clipboard, TRUE);
}

void
pika_text_tool_copy_clipboard (PikaTextTool *text_tool)
{
  PikaDisplayShell *shell;
  GtkClipboard     *clipboard;

  g_return_if_fail (PIKA_IS_TEXT_TOOL (text_tool));

  shell = pika_display_get_shell (PIKA_TOOL (text_tool)->display);

  clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
                                        GDK_SELECTION_CLIPBOARD);

  /*  need to block "end-user-action" on the text buffer, because
   *  GtkTextBuffer considers copying text to the clipboard an
   *  undo-relevant user action, which is clearly a bug, but what
   *  can we do...
   */
  g_signal_handlers_block_by_func (text_tool->buffer,
                                   pika_text_tool_buffer_begin_edit,
                                   text_tool);
  g_signal_handlers_block_by_func (text_tool->buffer,
                                   pika_text_tool_buffer_end_edit,
                                   text_tool);

  gtk_text_buffer_copy_clipboard (GTK_TEXT_BUFFER (text_tool->buffer),
                                  clipboard);

  g_signal_handlers_unblock_by_func (text_tool->buffer,
                                     pika_text_tool_buffer_end_edit,
                                     text_tool);
  g_signal_handlers_unblock_by_func (text_tool->buffer,
                                     pika_text_tool_buffer_begin_edit,
                                     text_tool);
}

void
pika_text_tool_paste_clipboard (PikaTextTool *text_tool)
{
  PikaDisplayShell *shell;
  GtkClipboard     *clipboard;

  g_return_if_fail (PIKA_IS_TEXT_TOOL (text_tool));

  shell = pika_display_get_shell (PIKA_TOOL (text_tool)->display);

  clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
                                        GDK_SELECTION_CLIPBOARD);

  gtk_text_buffer_paste_clipboard (GTK_TEXT_BUFFER (text_tool->buffer),
                                   clipboard, NULL, TRUE);
}

void
pika_text_tool_create_vectors (PikaTextTool *text_tool)
{
  PikaVectors *vectors;

  g_return_if_fail (PIKA_IS_TEXT_TOOL (text_tool));

  if (! text_tool->text || ! text_tool->image)
    return;

  vectors = pika_text_vectors_new (text_tool->image, text_tool->text);

  if (text_tool->layer)
    {
      gint x, y;

      pika_item_get_offset (PIKA_ITEM (text_tool->layer), &x, &y);
      pika_item_translate (PIKA_ITEM (vectors), x, y, FALSE);
    }

  pika_image_add_vectors (text_tool->image, vectors,
                          PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE);

  pika_image_flush (text_tool->image);
}

gboolean
pika_text_tool_create_vectors_warped (PikaTextTool  *text_tool,
                                      GError       **error)
{
  GList             *vectors0;
  PikaVectors       *vectors;
  gdouble            box_width;
  gdouble            box_height;
  PikaTextDirection  dir;
  gdouble            offset = 0.0;

  g_return_val_if_fail (PIKA_IS_TEXT_TOOL (text_tool), FALSE);

  if (! text_tool->text || ! text_tool->image || ! text_tool->layer)
    {
      if (! text_tool->text)
        g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
                             _("Text is required."));
      if (! text_tool->image)
        g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
                             _("No image."));
      if (! text_tool->layer)
        g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
                             _("No layer."));
      return FALSE;
    }

  box_width  = pika_item_get_width  (PIKA_ITEM (text_tool->layer));
  box_height = pika_item_get_height (PIKA_ITEM (text_tool->layer));

  vectors0 = pika_image_get_selected_vectors (text_tool->image);
  if (g_list_length (vectors0) != 1)
    {
      g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
                           _("Exactly one path must be selected."));
      return FALSE;
    }

  vectors = pika_text_vectors_new (text_tool->image, text_tool->text);

  offset = 0;
  dir = pika_text_tool_get_direction (text_tool);
  switch (dir)
    {
    case PIKA_TEXT_DIRECTION_LTR:
    case PIKA_TEXT_DIRECTION_RTL:
      offset = 0.5 * box_height;
      break;
    case PIKA_TEXT_DIRECTION_TTB_RTL:
    case PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
    case PIKA_TEXT_DIRECTION_TTB_LTR:
    case PIKA_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
      {
        PikaStroke *stroke = NULL;

        while ((stroke = pika_vectors_stroke_get_next (vectors, stroke)))
          {
            pika_stroke_rotate (stroke, 0, 0, 270);
            pika_stroke_translate (stroke, 0, box_width);
          }
      }
      offset = 0.5 * box_width;
      break;
    }

  pika_vectors_warp_vectors (vectors0->data, vectors, offset);

  pika_item_set_visible (PIKA_ITEM (vectors), TRUE, FALSE);

  pika_image_add_vectors (text_tool->image, vectors,
                          PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE);

  pika_image_flush (text_tool->image);

  return TRUE;
}

PikaTextDirection
pika_text_tool_get_direction  (PikaTextTool *text_tool)
{
  PikaTextOptions *options = PIKA_TEXT_TOOL_GET_OPTIONS (text_tool);
  return options->base_dir;
}
