gimpimage-arrange.c 11.8 KB
Newer Older
1
/* GIMP - The GNU Image Manipulation Program
2 3
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7 8 9 10 11 12 13 14
 * (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
15
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16 17 18 19
 */

#include "config.h"

20
#include <gdk-pixbuf/gdk-pixbuf.h>
21
#include <gegl.h>
22

23 24
#include "libgimpmath/gimpmath.h"

25 26 27 28 29 30 31 32 33 34 35
#include "core-types.h"

#include "gimpimage.h"
#include "gimpimage-arrange.h"
#include "gimpimage-guides.h"
#include "gimpimage-undo.h"
#include "gimpitem.h"
#include "gimpguide.h"

#include "gimp-intl.h"

36

37 38 39 40 41 42 43 44 45
static GList * sort_by_offset  (GList             *list);
static void    compute_offsets (GList             *list,
                                GimpAlignmentType  alignment);
static void    compute_offset  (GObject           *object,
                                GimpAlignmentType  alignment);
static gint    offset_compare  (gconstpointer      a,
                                gconstpointer      b);


46 47 48 49 50
/**
 * gimp_image_arrange_objects:
 * @image:                The #GimpImage to which the objects belong.
 * @list:                 A #GList of objects to be aligned.
 * @alignment:            The point on each target object to bring into alignment.
51
 * @reference:            The #GObject to align the targets with, or #NULL.
52 53 54
 * @reference_alignment:  The point on the reference object to align the target item with..
 * @offset:               How much to shift the target from perfect alignment..
 *
55 56 57 58 59 60
 * This function shifts the positions of a set of target objects,
 * which can be "items" or guides, to bring them into a specified type
 * of alignment with a reference object, which can be an item, guide,
 * or image.  If the requested alignment does not make sense (i.e.,
 * trying to align a vertical guide vertically), nothing happens and
 * no error message is generated.
61
 *
62 63
 * The objects in the list are sorted into increasing order before
 * being arranged, where the order is defined by the type of alignment
64 65
 * being requested.  If the @reference argument is #NULL, then the
 * first object in the sorted list is used as reference.
66
 *
67 68 69 70
 * When there are multiple target objects, they are arranged so that
 * the spacing between consecutive ones is given by the argument
 * @offset but for HFILL and VFILL - in this case, @offset works as an
 * internal margin for the distribution (and it can be negative).
71 72 73 74 75 76 77 78 79
 */
void
gimp_image_arrange_objects (GimpImage         *image,
                            GList             *list,
                            GimpAlignmentType  alignment,
                            GObject           *reference,
                            GimpAlignmentType  reference_alignment,
                            gint               offset)
{
80 81 82
  gboolean do_x = FALSE;
  gboolean do_y = FALSE;
  gint     z0   = 0;
83
  GList   *object_list;
84 85

  g_return_if_fail (GIMP_IS_IMAGE (image));
86
  g_return_if_fail (G_IS_OBJECT (reference) || reference == NULL);
87

88 89
  /* get offsets used for sorting */
  switch (alignment)
90
    {
91
      /* order vertically for horizontal alignment */
92
    case GIMP_ALIGN_LEFT:
93
    case GIMP_ALIGN_HCENTER:
94 95
    case GIMP_ALIGN_RIGHT:
      do_x = TRUE;
96 97
      compute_offsets (list, GIMP_ALIGN_TOP);
      break;
98

99 100 101 102
      /* order horizontally for horizontal arrangement */
    case GIMP_ARRANGE_LEFT:
    case GIMP_ARRANGE_HCENTER:
    case GIMP_ARRANGE_RIGHT:
103
    case GIMP_ARRANGE_HFILL:
104 105
      do_x = TRUE;
      compute_offsets (list, alignment);
106
      break;
107

108
      /* order horizontally for vertical alignment */
109
    case GIMP_ALIGN_TOP:
110
    case GIMP_ALIGN_VCENTER:
111 112
    case GIMP_ALIGN_BOTTOM:
      do_y = TRUE;
113 114
      compute_offsets (list, GIMP_ALIGN_LEFT);
      break;
115

116 117 118 119
      /* order vertically for vertical arrangement */
    case GIMP_ARRANGE_TOP:
    case GIMP_ARRANGE_VCENTER:
    case GIMP_ARRANGE_BOTTOM:
120
    case GIMP_ARRANGE_VFILL:
121 122
      do_y = TRUE;
      compute_offsets (list, alignment);
123
      break;
124 125 126

    default:
      g_return_if_reached ();
127 128
    }

129
  object_list = sort_by_offset (list);
130

131 132
  /* now get offsets used for aligning */
  compute_offsets (list, alignment);
133

134
  if (reference == NULL)
135
    {
136 137
      reference = G_OBJECT (object_list->data);
      object_list = g_list_next (object_list);
138
      reference_alignment = alignment;
139
    }
140
  else
141 142 143
    {
      compute_offset (reference, reference_alignment);
    }
144

145
  z0 = GPOINTER_TO_INT (g_object_get_data (reference, "align-offset"));
146

147
  if (object_list)
148
    {
149
      GList   *list;
150 151 152 153
      gint     n;
      gint     distr_width  = 0;
      gint     distr_height = 0;
      gdouble  fill_offset  = 0;
154

155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
      if (reference_alignment == GIMP_ARRANGE_HFILL)
        {
          distr_width = GPOINTER_TO_INT (g_object_get_data
                                         (reference, "align-width"));
          /* The offset parameter works as an internal margin */
          fill_offset = (distr_width - 2 * offset) /
                         g_list_length (object_list);
        }
      if (reference_alignment == GIMP_ARRANGE_VFILL)
        {
          distr_height = GPOINTER_TO_INT (g_object_get_data
                                          (reference, "align-height"));
          fill_offset = (distr_height - 2 * offset) /
                         g_list_length (object_list);
        }
170

171 172
      /* FIXME: undo group type is wrong */
      gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_DISPLACE,
173
                                   C_("undo-type", "Arrange Objects"));
174

175 176 177
      for (list = object_list, n = 1;
           list;
           list = g_list_next (list), n++)
178
        {
179 180 181
          GObject *target     = list->data;
          gint     xtranslate = 0;
          gint     ytranslate = 0;
182
          gint     z1;
183

184
          z1 = GPOINTER_TO_INT (g_object_get_data (target, "align-offset"));
185

186 187
          if (reference_alignment == GIMP_ARRANGE_HFILL)
            {
188 189
              gint width = GPOINTER_TO_INT (g_object_get_data (target,
                                                               "align-width"));
190
              xtranslate = ROUND (z0 - z1 + (n - 0.5) * fill_offset -
191
                                  width / 2.0 + offset);
192 193 194
            }
          else if (reference_alignment == GIMP_ARRANGE_VFILL)
            {
195 196
              gint height = GPOINTER_TO_INT (g_object_get_data (target,
                                                                "align-height"));
197
              ytranslate =  ROUND (z0 - z1 + (n - 0.5) * fill_offset -
198
                                   height / 2.0 + offset);
199 200 201 202 203 204 205
            }
          else /* the normal computing, when we don't depend on the
                * width or height of the reference object
                */
            {
              if (do_x)
                xtranslate = z0 - z1 + n * offset;
206

207 208 209
              if (do_y)
                ytranslate = z0 - z1 + n * offset;
            }
210 211 212 213 214 215 216 217 218 219 220 221 222 223

          /* now actually align the target object */
          if (GIMP_IS_ITEM (target))
            {
              gimp_item_translate (GIMP_ITEM (target),
                                   xtranslate, ytranslate, TRUE);
            }
          else if (GIMP_IS_GUIDE (target))
            {
              GimpGuide *guide = GIMP_GUIDE (target);

              switch (gimp_guide_get_orientation (guide))
                {
                case GIMP_ORIENTATION_VERTICAL:
224
                  gimp_image_move_guide (image, guide, z1 + xtranslate, TRUE);
225 226 227
                  break;

                case GIMP_ORIENTATION_HORIZONTAL:
228
                  gimp_image_move_guide (image, guide, z1 + ytranslate, TRUE);
229 230 231 232 233 234 235 236 237
                  break;

                default:
                  break;
                }
            }
        }

      gimp_image_undo_group_end (image);
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
    }

  g_list_free (object_list);
}

static GList *
sort_by_offset (GList *list)
{
  return g_list_sort (g_list_copy (list),
                      offset_compare);

}

static gint
offset_compare (gconstpointer a,
                gconstpointer b)
{
  gint offset1 = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (a),
                                                     "align-offset"));
  gint offset2 = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (b),
                                                     "align-offset"));

  return offset1 - offset2;
}

263
/* this function computes the position of the alignment point
264 265 266 267 268 269 270
 * for each object in the list, and attaches it to the
 * object as object data.
 */
static void
compute_offsets (GList             *list,
                 GimpAlignmentType  alignment)
{
271
  GList *l;
272

273 274
  for (l = list; l; l = g_list_next (l))
    compute_offset (l->data, alignment);
275 276 277
}

static void
278
compute_offset (GObject           *object,
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
                GimpAlignmentType  alignment)
{
  gint object_offset_x = 0;
  gint object_offset_y = 0;
  gint object_height   = 0;
  gint object_width    = 0;
  gint offset          = 0;

  if (GIMP_IS_IMAGE (object))
    {
      GimpImage *image = GIMP_IMAGE (object);

      object_offset_x = 0;
      object_offset_y = 0;
      object_height   = gimp_image_get_height (image);
      object_width    = gimp_image_get_width (image);
    }
  else if (GIMP_IS_ITEM (object))
    {
      GimpItem *item = GIMP_ITEM (object);
299
      gint      off_x, off_y;
300

301 302 303 304 305
      gimp_item_bounds (item,
                        &object_offset_x,
                        &object_offset_y,
                        &object_width,
                        &object_height);
306

307 308 309
      gimp_item_get_offset (item, &off_x, &off_y);
      object_offset_x += off_x;
      object_offset_y += off_y;
310 311 312 313 314 315 316 317 318 319 320
    }
  else if (GIMP_IS_GUIDE (object))
    {
      GimpGuide *guide = GIMP_GUIDE (object);

      switch (gimp_guide_get_orientation (guide))
        {
        case GIMP_ORIENTATION_VERTICAL:
          object_offset_x = gimp_guide_get_position (guide);
          object_width = 0;
          break;
321

322 323 324 325 326 327 328 329
        case GIMP_ORIENTATION_HORIZONTAL:
          object_offset_y = gimp_guide_get_position (guide);
          object_height = 0;
          break;

        default:
          break;
        }
330
    }
331 332 333 334 335 336 337 338 339
  else
    {
      g_printerr ("Alignment object is not an image, item or guide.\n");
    }

  switch (alignment)
    {
    case GIMP_ALIGN_LEFT:
    case GIMP_ARRANGE_LEFT:
340
    case GIMP_ARRANGE_HFILL:
341 342
      offset = object_offset_x;
      break;
343

344 345
    case GIMP_ALIGN_HCENTER:
    case GIMP_ARRANGE_HCENTER:
346
      offset = object_offset_x + object_width / 2;
347
      break;
348

349 350 351 352
    case GIMP_ALIGN_RIGHT:
    case GIMP_ARRANGE_RIGHT:
      offset = object_offset_x + object_width;
      break;
353

354 355
    case GIMP_ALIGN_TOP:
    case GIMP_ARRANGE_TOP:
356
    case GIMP_ARRANGE_VFILL:
357 358
      offset = object_offset_y;
      break;
359

360 361
    case GIMP_ALIGN_VCENTER:
    case GIMP_ARRANGE_VCENTER:
362
      offset = object_offset_y + object_height / 2;
363
      break;
364

365 366 367 368
    case GIMP_ALIGN_BOTTOM:
    case GIMP_ARRANGE_BOTTOM:
      offset = object_offset_y + object_height;
      break;
369

370
    default:
371
      g_return_if_reached ();
372 373 374 375
    }

  g_object_set_data (object, "align-offset",
                     GINT_TO_POINTER (offset));
376

377 378 379 380 381
  /* These are only used for HFILL and VFILL, but since the call to
   * gimp_image_arrange_objects allows for two different alignments
   * (object and reference_alignment) we better be on the safe side in
   * case they differ.  (the current implementation of the align tool
   * always pass the same value to both parameters)
382 383 384 385 386 387
   */
  g_object_set_data (object, "align-width",
                     GINT_TO_POINTER (object_width));

  g_object_set_data (object, "align-height",
                     GINT_TO_POINTER (object_height));
388
}