/* 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
 *
 * 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 "libpikamath/pikamath.h"

#include "display-types.h"
#include "tools/tools-types.h"

#include "config/pikaguiconfig.h"

#include "core/pika.h"
#include "core/pikacontainer.h"
#include "core/pikacontext.h"
#include "core/pikaimage.h"
#include "core/pikaprogress.h"

#include "widgets/pikadialogfactory.h"

#include "tools/pikatool.h"
#include "tools/tool_manager.h"

#include "pikadisplay.h"
#include "pikadisplay-handlers.h"
#include "pikadisplayshell.h"
#include "pikadisplayshell-expose.h"
#include "pikadisplayshell-handlers.h"
#include "pikadisplayshell-render.h"
#include "pikadisplayshell-scroll.h"
#include "pikadisplayshell-scrollbars.h"
#include "pikadisplayshell-title.h"
#include "pikadisplayshell-transform.h"
#include "pikaimagewindow.h"

#include "pika-intl.h"


#define PAINT_AREA_CHUNK_WIDTH  32
#define PAINT_AREA_CHUNK_HEIGHT 32


enum
{
  PROP_0,
  PROP_IMAGE,
  PROP_SHELL
};


struct _PikaDisplayImplPrivate
{
  PikaImage      *image;        /*  pointer to the associated image     */
  gint            instance;     /*  the instance # of this display as
                                 *  taken from the image at creation    */

  GeglRectangle   bounding_box;

  GtkWidget      *shell;
  cairo_region_t *update_region;
};


/*  local function prototypes  */

static void     pika_display_progress_iface_init  (PikaProgressInterface *iface);

static void     pika_display_set_property           (GObject             *object,
                                                     guint                property_id,
                                                     const GValue        *value,
                                                     GParamSpec          *pspec);
static void     pika_display_get_property           (GObject             *object,
                                                     guint                property_id,
                                                     GValue              *value,
                                                     GParamSpec          *pspec);

static gboolean pika_display_impl_present           (PikaDisplay         *display);
static gboolean pika_display_impl_grab_focus        (PikaDisplay         *display);

static PikaProgress * pika_display_progress_start   (PikaProgress        *progress,
                                                     gboolean             cancellable,
                                                     const gchar         *message);
static void     pika_display_progress_end           (PikaProgress        *progress);
static gboolean pika_display_progress_is_active     (PikaProgress        *progress);
static void     pika_display_progress_set_text      (PikaProgress        *progress,
                                                     const gchar         *message);
static void     pika_display_progress_set_value     (PikaProgress        *progress,
                                                     gdouble              percentage);
static gdouble  pika_display_progress_get_value     (PikaProgress        *progress);
static void     pika_display_progress_pulse         (PikaProgress        *progress);
static GBytes * pika_display_progress_get_window_id (PikaProgress        *progress);
static gboolean pika_display_progress_message       (PikaProgress        *progress,
                                                     Pika                *pika,
                                                     PikaMessageSeverity  severity,
                                                     const gchar         *domain,
                                                     const gchar         *message);
static void     pika_display_progress_canceled      (PikaProgress        *progress,
                                                     PikaDisplay         *display);

static void     pika_display_flush_update_region    (PikaDisplay         *display);
static void     pika_display_paint_area             (PikaDisplay         *display,
                                                     gint                 x,
                                                     gint                 y,
                                                     gint                 w,
                                                     gint                 h);


G_DEFINE_TYPE_WITH_CODE (PikaDisplayImpl, pika_display_impl, PIKA_TYPE_DISPLAY,
                         G_ADD_PRIVATE (PikaDisplayImpl)
                         G_IMPLEMENT_INTERFACE (PIKA_TYPE_PROGRESS,
                                                pika_display_progress_iface_init))

#define parent_class pika_display_impl_parent_class


static void
pika_display_impl_class_init (PikaDisplayImplClass *klass)
{
  GObjectClass     *object_class  = G_OBJECT_CLASS (klass);
  PikaDisplayClass *display_class = PIKA_DISPLAY_CLASS (klass);

  object_class->set_property = pika_display_set_property;
  object_class->get_property = pika_display_get_property;

  display_class->present     = pika_display_impl_present;
  display_class->grab_focus  = pika_display_impl_grab_focus;

  g_object_class_install_property (object_class, PROP_IMAGE,
                                   g_param_spec_object ("image",
                                                        NULL, NULL,
                                                        PIKA_TYPE_IMAGE,
                                                        PIKA_PARAM_READABLE));

  g_object_class_install_property (object_class, PROP_SHELL,
                                   g_param_spec_object ("shell",
                                                        NULL, NULL,
                                                        PIKA_TYPE_DISPLAY_SHELL,
                                                        PIKA_PARAM_READABLE));
}

static void
pika_display_impl_init (PikaDisplayImpl *display)
{
  display->priv = pika_display_impl_get_instance_private (display);
}

static void
pika_display_progress_iface_init (PikaProgressInterface *iface)
{
  iface->start         = pika_display_progress_start;
  iface->end           = pika_display_progress_end;
  iface->is_active     = pika_display_progress_is_active;
  iface->set_text      = pika_display_progress_set_text;
  iface->set_value     = pika_display_progress_set_value;
  iface->get_value     = pika_display_progress_get_value;
  iface->pulse         = pika_display_progress_pulse;
  iface->get_window_id = pika_display_progress_get_window_id;
  iface->message       = pika_display_progress_message;
}

static void
pika_display_set_property (GObject      *object,
                           guint         property_id,
                           const GValue *value,
                           GParamSpec   *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_display_get_property (GObject    *object,
                           guint       property_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
  PikaDisplayImpl *display = PIKA_DISPLAY_IMPL (object);

  switch (property_id)
    {
    case PROP_IMAGE:
      g_value_set_object (value, display->priv->image);
      break;

    case PROP_SHELL:
      g_value_set_object (value, display->priv->shell);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static gboolean
pika_display_impl_present (PikaDisplay *display)
{
  pika_display_shell_present (pika_display_get_shell (display));

  return TRUE;
}

static gboolean
pika_display_impl_grab_focus (PikaDisplay *display)
{
  PikaDisplayImpl *display_impl = PIKA_DISPLAY_IMPL (display);

  if (display_impl->priv->shell && pika_display_get_image (display))
    {
      PikaImageWindow *image_window;

      image_window = pika_display_shell_get_window (pika_display_get_shell (display));

      pika_display_present (display);
      gtk_widget_grab_focus (pika_window_get_primary_focus_widget (PIKA_WINDOW (image_window)));

      return TRUE;
    }

  return FALSE;
}

static PikaProgress *
pika_display_progress_start (PikaProgress *progress,
                             gboolean      cancellable,
                             const gchar  *message)
{
  PikaDisplayImpl *display = PIKA_DISPLAY_IMPL (progress);

  if (display->priv->shell)
    return pika_progress_start (PIKA_PROGRESS (display->priv->shell),
                                cancellable,
                                "%s", message);

  return NULL;
}

static void
pika_display_progress_end (PikaProgress *progress)
{
  PikaDisplayImpl *display = PIKA_DISPLAY_IMPL (progress);

  if (display->priv->shell)
    pika_progress_end (PIKA_PROGRESS (display->priv->shell));
}

static gboolean
pika_display_progress_is_active (PikaProgress *progress)
{
  PikaDisplayImpl *display = PIKA_DISPLAY_IMPL (progress);

  if (display->priv->shell)
    return pika_progress_is_active (PIKA_PROGRESS (display->priv->shell));

  return FALSE;
}

static void
pika_display_progress_set_text (PikaProgress *progress,
                                const gchar  *message)
{
  PikaDisplayImpl *display = PIKA_DISPLAY_IMPL (progress);

  if (display->priv->shell)
    pika_progress_set_text_literal (PIKA_PROGRESS (display->priv->shell),
                                    message);
}

static void
pika_display_progress_set_value (PikaProgress *progress,
                                 gdouble       percentage)
{
  PikaDisplayImpl *display = PIKA_DISPLAY_IMPL (progress);

  if (display->priv->shell)
    pika_progress_set_value (PIKA_PROGRESS (display->priv->shell),
                             percentage);
}

static gdouble
pika_display_progress_get_value (PikaProgress *progress)
{
  PikaDisplayImpl *display = PIKA_DISPLAY_IMPL (progress);

  if (display->priv->shell)
    return pika_progress_get_value (PIKA_PROGRESS (display->priv->shell));

  return 0.0;
}

static void
pika_display_progress_pulse (PikaProgress *progress)
{
  PikaDisplayImpl *display = PIKA_DISPLAY_IMPL (progress);

  if (display->priv->shell)
    pika_progress_pulse (PIKA_PROGRESS (display->priv->shell));
}

static GBytes *
pika_display_progress_get_window_id (PikaProgress *progress)
{
  PikaDisplayImpl *display = PIKA_DISPLAY_IMPL (progress);

  if (display->priv->shell)
    return pika_progress_get_window_id (PIKA_PROGRESS (display->priv->shell));

  return NULL;
}

static gboolean
pika_display_progress_message (PikaProgress        *progress,
                               Pika                *pika,
                               PikaMessageSeverity  severity,
                               const gchar         *domain,
                               const gchar         *message)
{
  PikaDisplayImpl *display = PIKA_DISPLAY_IMPL (progress);

  if (display->priv->shell)
    return pika_progress_message (PIKA_PROGRESS (display->priv->shell), pika,
                                  severity, domain, message);

  return FALSE;
}

static void
pika_display_progress_canceled (PikaProgress *progress,
                                PikaDisplay  *display)
{
  pika_progress_cancel (PIKA_PROGRESS (display));
}


/*  public functions  */

PikaDisplay *
pika_display_new (Pika              *pika,
                  PikaImage         *image,
                  PikaUnit           unit,
                  gdouble            scale,
                  PikaUIManager     *popup_manager,
                  PikaDialogFactory *dialog_factory,
                  GdkMonitor        *monitor)
{
  PikaDisplay            *display;
  PikaDisplayImplPrivate *private;
  PikaImageWindow        *window = NULL;
  PikaDisplayShell       *shell;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
  g_return_val_if_fail (image == NULL || PIKA_IS_IMAGE (image), NULL);
  g_return_val_if_fail (GDK_IS_MONITOR (monitor), NULL);

  /*  If there isn't an interface, never create a display  */
  if (pika->no_interface)
    return NULL;

  display = g_object_new (PIKA_TYPE_DISPLAY_IMPL,
                          "pika", pika,
                          NULL);

  private = PIKA_DISPLAY_IMPL (display)->priv;

  /*  refs the image  */
  if (image)
    pika_display_set_image (display, image);

  /*  get an image window  */
  if (PIKA_GUI_CONFIG (display->config)->single_window_mode)
    {
      PikaDisplay *active_display;

      active_display = pika_context_get_display (pika_get_user_context (pika));

      if (! active_display)
        {
          active_display =
            PIKA_DISPLAY (pika_container_get_first_child (pika->displays));
        }

      if (active_display)
        {
          PikaDisplayShell *shell = pika_display_get_shell (active_display);

          window = pika_display_shell_get_window (shell);
        }
    }

  if (! window)
    {
      window = pika_image_window_new (pika,
                                      private->image,
                                      dialog_factory,
                                      monitor);
    }

  /*  create the shell for the image  */
  private->shell = pika_display_shell_new (display, unit, scale,
                                           popup_manager,
                                           monitor);

  shell = pika_display_get_shell (display);

  pika_display_update_bounding_box (display);

  pika_image_window_add_shell (window, shell);
  pika_display_shell_present (shell);

  /* make sure the docks are visible, in case all other image windows
   * are iconified, see bug #686544.
   */
  pika_dialog_factory_show_with_display (dialog_factory);

  g_signal_connect (pika_display_shell_get_statusbar (shell), "cancel",
                    G_CALLBACK (pika_display_progress_canceled),
                    display);

  /* add the display to the list */
  pika_container_add (pika->displays, PIKA_OBJECT (display));

  return display;
}

/**
 * pika_display_delete:
 * @display:
 *
 * Closes the display and removes it from the display list. You should
 * not call this function directly, use pika_display_close() instead.
 */
void
pika_display_delete (PikaDisplay *display)
{
  PikaDisplayImplPrivate *private;
  PikaTool               *active_tool;

  g_return_if_fail (PIKA_IS_DISPLAY (display));

  private = PIKA_DISPLAY_IMPL (display)->priv;

  /* remove the display from the list */
  pika_container_remove (display->pika->displays, PIKA_OBJECT (display));

  /*  unrefs the image  */
  pika_display_set_image (display, NULL);

  active_tool = tool_manager_get_active (display->pika);

  if (active_tool && active_tool->focus_display == display)
    tool_manager_focus_display_active (display->pika, NULL);

  if (private->shell)
    {
      PikaDisplayShell *shell  = pika_display_get_shell (display);
      PikaImageWindow  *window = pika_display_shell_get_window (shell);

      /*  set private->shell to NULL *before* destroying the shell.
       *  all callbacks in pikadisplayshell-callbacks.c will check
       *  this pointer and do nothing if the shell is in destruction.
       */
      private->shell = NULL;

      if (window)
        {
          if (pika_image_window_get_n_shells (window) > 1)
            {
              g_object_ref (shell);

              pika_image_window_remove_shell (window, shell);
              gtk_widget_destroy (GTK_WIDGET (shell));

              g_object_unref (shell);
            }
          else
            {
              pika_image_window_destroy (window);
            }
        }
      else
        {
          g_object_unref (shell);
        }
    }

  g_object_unref (display);
}

/**
 * pika_display_close:
 * @display:
 *
 * Closes the display. If this is the last display, it will remain
 * open, but without an image.
 */
void
pika_display_close (PikaDisplay *display)
{
  g_return_if_fail (PIKA_IS_DISPLAY (display));

  if (pika_container_get_n_children (display->pika->displays) > 1)
    {
      pika_display_delete (display);
    }
  else
    {
      pika_display_empty (display);
    }
}

/**
 * pika_display_get_action_name:
 * @display:
 *
 * Returns: The action name for the given display. The action name
 * depends on the display ID. The result must be freed with g_free().
 **/
gchar *
pika_display_get_action_name (PikaDisplay *display)
{
  g_return_val_if_fail (PIKA_IS_DISPLAY (display), NULL);

  return g_strdup_printf ("windows-display-%04d",
                          pika_display_get_id (display));
}

PikaImage *
pika_display_get_image (PikaDisplay *display)
{
  g_return_val_if_fail (PIKA_IS_DISPLAY (display), NULL);

  return PIKA_DISPLAY_IMPL (display)->priv->image;
}

void
pika_display_set_image (PikaDisplay *display,
                        PikaImage   *image)
{
  PikaDisplayImplPrivate *private;
  PikaImage              *old_image = NULL;
  PikaDisplayShell       *shell;

  g_return_if_fail (PIKA_IS_DISPLAY (display));
  g_return_if_fail (image == NULL || PIKA_IS_IMAGE (image));

  private = PIKA_DISPLAY_IMPL (display)->priv;

  shell = pika_display_get_shell (display);

  if (private->image)
    {
      /*  stop any active tool  */
      tool_manager_control_active (display->pika, PIKA_TOOL_ACTION_HALT,
                                   display);

      pika_display_shell_disconnect (shell);

      pika_display_disconnect (display);

      g_clear_pointer (&private->update_region, cairo_region_destroy);

      pika_image_dec_display_count (private->image);

      /*  set private->image before unrefing because there may be code
       *  that listens for image removals and then iterates the
       *  display list to find a valid display.
       */
      old_image = private->image;

#if 0
      g_print ("%s: image->ref_count before unrefing: %d\n",
               G_STRFUNC, G_OBJECT (old_image)->ref_count);
#endif
    }

  private->image = image;

  if (image)
    {
#if 0
      g_print ("%s: image->ref_count before refing: %d\n",
               G_STRFUNC, G_OBJECT (image)->ref_count);
#endif

      g_object_ref (image);

      private->instance = pika_image_get_instance_count (image);
      pika_image_inc_instance_count (image);

      pika_image_inc_display_count (image);

      pika_display_connect (display);

      if (shell)
        pika_display_shell_connect (shell);
    }

  if (old_image)
    g_object_unref (old_image);

  pika_display_update_bounding_box (display);

  if (shell)
    {
      if (image)
        {
          pika_display_shell_reconnect (shell);
        }
      else
        {
          pika_display_shell_title_update (shell);
        }
    }

  if (old_image != image)
    g_object_notify (G_OBJECT (display), "image");
}

gint
pika_display_get_instance (PikaDisplay *display)
{
  g_return_val_if_fail (PIKA_IS_DISPLAY (display), 0);

  return PIKA_DISPLAY_IMPL (display)->priv->instance;
}

PikaDisplayShell *
pika_display_get_shell (PikaDisplay *display)
{
  g_return_val_if_fail (PIKA_IS_DISPLAY (display), NULL);

  return PIKA_DISPLAY_SHELL (PIKA_DISPLAY_IMPL (display)->priv->shell);
}

void
pika_display_empty (PikaDisplay *display)
{
  PikaDisplayImplPrivate *private;
  GList                 *iter;

  g_return_if_fail (PIKA_IS_DISPLAY (display));

  private = PIKA_DISPLAY_IMPL (display)->priv;

  g_return_if_fail (PIKA_IS_IMAGE (private->image));

  for (iter = display->pika->context_list; iter; iter = g_list_next (iter))
    {
      PikaContext *context = iter->data;

      if (pika_context_get_display (context) == display)
        pika_context_set_image (context, NULL);
    }

  pika_display_set_image (display, NULL);

  pika_display_shell_empty (pika_display_get_shell (display));
}

void
pika_display_fill (PikaDisplay *display,
                   PikaImage   *image,
                   PikaUnit     unit,
                   gdouble      scale)
{
  PikaDisplayImplPrivate *private;

  g_return_if_fail (PIKA_IS_DISPLAY (display));
  g_return_if_fail (PIKA_IS_IMAGE (image));

  private = PIKA_DISPLAY_IMPL (display)->priv;

  g_return_if_fail (private->image == NULL);

  pika_display_set_image (display, image);

  pika_display_shell_fill (pika_display_get_shell (display),
                           image, unit, scale);
}

void
pika_display_update_bounding_box (PikaDisplay *display)
{
  PikaDisplayImplPrivate *private;
  PikaDisplayShell       *shell;
  GeglRectangle           bounding_box = {};

  g_return_if_fail (PIKA_IS_DISPLAY (display));

  private = PIKA_DISPLAY_IMPL (display)->priv;
  shell   = pika_display_get_shell (display);

  if (shell)
    {
      bounding_box = pika_display_shell_get_bounding_box (shell);

      if (! gegl_rectangle_equal (&bounding_box, &private->bounding_box))
        {
          GeglRectangle diff_rects[4];
          gint          n_diff_rects;
          gint          i;

          n_diff_rects = gegl_rectangle_subtract (diff_rects,
                                                  &private->bounding_box,
                                                  &bounding_box);

          for (i = 0; i < n_diff_rects; i++)
            {
              pika_display_paint_area (display,
                                       diff_rects[i].x,     diff_rects[i].y,
                                       diff_rects[i].width, diff_rects[i].height);
            }

          private->bounding_box = bounding_box;

          pika_display_shell_scroll_clamp_and_update (shell);
          pika_display_shell_scrollbars_update (shell);
        }
    }
  else
    {
      private->bounding_box = bounding_box;
    }
}

void
pika_display_update_area (PikaDisplay *display,
                          gboolean     now,
                          gint         x,
                          gint         y,
                          gint         w,
                          gint         h)
{
  PikaDisplayImplPrivate *private;

  g_return_if_fail (PIKA_IS_DISPLAY (display));

  private = PIKA_DISPLAY_IMPL (display)->priv;

  if (now)
    {
      pika_display_paint_area (display, x, y, w, h);
    }
  else
    {
      cairo_rectangle_int_t rect;
      gint                  image_width;
      gint                  image_height;

      image_width  = pika_image_get_width  (private->image);
      image_height = pika_image_get_height (private->image);

      rect.x      = CLAMP (x,     0, image_width);
      rect.y      = CLAMP (y,     0, image_height);
      rect.width  = CLAMP (x + w, 0, image_width)  - rect.x;
      rect.height = CLAMP (y + h, 0, image_height) - rect.y;

      if (private->update_region)
        cairo_region_union_rectangle (private->update_region, &rect);
      else
        private->update_region = cairo_region_create_rectangle (&rect);
    }
}

void
pika_display_flush (PikaDisplay *display)
{
  g_return_if_fail (PIKA_IS_DISPLAY (display));

  /* FIXME: we can end up being called during shell construction if "show all"
   * is enabled by default, in which case the shell's display pointer is still
   * NULL
   */
  if (pika_display_get_shell (display))
    {
      pika_display_flush_update_region (display);

      pika_display_shell_flush (pika_display_get_shell (display));
    }
}

void
pika_display_flush_now (PikaDisplay *display)
{
  g_return_if_fail (PIKA_IS_DISPLAY (display));

  pika_display_flush_update_region (display);
}


/*  private functions  */

static void
pika_display_flush_update_region (PikaDisplay *display)
{
  PikaDisplayImplPrivate *private = PIKA_DISPLAY_IMPL (display)->priv;

  if (private->update_region)
    {
      gint n_rects = cairo_region_num_rectangles (private->update_region);
      gint i;

      for (i = 0; i < n_rects; i++)
        {
          cairo_rectangle_int_t rect;

          cairo_region_get_rectangle (private->update_region,
                                      i, &rect);

          pika_display_paint_area (display,
                                   rect.x,
                                   rect.y,
                                   rect.width,
                                   rect.height);
        }

      g_clear_pointer (&private->update_region, cairo_region_destroy);
    }
}

static void
pika_display_paint_area (PikaDisplay *display,
                         gint         x,
                         gint         y,
                         gint         w,
                         gint         h)
{
  PikaDisplayImplPrivate *private = PIKA_DISPLAY_IMPL (display)->priv;
  PikaDisplayShell       *shell   = pika_display_get_shell (display);
  GeglRectangle           rect;
  gint                    x1, y1, x2, y2;
  gdouble                 x1_f, y1_f, x2_f, y2_f;

  if (! gegl_rectangle_intersect (&rect,
                                  &private->bounding_box,
                                  GEGL_RECTANGLE (x, y, w, h)))
    {
      return;
    }

  /*  display the area  */
  pika_display_shell_transform_bounds (shell,
                                       rect.x,
                                       rect.y,
                                       rect.x + rect.width,
                                       rect.y + rect.height,
                                       &x1_f, &y1_f, &x2_f, &y2_f);

  /*  make sure to expose a superset of the transformed sub-pixel expose
   *  area, not a subset. bug #126942. --mitch
   *
   *  also accommodate for spill introduced by potential box filtering.
   *  (bug #474509). --simon
   */
  x1 = floor (x1_f - 0.5);
  y1 = floor (y1_f - 0.5);
  x2 = ceil (x2_f + 0.5);
  y2 = ceil (y2_f + 0.5);

  /*  align transformed area to a coarse grid, to simplify the
   *  invalidated area
   */
  x1 = floor ((gdouble) x1 / PAINT_AREA_CHUNK_WIDTH)  * PAINT_AREA_CHUNK_WIDTH;
  y1 = floor ((gdouble) y1 / PAINT_AREA_CHUNK_HEIGHT) * PAINT_AREA_CHUNK_HEIGHT;
  x2 = ceil  ((gdouble) x2 / PAINT_AREA_CHUNK_WIDTH)  * PAINT_AREA_CHUNK_WIDTH;
  y2 = ceil  ((gdouble) y2 / PAINT_AREA_CHUNK_HEIGHT) * PAINT_AREA_CHUNK_HEIGHT;

  pika_display_shell_expose_area (shell, x1, y1, x2 - x1, y2 - y1);

  pika_display_shell_render_invalidate_area (shell, x1, y1, x2 - x1, y2 - y1);
}
