/**
 * gimp_display_shell_scale_image_is_within_viewport:
 * @shell:
 *
 * Returns: %TRUE if the (scaled) image is smaller than and within the
 *          viewport.
 **/
gboolean
gimp_display_shell_scale_image_is_within_viewport (GimpDisplayShell *shell,
                                                   gboolean         *horizontally,
                                                   gboolean         *vertically)
{
  gint     sw, sh;
  gboolean horizontally_dummy, vertically_dummy;

  g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);

  if (! horizontally) horizontally = &horizontally_dummy;
  if (! vertically)   vertically   = &vertically_dummy;

  gimp_display_shell_scale_get_image_size (shell, &sw, &sh);

  *horizontally = sw              <= shell->disp_width       &&
                  shell->offset_x <= 0                       &&
                  shell->offset_x >= sw - shell->disp_width;

  *vertically   = sh              <= shell->disp_height      &&
                  shell->offset_y <= 0                       &&
                  shell->offset_y >= sh - shell->disp_height;

  return *vertically && *horizontally;
}
/**
 * gimp_display_shell_scroll_setup_vscrollbar:
 * @shell:
 * @value:
 *
 * Setup the limits of the vertical scrollbar
 *
 **/
void
gimp_display_shell_scroll_setup_vscrollbar (GimpDisplayShell *shell,
                                            gdouble           value)
{
  gint    sh;
  gdouble lower;
  gdouble upper;

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

  if (! shell->display ||
      ! gimp_display_get_image (shell->display))
    return;

  gimp_display_shell_scale_get_image_size (shell, NULL, &sh);

  if (shell->disp_height < sh)
    {
      lower = MIN (value, 0);
      upper = MAX (value + shell->disp_height, sh);
    }
  else
    {
      lower = MIN (value, -(shell->disp_height - sh) / 2);
      upper = MAX (value + shell->disp_height,
                   sh + (shell->disp_height - sh) / 2);
    }

  g_object_set (shell->vsbdata,
                "lower",          lower,
                "upper",          upper,
                "step-increment", (gdouble) MAX (shell->scale_y,
                                                 MINIMUM_STEP_AMOUNT),
                NULL);
}
/**
 * gimp_display_shell_scroll_center_image:
 * @shell:
 * @horizontally:
 * @vertically:
 *
 * Centers the image in the display shell on the desired axes.
 *
 **/
void
gimp_display_shell_scroll_center_image (GimpDisplayShell *shell,
                                        gboolean          horizontally,
                                        gboolean          vertically)
{
  gint sw, sh;
  gint target_offset_x, target_offset_y;

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

  if (! shell->display                          ||
      ! gimp_display_get_image (shell->display) ||
      (! vertically && ! horizontally))
    return;

  target_offset_x = shell->offset_x;
  target_offset_y = shell->offset_y;

  gimp_display_shell_scale_get_image_size (shell, &sw, &sh);

  if (horizontally)
    {
      target_offset_x = (sw - shell->disp_width) / 2;
    }

  if (vertically)
    {
      target_offset_y = (sh - shell->disp_height) / 2;
    }

  gimp_display_shell_scroll_set_offset (shell,
                                        target_offset_x,
                                        target_offset_y);
}
static void
gimp_display_shell_scale_get_image_center_viewport (GimpDisplayShell *shell,
                                                    gint             *image_center_x,
                                                    gint             *image_center_y)
{
  gint sw, sh;

  gimp_display_shell_scale_get_image_size (shell, &sw, &sh);

  if (image_center_x) *image_center_x = -shell->offset_x + sw / 2;
  if (image_center_y) *image_center_y = -shell->offset_y + sh / 2;
}
static void
gimp_canvas_passe_partout_draw (GimpCanvasItem *item,
                                cairo_t        *cr)
{
  GimpDisplayShell *shell = gimp_canvas_item_get_shell (item);
  gint              w, h;

  gimp_display_shell_scale_get_image_size (shell, &w, &h);
  cairo_rectangle (cr, - shell->offset_x, - shell->offset_y, w, h);

  GIMP_CANVAS_ITEM_CLASS (parent_class)->draw (item, cr);
}
void
gimp_display_shell_rotate_drag (GimpDisplayShell *shell,
                                gdouble           last_x,
                                gdouble           last_y,
                                gdouble           cur_x,
                                gdouble           cur_y,
                                gboolean          constrain)
{
  gint    image_width, image_height;
  gdouble px, py;
  gdouble x1, y1, x2, y2;
  gdouble angle1, angle2, angle;

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

  gimp_display_shell_scale_get_image_size (shell,
                                           &image_width, &image_height);

  px = -shell->offset_x + image_width  / 2;
  py = -shell->offset_y + image_height / 2;

  x1 = cur_x  - px;
  x2 = last_x - px;
  y1 = py - cur_y;
  y2 = py - last_y;

  /*  find the first angle  */
  angle1 = atan2 (y1, x1);

  /*  find the angle  */
  angle2 = atan2 (y2, x2);

  angle = angle2 - angle1;

  if (angle > G_PI || angle < -G_PI)
    angle = angle2 - ((angle1 < 0) ? 2.0 * G_PI + angle1 : angle1 - 2.0 * G_PI);

  shell->rotate_drag_angle += (angle * 180.0 / G_PI);

  if (shell->rotate_drag_angle < 0.0)
    shell->rotate_drag_angle += 360;

  if (shell->rotate_drag_angle >= 360.0)
    shell->rotate_drag_angle -= 360;

  gimp_display_shell_rotate_to (shell,
                                constrain ?
                                (gint) shell->rotate_drag_angle / 15 * 15 :
                                shell->rotate_drag_angle);
}
/**
 * gimp_display_shell_scroll_unoverscrollify:
 * @shell:
 * @in_offset_x:
 * @in_offset_y:
 * @out_offset_x:
 * @out_offset_y:
 *
 * Takes a scroll offset and returns the offset that will not result
 * in a scroll beyond the image border. If the image is already
 * overscrolled, the return value is 0 for that given axis.
 **/
void
gimp_display_shell_scroll_unoverscrollify (GimpDisplayShell *shell,
                                           gint              in_offset_x,
                                           gint              in_offset_y,
                                           gint             *out_offset_x,
                                           gint             *out_offset_y)
{
  gint sw, sh;
  gint out_offset_x_dummy, out_offset_y_dummy;

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

  if (! out_offset_x) out_offset_x = &out_offset_x_dummy;
  if (! out_offset_y) out_offset_y = &out_offset_y_dummy;

  *out_offset_x = in_offset_x;
  *out_offset_y = in_offset_y;

  gimp_display_shell_scale_get_image_size (shell, &sw, &sh);

  if (in_offset_x < 0)
    {
      *out_offset_x = MAX (in_offset_x,
                           MIN (0, 0 - shell->offset_x));
    }
  else if (in_offset_x > 0)
    {
      gint min_offset = sw - shell->disp_width;

      *out_offset_x = MIN (in_offset_x,
                           MAX (0, min_offset - shell->offset_x));
    }

  if (in_offset_y < 0)
    {
      *out_offset_y = MAX (in_offset_y,
                           MIN (0, 0 - shell->offset_y));
    }
  else if (in_offset_y > 0)
    {
      gint min_offset = sh - shell->disp_height;

      *out_offset_y = MIN (in_offset_y,
                           MAX (0, min_offset - shell->offset_y));
    }
}
static cairo_region_t *
gimp_canvas_passe_partout_get_extents (GimpCanvasItem *item)
{
  GimpDisplayShell      *shell = gimp_canvas_item_get_shell (item);
  cairo_rectangle_int_t  rectangle;
  cairo_region_t        *inner;
  cairo_region_t        *outer;

  rectangle.x = - shell->offset_x;
  rectangle.y = - shell->offset_y;
  gimp_display_shell_scale_get_image_size (shell,
                                           &rectangle.width,
                                           &rectangle.height);

  outer = cairo_region_create_rectangle (&rectangle);

  inner = GIMP_CANVAS_ITEM_CLASS (parent_class)->get_extents (item);

  cairo_region_subtract (outer, inner);

  return outer;
}
void
gimp_display_shell_rotate_update_transform (GimpDisplayShell *shell)
{
  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

  g_free (shell->rotate_transform);
  g_free (shell->rotate_untransform);

  if (shell->rotate_angle != 0.0 && gimp_display_get_image (shell->display))
    {
      gint    image_width, image_height;
      gdouble cx, cy;

      shell->rotate_transform   = g_new (cairo_matrix_t, 1);
      shell->rotate_untransform = g_new (cairo_matrix_t, 1);

      gimp_display_shell_scale_get_image_size (shell,
                                               &image_width, &image_height);

      cx = -shell->offset_x + image_width  / 2;
      cy = -shell->offset_y + image_height / 2;

      cairo_matrix_init_translate (shell->rotate_transform, cx, cy);
      cairo_matrix_rotate (shell->rotate_transform,
                           shell->rotate_angle / 180.0 * G_PI);
      cairo_matrix_translate (shell->rotate_transform, -cx, -cy);

      *shell->rotate_untransform = *shell->rotate_transform;
      cairo_matrix_invert (shell->rotate_untransform);
    }
  else
    {
      shell->rotate_transform   = NULL;
      shell->rotate_untransform = NULL;
    }
}
static void
gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell,
                                      cairo_t          *cr)
{
  cairo_rectangle_list_t *clip_rectangles;
  cairo_rectangle_int_t   image_rect;

  image_rect.x = - shell->offset_x;
  image_rect.y = - shell->offset_y;
  gimp_display_shell_scale_get_image_size (shell,
                                           &image_rect.width,
                                           &image_rect.height);


  /*  first, clear the exposed part of the region that is outside the
   *  image, which is the exposed region minus the image rectangle
   */

  cairo_save (cr);

  if (shell->rotate_transform)
    cairo_transform (cr, shell->rotate_transform);

  cairo_rectangle (cr,
                   image_rect.x,
                   image_rect.y,
                   image_rect.width,
                   image_rect.height);

  cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
  cairo_clip (cr);

  if (gdk_cairo_get_clip_rectangle (cr, NULL))
    gimp_display_shell_draw_background (shell, cr);

  cairo_restore (cr);


  /*  then, draw the exposed part of the region that is inside the
   *  image
   */

  cairo_save (cr);
  clip_rectangles = cairo_copy_clip_rectangle_list (cr);

  if (shell->rotate_transform)
    cairo_transform (cr, shell->rotate_transform);

  cairo_rectangle (cr,
                   image_rect.x,
                   image_rect.y,
                   image_rect.width,
                   image_rect.height);
  cairo_clip (cr);

  if (gdk_cairo_get_clip_rectangle (cr, NULL))
    {
      gint i;

      cairo_save (cr);
      gimp_display_shell_draw_checkerboard (shell, cr);
      cairo_restore (cr);

      for (i = 0; i < clip_rectangles->num_rectangles; i++)
        {
          cairo_rectangle_t rect = clip_rectangles->rectangles[i];

          gimp_display_shell_draw_image (shell, cr,
                                         floor (rect.x),
                                         floor (rect.y),
                                         ceil (rect.width),
                                         ceil (rect.height));
        }
    }

  cairo_rectangle_list_destroy (clip_rectangles);
  cairo_restore (cr);


  /*  finally, draw all the remaining image window stuff on top
   */

  /* draw canvas items */
  cairo_save (cr);

  if (shell->rotate_transform)
    cairo_transform (cr, shell->rotate_transform);

  gimp_canvas_item_draw (shell->canvas_item, cr);

  cairo_restore (cr);

  gimp_canvas_item_draw (shell->unrotated_item, cr);

  /* restart (and recalculate) the selection boundaries */
  gimp_display_shell_selection_restart (shell);
}
void
gimp_display_shell_canvas_size_allocate (GtkWidget        *widget,
                                         GtkAllocation    *allocation,
                                         GimpDisplayShell *shell)
{
  /*  are we in destruction?  */
  if (! shell->display || ! gimp_display_get_shell (shell->display))
    return;

  if ((shell->disp_width  != allocation->width) ||
      (shell->disp_height != allocation->height))
    {
      if (shell->zoom_on_resize   &&
          shell->disp_width  > 64 &&
          shell->disp_height > 64 &&
          allocation->width  > 64 &&
          allocation->height > 64)
        {
          gdouble scale = gimp_zoom_model_get_factor (shell->zoom);
          gint    offset_x;
          gint    offset_y;

          /* FIXME: The code is a bit of a mess */

          /*  multiply the zoom_factor with the ratio of the new and
           *  old canvas diagonals
           */
          scale *= (sqrt (SQR (allocation->width) +
                          SQR (allocation->height)) /
                    sqrt (SQR (shell->disp_width) +
                          SQR (shell->disp_height)));

          offset_x = UNSCALEX (shell, shell->offset_x);
          offset_y = UNSCALEX (shell, shell->offset_y);

          gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale);

          shell->offset_x = SCALEX (shell, offset_x);
          shell->offset_y = SCALEY (shell, offset_y);
        }

      shell->disp_width  = allocation->width;
      shell->disp_height = allocation->height;

      /* When we size-allocate due to resize of the top level window,
       * we want some additional logic. Don't apply it on
       * zoom_on_resize though.
       */
      if (shell->size_allocate_from_configure_event &&
          ! shell->zoom_on_resize)
        {
          gboolean center_horizontally;
          gboolean center_vertically;
          gint     target_offset_x;
          gint     target_offset_y;
          gint     sw;
          gint     sh;

          gimp_display_shell_scale_get_image_size (shell, &sw, &sh);

          center_horizontally = sw <= shell->disp_width;
          center_vertically   = sh <= shell->disp_height;

          gimp_display_shell_scroll_center_image (shell,
                                                  center_horizontally,
                                                  center_vertically);

          /* This is basically the best we can do before we get an
           * API for storing the image offset at the start of an
           * image window resize using the mouse
           */
          target_offset_x = shell->offset_x;
          target_offset_y = shell->offset_y;

          if (! center_horizontally)
            {
              target_offset_x = MAX (shell->offset_x, 0);
            }

          if (! center_vertically)
            {
              target_offset_y = MAX (shell->offset_y, 0);
            }

          gimp_display_shell_scroll_set_offset (shell,
                                                target_offset_x,
                                                target_offset_y);
        }

      gimp_display_shell_scroll_clamp_and_update (shell);
      gimp_display_shell_scaled (shell);

      /* Reset */
      shell->size_allocate_from_configure_event = FALSE;
    }
}