1 /* Do not edit: this file is generated from https://git.gnome.org/browse/gtksourceview/plain/gtksourceview/gtksourceregion.c */
2
3 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
4 * gspellregion.c - GtkTextMark-based region utility
5 * This file is part of GtkSourceView
6 *
7 * Copyright (C) 2002 Gustavo Giráldez <gustavo.giraldez@gmx.net>
8 * Copyright (C) 2016 Sébastien Wilmet <swilmet@gnome.org>
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include "gspellregion.h"
30
31 /*
32 * SECTION:region
33 * @Short_description: Region utility
34 * @Title: GspellRegion
35 * @See_also: #GtkTextBuffer
36 *
37 * A #GspellRegion permits to store a group of subregions of a
38 * #GtkTextBuffer. #GspellRegion stores the subregions with pairs of
39 * #GtkTextMark's, so the region is still valid after insertions and deletions
40 * in the #GtkTextBuffer.
41 *
42 * The #GtkTextMark for the start of a subregion has a left gravity, while the
43 * #GtkTextMark for the end of a subregion has a right gravity.
44 *
45 * The typical use-case of #GspellRegion is to scan a #GtkTextBuffer chunk by
46 * chunk, not the whole buffer at once to not block the user interface. The
47 * #GspellRegion represents in that case the remaining region to scan. You
48 * can listen to the #GtkTextBuffer::insert-text and
49 * #GtkTextBuffer::delete-range signals to update the #GspellRegion
50 * accordingly.
51 *
52 * To iterate through the subregions, you need to use a #GspellRegionIter,
53 * for example:
54 * |[
55 * GspellRegion *region;
56 * GspellRegionIter region_iter;
57 *
58 * _gspell_region_get_start_region_iter (region, ®ion_iter);
59 *
60 * while (!_gspell_region_iter_is_end (®ion_iter))
61 * {
62 * GtkTextIter subregion_start;
63 * GtkTextIter subregion_end;
64 *
65 * if (!_gspell_region_iter_get_subregion (®ion_iter,
66 * &subregion_start,
67 * &subregion_end))
68 * {
69 * break;
70 * }
71 *
72 * // Do something useful with the subregion.
73 *
74 * _gspell_region_iter_next (®ion_iter);
75 * }
76 * ]|
77 */
78
79 /* With the gravities of the GtkTextMarks, it is possible for subregions to
80 * become interlaced:
81 * Buffer content:
82 * "hello world"
83 * Add two subregions:
84 * "[hello] [world]"
85 * Delete the space:
86 * "[hello][world]"
87 * Undo:
88 * "[hello[ ]world]"
89 *
90 * FIXME: when iterating through the subregions, it should simplify them first.
91 * I don't know if it's done (swilmet).
92 */
93
94 #undef ENABLE_DEBUG
95 /*
96 #define ENABLE_DEBUG
97 */
98
99 #ifdef ENABLE_DEBUG
100 #define DEBUG(x) (x)
101 #else
102 #define DEBUG(x)
103 #endif
104
105 typedef struct _GspellRegionPrivate GspellRegionPrivate;
106 typedef struct _Subregion Subregion;
107 typedef struct _GspellRegionIterReal GspellRegionIterReal;
108
109 struct _GspellRegionPrivate
110 {
111 /* Weak pointer to the buffer. */
112 GtkTextBuffer *buffer;
113
114 /* List of sorted 'Subregion*' */
115 GList *subregions;
116
117 guint32 timestamp;
118 };
119
120 struct _Subregion
121 {
122 GtkTextMark *start;
123 GtkTextMark *end;
124 };
125
126 struct _GspellRegionIterReal
127 {
128 GspellRegion *region;
129 guint32 region_timestamp;
130 GList *subregions;
131 };
132
133 enum
134 {
135 PROP_0,
136 PROP_BUFFER,
137 N_PROPERTIES
138 };
139
140 static GParamSpec *properties[N_PROPERTIES];
141
G_DEFINE_TYPE_WITH_PRIVATE(GspellRegion,_gspell_region,G_TYPE_OBJECT)142 G_DEFINE_TYPE_WITH_PRIVATE (GspellRegion, _gspell_region, G_TYPE_OBJECT)
143
144 #ifdef ENABLE_DEBUG
145 static void
146 print_region (GspellRegion *region)
147 {
148 gchar *str;
149
150 str = _gspell_region_to_string (region);
151 g_print ("%s\n", str);
152 g_free (str);
153 }
154 #endif
155
156 /* Find and return a subregion node which contains the given text
157 * iter. If left_side is TRUE, return the subregion which contains
158 * the text iter or which is the leftmost; else return the rightmost
159 * subregion.
160 */
161 static GList *
find_nearest_subregion(GspellRegion * region,const GtkTextIter * iter,GList * begin,gboolean leftmost,gboolean include_edges)162 find_nearest_subregion (GspellRegion *region,
163 const GtkTextIter *iter,
164 GList *begin,
165 gboolean leftmost,
166 gboolean include_edges)
167 {
168 GspellRegionPrivate *priv = _gspell_region_get_instance_private (region);
169 GList *retval;
170 GList *l;
171
172 g_assert (iter != NULL);
173
174 if (begin == NULL)
175 {
176 begin = priv->subregions;
177 }
178
179 if (begin != NULL)
180 {
181 retval = begin->prev;
182 }
183 else
184 {
185 retval = NULL;
186 }
187
188 for (l = begin; l != NULL; l = l->next)
189 {
190 GtkTextIter sr_iter;
191 Subregion *sr = l->data;
192 gint cmp;
193
194 if (!leftmost)
195 {
196 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_iter, sr->end);
197 cmp = gtk_text_iter_compare (iter, &sr_iter);
198 if (cmp < 0 || (cmp == 0 && include_edges))
199 {
200 retval = l;
201 break;
202 }
203
204 }
205 else
206 {
207 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_iter, sr->start);
208 cmp = gtk_text_iter_compare (iter, &sr_iter);
209 if (cmp > 0 || (cmp == 0 && include_edges))
210 {
211 retval = l;
212 }
213 else
214 {
215 break;
216 }
217 }
218 }
219
220 return retval;
221 }
222
223 static void
_gspell_region_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)224 _gspell_region_get_property (GObject *object,
225 guint prop_id,
226 GValue *value,
227 GParamSpec *pspec)
228 {
229 GspellRegion *region = GSPELL_REGION (object);
230
231 switch (prop_id)
232 {
233 case PROP_BUFFER:
234 g_value_set_object (value, _gspell_region_get_buffer (region));
235 break;
236
237 default:
238 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
239 break;
240 }
241 }
242
243 static void
_gspell_region_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)244 _gspell_region_set_property (GObject *object,
245 guint prop_id,
246 const GValue *value,
247 GParamSpec *pspec)
248 {
249 GspellRegionPrivate *priv = _gspell_region_get_instance_private (GSPELL_REGION (object));
250
251 switch (prop_id)
252 {
253 case PROP_BUFFER:
254 g_assert (priv->buffer == NULL);
255 priv->buffer = g_value_get_object (value);
256 g_object_add_weak_pointer (G_OBJECT (priv->buffer),
257 (gpointer *) &priv->buffer);
258 break;
259
260 default:
261 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
262 break;
263 }
264 }
265
266 static void
_gspell_region_dispose(GObject * object)267 _gspell_region_dispose (GObject *object)
268 {
269 GspellRegionPrivate *priv = _gspell_region_get_instance_private (GSPELL_REGION (object));
270
271 while (priv->subregions != NULL)
272 {
273 Subregion *sr = priv->subregions->data;
274
275 if (priv->buffer != NULL)
276 {
277 gtk_text_buffer_delete_mark (priv->buffer, sr->start);
278 gtk_text_buffer_delete_mark (priv->buffer, sr->end);
279 }
280
281 g_slice_free (Subregion, sr);
282 priv->subregions = g_list_delete_link (priv->subregions, priv->subregions);
283 }
284
285 if (priv->buffer != NULL)
286 {
287 g_object_remove_weak_pointer (G_OBJECT (priv->buffer),
288 (gpointer *) &priv->buffer);
289
290 priv->buffer = NULL;
291 }
292
293 G_OBJECT_CLASS (_gspell_region_parent_class)->dispose (object);
294 }
295
296 static void
_gspell_region_class_init(GspellRegionClass * klass)297 _gspell_region_class_init (GspellRegionClass *klass)
298 {
299 GObjectClass *object_class = G_OBJECT_CLASS (klass);
300
301 object_class->get_property = _gspell_region_get_property;
302 object_class->set_property = _gspell_region_set_property;
303 object_class->dispose = _gspell_region_dispose;
304
305 /*
306 * GspellRegion:buffer:
307 *
308 * The #GtkTextBuffer. The #GspellRegion has a weak reference to the
309 * buffer.
310 *
311 * Since: 3.22
312 */
313 properties[PROP_BUFFER] =
314 g_param_spec_object ("buffer",
315 "Buffer",
316 "",
317 GTK_TYPE_TEXT_BUFFER,
318 G_PARAM_READWRITE |
319 G_PARAM_CONSTRUCT_ONLY |
320 G_PARAM_STATIC_STRINGS);
321
322 g_object_class_install_properties (object_class, N_PROPERTIES, properties);
323 }
324
325 static void
_gspell_region_init(GspellRegion * region)326 _gspell_region_init (GspellRegion *region)
327 {
328 }
329
330 /*
331 * _gspell_region_new:
332 * @buffer: a #GtkTextBuffer.
333 *
334 * Returns: a new #GspellRegion object for @buffer.
335 * Since: 3.22
336 */
337 GspellRegion *
_gspell_region_new(GtkTextBuffer * buffer)338 _gspell_region_new (GtkTextBuffer *buffer)
339 {
340 g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
341
342 return g_object_new (GSPELL_TYPE_REGION,
343 "buffer", buffer,
344 NULL);
345 }
346
347 /*
348 * _gspell_region_get_buffer:
349 * @region: a #GspellRegion.
350 *
351 * Returns: (transfer none) (nullable): the #GtkTextBuffer.
352 * Since: 3.22
353 */
354 GtkTextBuffer *
_gspell_region_get_buffer(GspellRegion * region)355 _gspell_region_get_buffer (GspellRegion *region)
356 {
357 GspellRegionPrivate *priv;
358
359 g_return_val_if_fail (GSPELL_IS_REGION (region), NULL);
360
361 priv = _gspell_region_get_instance_private (region);
362 return priv->buffer;
363 }
364
365 static void
_gspell_region_clear_zero_length_subregions(GspellRegion * region)366 _gspell_region_clear_zero_length_subregions (GspellRegion *region)
367 {
368 GspellRegionPrivate *priv = _gspell_region_get_instance_private (region);
369 GList *node;
370
371 node = priv->subregions;
372 while (node != NULL)
373 {
374 Subregion *sr = node->data;
375 GtkTextIter start;
376 GtkTextIter end;
377
378 gtk_text_buffer_get_iter_at_mark (priv->buffer, &start, sr->start);
379 gtk_text_buffer_get_iter_at_mark (priv->buffer, &end, sr->end);
380
381 if (gtk_text_iter_equal (&start, &end))
382 {
383 gtk_text_buffer_delete_mark (priv->buffer, sr->start);
384 gtk_text_buffer_delete_mark (priv->buffer, sr->end);
385 g_slice_free (Subregion, sr);
386
387 if (node == priv->subregions)
388 {
389 priv->subregions = node = g_list_delete_link (node, node);
390 }
391 else
392 {
393 node = g_list_delete_link (node, node);
394 }
395
396 priv->timestamp++;
397 }
398 else
399 {
400 node = node->next;
401 }
402 }
403 }
404
405 /*
406 * _gspell_region_add_subregion:
407 * @region: a #GspellRegion.
408 * @_start: the start of the subregion.
409 * @_end: the end of the subregion.
410 *
411 * Adds the subregion delimited by @_start and @_end to @region.
412 *
413 * Since: 3.22
414 */
415 void
_gspell_region_add_subregion(GspellRegion * region,const GtkTextIter * _start,const GtkTextIter * _end)416 _gspell_region_add_subregion (GspellRegion *region,
417 const GtkTextIter *_start,
418 const GtkTextIter *_end)
419 {
420 GspellRegionPrivate *priv;
421 GList *start_node;
422 GList *end_node;
423 GtkTextIter start;
424 GtkTextIter end;
425
426 g_return_if_fail (GSPELL_IS_REGION (region));
427 g_return_if_fail (_start != NULL);
428 g_return_if_fail (_end != NULL);
429
430 priv = _gspell_region_get_instance_private (region);
431
432 if (priv->buffer == NULL)
433 {
434 return;
435 }
436
437 start = *_start;
438 end = *_end;
439
440 DEBUG (g_print ("---\n"));
441 DEBUG (print_region (region));
442 DEBUG (g_message ("region_add (%d, %d)",
443 gtk_text_iter_get_offset (&start),
444 gtk_text_iter_get_offset (&end)));
445
446 gtk_text_iter_order (&start, &end);
447
448 /* Don't add zero-length regions. */
449 if (gtk_text_iter_equal (&start, &end))
450 {
451 return;
452 }
453
454 /* Find bounding subregions. */
455 start_node = find_nearest_subregion (region, &start, NULL, FALSE, TRUE);
456 end_node = find_nearest_subregion (region, &end, start_node, TRUE, TRUE);
457
458 if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
459 {
460 /* Create the new subregion. */
461 Subregion *sr = g_slice_new0 (Subregion);
462 sr->start = gtk_text_buffer_create_mark (priv->buffer, NULL, &start, TRUE);
463 sr->end = gtk_text_buffer_create_mark (priv->buffer, NULL, &end, FALSE);
464
465 if (start_node == NULL)
466 {
467 /* Append the new region. */
468 priv->subregions = g_list_append (priv->subregions, sr);
469 }
470 else if (end_node == NULL)
471 {
472 /* Prepend the new region. */
473 priv->subregions = g_list_prepend (priv->subregions, sr);
474 }
475 else
476 {
477 /* We are in the middle of two subregions. */
478 priv->subregions = g_list_insert_before (priv->subregions, start_node, sr);
479 }
480 }
481 else
482 {
483 GtkTextIter iter;
484 Subregion *sr = start_node->data;
485
486 if (start_node != end_node)
487 {
488 /* We need to merge some subregions. */
489 GList *l = start_node->next;
490 Subregion *q;
491
492 gtk_text_buffer_delete_mark (priv->buffer, sr->end);
493
494 while (l != end_node)
495 {
496 q = l->data;
497 gtk_text_buffer_delete_mark (priv->buffer, q->start);
498 gtk_text_buffer_delete_mark (priv->buffer, q->end);
499 g_slice_free (Subregion, q);
500 l = g_list_delete_link (l, l);
501 }
502
503 q = l->data;
504 gtk_text_buffer_delete_mark (priv->buffer, q->start);
505 sr->end = q->end;
506 g_slice_free (Subregion, q);
507 l = g_list_delete_link (l, l);
508 }
509
510 /* Now move marks if that action expands the region. */
511 gtk_text_buffer_get_iter_at_mark (priv->buffer, &iter, sr->start);
512 if (gtk_text_iter_compare (&iter, &start) > 0)
513 {
514 gtk_text_buffer_move_mark (priv->buffer, sr->start, &start);
515 }
516
517 gtk_text_buffer_get_iter_at_mark (priv->buffer, &iter, sr->end);
518 if (gtk_text_iter_compare (&iter, &end) < 0)
519 {
520 gtk_text_buffer_move_mark (priv->buffer, sr->end, &end);
521 }
522 }
523
524 priv->timestamp++;
525
526 DEBUG (print_region (region));
527 }
528
529 /*
530 * _gspell_region_add_region:
531 * @region: a #GspellRegion.
532 * @region_to_add: (nullable): the #GspellRegion to add to @region, or %NULL.
533 *
534 * Adds @region_to_add to @region. @region_to_add is not modified.
535 *
536 * Since: 3.22
537 */
538 void
_gspell_region_add_region(GspellRegion * region,GspellRegion * region_to_add)539 _gspell_region_add_region (GspellRegion *region,
540 GspellRegion *region_to_add)
541 {
542 GspellRegionIter iter;
543 GtkTextBuffer *region_buffer;
544 GtkTextBuffer *region_to_add_buffer;
545
546 g_return_if_fail (GSPELL_IS_REGION (region));
547 g_return_if_fail (region_to_add == NULL || GSPELL_IS_REGION (region_to_add));
548
549 if (region_to_add == NULL)
550 {
551 return;
552 }
553
554 region_buffer = _gspell_region_get_buffer (region);
555 region_to_add_buffer = _gspell_region_get_buffer (region_to_add);
556 g_return_if_fail (region_buffer == region_to_add_buffer);
557
558 if (region_buffer == NULL)
559 {
560 return;
561 }
562
563 _gspell_region_get_start_region_iter (region_to_add, &iter);
564
565 while (!_gspell_region_iter_is_end (&iter))
566 {
567 GtkTextIter subregion_start;
568 GtkTextIter subregion_end;
569
570 if (!_gspell_region_iter_get_subregion (&iter,
571 &subregion_start,
572 &subregion_end))
573 {
574 break;
575 }
576
577 _gspell_region_add_subregion (region,
578 &subregion_start,
579 &subregion_end);
580
581 _gspell_region_iter_next (&iter);
582 }
583 }
584
585 /*
586 * _gspell_region_subtract_subregion:
587 * @region: a #GspellRegion.
588 * @_start: the start of the subregion.
589 * @_end: the end of the subregion.
590 *
591 * Subtracts the subregion delimited by @_start and @_end from @region.
592 *
593 * Since: 3.22
594 */
595 void
_gspell_region_subtract_subregion(GspellRegion * region,const GtkTextIter * _start,const GtkTextIter * _end)596 _gspell_region_subtract_subregion (GspellRegion *region,
597 const GtkTextIter *_start,
598 const GtkTextIter *_end)
599 {
600 GspellRegionPrivate *priv;
601 GList *start_node;
602 GList *end_node;
603 GList *node;
604 GtkTextIter sr_start_iter;
605 GtkTextIter sr_end_iter;
606 gboolean done;
607 gboolean start_is_outside;
608 gboolean end_is_outside;
609 Subregion *sr;
610 GtkTextIter start;
611 GtkTextIter end;
612
613 g_return_if_fail (GSPELL_IS_REGION (region));
614 g_return_if_fail (_start != NULL);
615 g_return_if_fail (_end != NULL);
616
617 priv = _gspell_region_get_instance_private (region);
618
619 if (priv->buffer == NULL)
620 {
621 return;
622 }
623
624 start = *_start;
625 end = *_end;
626
627 DEBUG (g_print ("---\n"));
628 DEBUG (print_region (region));
629 DEBUG (g_message ("region_substract (%d, %d)",
630 gtk_text_iter_get_offset (&start),
631 gtk_text_iter_get_offset (&end)));
632
633 gtk_text_iter_order (&start, &end);
634
635 /* Find bounding subregions. */
636 start_node = find_nearest_subregion (region, &start, NULL, FALSE, FALSE);
637 end_node = find_nearest_subregion (region, &end, start_node, TRUE, FALSE);
638
639 /* Easy case first. */
640 if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
641 {
642 return;
643 }
644
645 /* Deal with the start point. */
646 start_is_outside = end_is_outside = FALSE;
647
648 sr = start_node->data;
649 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_start_iter, sr->start);
650 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_end_iter, sr->end);
651
652 if (gtk_text_iter_in_range (&start, &sr_start_iter, &sr_end_iter) &&
653 !gtk_text_iter_equal (&start, &sr_start_iter))
654 {
655 /* The starting point is inside the first subregion. */
656 if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter) &&
657 !gtk_text_iter_equal (&end, &sr_end_iter))
658 {
659 /* The ending point is also inside the first
660 * subregion: we need to split.
661 */
662 Subregion *new_sr = g_slice_new0 (Subregion);
663 new_sr->end = sr->end;
664 new_sr->start = gtk_text_buffer_create_mark (priv->buffer,
665 NULL,
666 &end,
667 TRUE);
668
669 start_node = g_list_insert_before (start_node, start_node->next, new_sr);
670
671 sr->end = gtk_text_buffer_create_mark (priv->buffer,
672 NULL,
673 &start,
674 FALSE);
675
676 /* No further processing needed. */
677 DEBUG (g_message ("subregion splitted"));
678
679 return;
680 }
681 else
682 {
683 /* The ending point is outside, so just move
684 * the end of the subregion to the starting point.
685 */
686 gtk_text_buffer_move_mark (priv->buffer, sr->end, &start);
687 }
688 }
689 else
690 {
691 /* The starting point is outside (and so to the left)
692 * of the first subregion.
693 */
694 DEBUG (g_message ("start is outside"));
695
696 start_is_outside = TRUE;
697 }
698
699 /* Deal with the end point. */
700 if (start_node != end_node)
701 {
702 sr = end_node->data;
703 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_start_iter, sr->start);
704 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_end_iter, sr->end);
705 }
706
707 if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter) &&
708 !gtk_text_iter_equal (&end, &sr_end_iter))
709 {
710 /* Ending point is inside, move the start mark. */
711 gtk_text_buffer_move_mark (priv->buffer, sr->start, &end);
712 }
713 else
714 {
715 end_is_outside = TRUE;
716 DEBUG (g_message ("end is outside"));
717 }
718
719 /* Finally remove any intermediate subregions. */
720 done = FALSE;
721 node = start_node;
722
723 while (!done)
724 {
725 if (node == end_node)
726 {
727 /* We are done, exit in the next iteration. */
728 done = TRUE;
729 }
730
731 if ((node == start_node && !start_is_outside) ||
732 (node == end_node && !end_is_outside))
733 {
734 /* Skip starting or ending node. */
735 node = node->next;
736 }
737 else
738 {
739 GList *l = node->next;
740 sr = node->data;
741 gtk_text_buffer_delete_mark (priv->buffer, sr->start);
742 gtk_text_buffer_delete_mark (priv->buffer, sr->end);
743 g_slice_free (Subregion, sr);
744 priv->subregions = g_list_delete_link (priv->subregions, node);
745 node = l;
746 }
747 }
748
749 priv->timestamp++;
750
751 DEBUG (print_region (region));
752
753 /* Now get rid of empty subregions. */
754 _gspell_region_clear_zero_length_subregions (region);
755
756 DEBUG (print_region (region));
757 }
758
759 /*
760 * _gspell_region_subtract_region:
761 * @region: a #GspellRegion.
762 * @region_to_subtract: (nullable): the #GspellRegion to subtract from
763 * @region, or %NULL.
764 *
765 * Subtracts @region_to_subtract from @region. @region_to_subtract is not
766 * modified.
767 *
768 * Since: 3.22
769 */
770 void
_gspell_region_subtract_region(GspellRegion * region,GspellRegion * region_to_subtract)771 _gspell_region_subtract_region (GspellRegion *region,
772 GspellRegion *region_to_subtract)
773 {
774 GtkTextBuffer *region_buffer;
775 GtkTextBuffer *region_to_subtract_buffer;
776 GspellRegionIter iter;
777
778 g_return_if_fail (GSPELL_IS_REGION (region));
779 g_return_if_fail (region_to_subtract == NULL || GSPELL_IS_REGION (region_to_subtract));
780
781 region_buffer = _gspell_region_get_buffer (region);
782 region_to_subtract_buffer = _gspell_region_get_buffer (region_to_subtract);
783 g_return_if_fail (region_buffer == region_to_subtract_buffer);
784
785 if (region_buffer == NULL)
786 {
787 return;
788 }
789
790 _gspell_region_get_start_region_iter (region_to_subtract, &iter);
791
792 while (!_gspell_region_iter_is_end (&iter))
793 {
794 GtkTextIter subregion_start;
795 GtkTextIter subregion_end;
796
797 if (!_gspell_region_iter_get_subregion (&iter,
798 &subregion_start,
799 &subregion_end))
800 {
801 break;
802 }
803
804 _gspell_region_subtract_subregion (region,
805 &subregion_start,
806 &subregion_end);
807
808 _gspell_region_iter_next (&iter);
809 }
810 }
811
812 /*
813 * _gspell_region_is_empty:
814 * @region: (nullable): a #GspellRegion, or %NULL.
815 *
816 * Returns whether the @region is empty. A %NULL @region is considered empty.
817 *
818 * Returns: whether the @region is empty.
819 * Since: 3.22
820 */
821 gboolean
_gspell_region_is_empty(GspellRegion * region)822 _gspell_region_is_empty (GspellRegion *region)
823 {
824 GspellRegionIter region_iter;
825
826 if (region == NULL)
827 {
828 return TRUE;
829 }
830
831 /* A #GspellRegion can contain empty subregions. So checking the
832 * number of subregions is not sufficient.
833 * When calling _gspell_region_add_subregion() with equal iters, the
834 * subregion is not added. But when a subregion becomes empty, due to
835 * text deletion, the subregion is not removed from the
836 * #GspellRegion.
837 */
838
839 _gspell_region_get_start_region_iter (region, ®ion_iter);
840
841 while (!_gspell_region_iter_is_end (®ion_iter))
842 {
843 GtkTextIter subregion_start;
844 GtkTextIter subregion_end;
845
846 if (!_gspell_region_iter_get_subregion (®ion_iter,
847 &subregion_start,
848 &subregion_end))
849 {
850 return TRUE;
851 }
852
853 if (!gtk_text_iter_equal (&subregion_start, &subregion_end))
854 {
855 return FALSE;
856 }
857
858 _gspell_region_iter_next (®ion_iter);
859 }
860
861 return TRUE;
862 }
863
864 /*
865 * _gspell_region_get_bounds:
866 * @region: a #GspellRegion.
867 * @start: (out) (optional): iterator to initialize with the start of @region,
868 * or %NULL.
869 * @end: (out) (optional): iterator to initialize with the end of @region,
870 * or %NULL.
871 *
872 * Gets the @start and @end bounds of the @region.
873 *
874 * Returns: %TRUE if @start and @end have been set successfully (if non-%NULL),
875 * or %FALSE if the @region is empty.
876 * Since: 3.22
877 */
878 gboolean
_gspell_region_get_bounds(GspellRegion * region,GtkTextIter * start,GtkTextIter * end)879 _gspell_region_get_bounds (GspellRegion *region,
880 GtkTextIter *start,
881 GtkTextIter *end)
882 {
883 GspellRegionPrivate *priv;
884
885 g_return_val_if_fail (GSPELL_IS_REGION (region), FALSE);
886
887 priv = _gspell_region_get_instance_private (region);
888
889 if (priv->buffer == NULL ||
890 _gspell_region_is_empty (region))
891 {
892 return FALSE;
893 }
894
895 g_assert (priv->subregions != NULL);
896
897 if (start != NULL)
898 {
899 Subregion *first_subregion = priv->subregions->data;
900 gtk_text_buffer_get_iter_at_mark (priv->buffer, start, first_subregion->start);
901 }
902
903 if (end != NULL)
904 {
905 Subregion *last_subregion = g_list_last (priv->subregions)->data;
906 gtk_text_buffer_get_iter_at_mark (priv->buffer, end, last_subregion->end);
907 }
908
909 return TRUE;
910 }
911
912 /*
913 * _gspell_region_intersect_subregion:
914 * @region: a #GspellRegion.
915 * @_start: the start of the subregion.
916 * @_end: the end of the subregion.
917 *
918 * Returns the intersection between @region and the subregion delimited by
919 * @_start and @_end. @region is not modified.
920 *
921 * Returns: (transfer full) (nullable): the intersection as a new
922 * #GspellRegion.
923 * Since: 3.22
924 */
925 GspellRegion *
_gspell_region_intersect_subregion(GspellRegion * region,const GtkTextIter * _start,const GtkTextIter * _end)926 _gspell_region_intersect_subregion (GspellRegion *region,
927 const GtkTextIter *_start,
928 const GtkTextIter *_end)
929 {
930 GspellRegionPrivate *priv;
931 GspellRegion *new_region;
932 GspellRegionPrivate *new_priv;
933 GList *start_node;
934 GList *end_node;
935 GList *node;
936 GtkTextIter sr_start_iter;
937 GtkTextIter sr_end_iter;
938 Subregion *sr;
939 Subregion *new_sr;
940 gboolean done;
941 GtkTextIter start;
942 GtkTextIter end;
943
944 g_return_val_if_fail (GSPELL_IS_REGION (region), NULL);
945 g_return_val_if_fail (_start != NULL, NULL);
946 g_return_val_if_fail (_end != NULL, NULL);
947
948 priv = _gspell_region_get_instance_private (region);
949
950 if (priv->buffer == NULL)
951 {
952 return NULL;
953 }
954
955 start = *_start;
956 end = *_end;
957
958 gtk_text_iter_order (&start, &end);
959
960 /* Find bounding subregions. */
961 start_node = find_nearest_subregion (region, &start, NULL, FALSE, FALSE);
962 end_node = find_nearest_subregion (region, &end, start_node, TRUE, FALSE);
963
964 /* Easy case first. */
965 if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
966 {
967 return NULL;
968 }
969
970 new_region = _gspell_region_new (priv->buffer);
971 new_priv = _gspell_region_get_instance_private (new_region);
972 done = FALSE;
973
974 sr = start_node->data;
975 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_start_iter, sr->start);
976 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_end_iter, sr->end);
977
978 /* Starting node. */
979 if (gtk_text_iter_in_range (&start, &sr_start_iter, &sr_end_iter))
980 {
981 new_sr = g_slice_new0 (Subregion);
982 new_priv->subregions = g_list_prepend (new_priv->subregions, new_sr);
983
984 new_sr->start = gtk_text_buffer_create_mark (new_priv->buffer,
985 NULL,
986 &start,
987 TRUE);
988
989 if (start_node == end_node)
990 {
991 /* Things will finish shortly. */
992 done = TRUE;
993 if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter))
994 {
995 new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
996 NULL,
997 &end,
998 FALSE);
999 }
1000 else
1001 {
1002 new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
1003 NULL,
1004 &sr_end_iter,
1005 FALSE);
1006 }
1007 }
1008 else
1009 {
1010 new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
1011 NULL,
1012 &sr_end_iter,
1013 FALSE);
1014 }
1015
1016 node = start_node->next;
1017 }
1018 else
1019 {
1020 /* start should be the same as the subregion, so copy it in the
1021 * loop.
1022 */
1023 node = start_node;
1024 }
1025
1026 if (!done)
1027 {
1028 while (node != end_node)
1029 {
1030 /* Copy intermediate subregions verbatim. */
1031 sr = node->data;
1032 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_start_iter, sr->start);
1033 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_end_iter, sr->end);
1034
1035 new_sr = g_slice_new0 (Subregion);
1036 new_priv->subregions = g_list_prepend (new_priv->subregions, new_sr);
1037
1038 new_sr->start = gtk_text_buffer_create_mark (new_priv->buffer,
1039 NULL,
1040 &sr_start_iter,
1041 TRUE);
1042
1043 new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
1044 NULL,
1045 &sr_end_iter,
1046 FALSE);
1047
1048 /* Next node. */
1049 node = node->next;
1050 }
1051
1052 /* Ending node. */
1053 sr = node->data;
1054 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_start_iter, sr->start);
1055 gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_end_iter, sr->end);
1056
1057 new_sr = g_slice_new0 (Subregion);
1058 new_priv->subregions = g_list_prepend (new_priv->subregions, new_sr);
1059
1060 new_sr->start = gtk_text_buffer_create_mark (new_priv->buffer,
1061 NULL,
1062 &sr_start_iter,
1063 TRUE);
1064
1065 if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter))
1066 {
1067 new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
1068 NULL,
1069 &end,
1070 FALSE);
1071 }
1072 else
1073 {
1074 new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
1075 NULL,
1076 &sr_end_iter,
1077 FALSE);
1078 }
1079 }
1080
1081 new_priv->subregions = g_list_reverse (new_priv->subregions);
1082 return new_region;
1083 }
1084
1085 /*
1086 * _gspell_region_intersect_region:
1087 * @region1: (nullable): a #GspellRegion, or %NULL.
1088 * @region2: (nullable): a #GspellRegion, or %NULL.
1089 *
1090 * Returns the intersection between @region1 and @region2. @region1 and
1091 * @region2 are not modified.
1092 *
1093 * Returns: (transfer full) (nullable): the intersection as a #GspellRegion
1094 * object.
1095 * Since: 3.22
1096 */
1097 GspellRegion *
_gspell_region_intersect_region(GspellRegion * region1,GspellRegion * region2)1098 _gspell_region_intersect_region (GspellRegion *region1,
1099 GspellRegion *region2)
1100 {
1101 GtkTextBuffer *region1_buffer;
1102 GtkTextBuffer *region2_buffer;
1103 GspellRegion *full_intersect = NULL;
1104 GspellRegionIter region2_iter;
1105
1106 g_return_val_if_fail (region1 == NULL || GSPELL_IS_REGION (region1), NULL);
1107 g_return_val_if_fail (region2 == NULL || GSPELL_IS_REGION (region2), NULL);
1108
1109 if (region1 == NULL && region2 == NULL)
1110 {
1111 return NULL;
1112 }
1113 if (region1 == NULL)
1114 {
1115 return g_object_ref (region2);
1116 }
1117 if (region2 == NULL)
1118 {
1119 return g_object_ref (region1);
1120 }
1121
1122 region1_buffer = _gspell_region_get_buffer (region1);
1123 region2_buffer = _gspell_region_get_buffer (region2);
1124 g_return_val_if_fail (region1_buffer == region2_buffer, NULL);
1125
1126 if (region1_buffer == NULL)
1127 {
1128 return NULL;
1129 }
1130
1131 _gspell_region_get_start_region_iter (region2, ®ion2_iter);
1132
1133 while (!_gspell_region_iter_is_end (®ion2_iter))
1134 {
1135 GtkTextIter subregion2_start;
1136 GtkTextIter subregion2_end;
1137 GspellRegion *sub_intersect;
1138
1139 if (!_gspell_region_iter_get_subregion (®ion2_iter,
1140 &subregion2_start,
1141 &subregion2_end))
1142 {
1143 break;
1144 }
1145
1146 sub_intersect = _gspell_region_intersect_subregion (region1,
1147 &subregion2_start,
1148 &subregion2_end);
1149
1150 if (full_intersect == NULL)
1151 {
1152 full_intersect = sub_intersect;
1153 }
1154 else
1155 {
1156 _gspell_region_add_region (full_intersect, sub_intersect);
1157 g_clear_object (&sub_intersect);
1158 }
1159
1160 _gspell_region_iter_next (®ion2_iter);
1161 }
1162
1163 return full_intersect;
1164 }
1165
1166 static gboolean
check_iterator(GspellRegionIterReal * real)1167 check_iterator (GspellRegionIterReal *real)
1168 {
1169 GspellRegionPrivate *priv;
1170
1171 if (real->region == NULL)
1172 {
1173 goto invalid;
1174 }
1175
1176 priv = _gspell_region_get_instance_private (real->region);
1177
1178 if (real->region_timestamp == priv->timestamp)
1179 {
1180 return TRUE;
1181 }
1182
1183 invalid:
1184 g_warning ("Invalid GspellRegionIter: either the iterator is "
1185 "uninitialized, or the region has been modified since the "
1186 "iterator was created.");
1187
1188 return FALSE;
1189 }
1190
1191 /*
1192 * _gspell_region_get_start_region_iter:
1193 * @region: a #GspellRegion.
1194 * @iter: (out): iterator to initialize to the first subregion.
1195 *
1196 * Initializes a #GspellRegionIter to the first subregion of @region. If
1197 * @region is empty, @iter will be initialized to the end iterator.
1198 *
1199 * Since: 3.22
1200 */
1201 void
_gspell_region_get_start_region_iter(GspellRegion * region,GspellRegionIter * iter)1202 _gspell_region_get_start_region_iter (GspellRegion *region,
1203 GspellRegionIter *iter)
1204 {
1205 GspellRegionPrivate *priv;
1206 GspellRegionIterReal *real;
1207
1208 g_return_if_fail (GSPELL_IS_REGION (region));
1209 g_return_if_fail (iter != NULL);
1210
1211 priv = _gspell_region_get_instance_private (region);
1212 real = (GspellRegionIterReal *)iter;
1213
1214 /* priv->subregions may be NULL, -> end iter */
1215
1216 real->region = region;
1217 real->subregions = priv->subregions;
1218 real->region_timestamp = priv->timestamp;
1219 }
1220
1221 /*
1222 * _gspell_region_iter_is_end:
1223 * @iter: a #GspellRegionIter.
1224 *
1225 * Returns: whether @iter is the end iterator.
1226 * Since: 3.22
1227 */
1228 gboolean
_gspell_region_iter_is_end(GspellRegionIter * iter)1229 _gspell_region_iter_is_end (GspellRegionIter *iter)
1230 {
1231 GspellRegionIterReal *real;
1232
1233 g_return_val_if_fail (iter != NULL, FALSE);
1234
1235 real = (GspellRegionIterReal *)iter;
1236 g_return_val_if_fail (check_iterator (real), FALSE);
1237
1238 return real->subregions == NULL;
1239 }
1240
1241 /*
1242 * _gspell_region_iter_next:
1243 * @iter: a #GspellRegionIter.
1244 *
1245 * Moves @iter to the next subregion.
1246 *
1247 * Returns: %TRUE if @iter moved and is dereferenceable, or %FALSE if @iter has
1248 * been set to the end iterator.
1249 * Since: 3.22
1250 */
1251 gboolean
_gspell_region_iter_next(GspellRegionIter * iter)1252 _gspell_region_iter_next (GspellRegionIter *iter)
1253 {
1254 GspellRegionIterReal *real;
1255
1256 g_return_val_if_fail (iter != NULL, FALSE);
1257
1258 real = (GspellRegionIterReal *)iter;
1259 g_return_val_if_fail (check_iterator (real), FALSE);
1260
1261 if (real->subregions != NULL)
1262 {
1263 real->subregions = real->subregions->next;
1264 return TRUE;
1265 }
1266
1267 return FALSE;
1268 }
1269
1270 /*
1271 * _gspell_region_iter_get_subregion:
1272 * @iter: a #GspellRegionIter.
1273 * @start: (out) (optional): iterator to initialize with the subregion start, or %NULL.
1274 * @end: (out) (optional): iterator to initialize with the subregion end, or %NULL.
1275 *
1276 * Gets the subregion at this iterator.
1277 *
1278 * Returns: %TRUE if @start and @end have been set successfully (if non-%NULL),
1279 * or %FALSE if @iter is the end iterator or if the region is empty.
1280 * Since: 3.22
1281 */
1282 gboolean
_gspell_region_iter_get_subregion(GspellRegionIter * iter,GtkTextIter * start,GtkTextIter * end)1283 _gspell_region_iter_get_subregion (GspellRegionIter *iter,
1284 GtkTextIter *start,
1285 GtkTextIter *end)
1286 {
1287 GspellRegionIterReal *real;
1288 GspellRegionPrivate *priv;
1289 Subregion *sr;
1290
1291 g_return_val_if_fail (iter != NULL, FALSE);
1292
1293 real = (GspellRegionIterReal *)iter;
1294 g_return_val_if_fail (check_iterator (real), FALSE);
1295
1296 if (real->subregions == NULL)
1297 {
1298 return FALSE;
1299 }
1300
1301 priv = _gspell_region_get_instance_private (real->region);
1302
1303 if (priv->buffer == NULL)
1304 {
1305 return FALSE;
1306 }
1307
1308 sr = real->subregions->data;
1309 g_return_val_if_fail (sr != NULL, FALSE);
1310
1311 if (start != NULL)
1312 {
1313 gtk_text_buffer_get_iter_at_mark (priv->buffer, start, sr->start);
1314 }
1315
1316 if (end != NULL)
1317 {
1318 gtk_text_buffer_get_iter_at_mark (priv->buffer, end, sr->end);
1319 }
1320
1321 return TRUE;
1322 }
1323
1324 /*
1325 * _gspell_region_to_string:
1326 * @region: a #GspellRegion.
1327 *
1328 * Gets a string represention of @region, for debugging purposes.
1329 *
1330 * The returned string contains the character offsets of the subregions. It
1331 * doesn't include a newline character at the end of the string.
1332 *
1333 * Returns: (transfer full) (nullable): a string represention of @region. Free
1334 * with g_free() when no longer needed.
1335 * Since: 3.22
1336 */
1337 gchar *
_gspell_region_to_string(GspellRegion * region)1338 _gspell_region_to_string (GspellRegion *region)
1339 {
1340 GspellRegionPrivate *priv;
1341 GString *string;
1342 GList *l;
1343
1344 g_return_val_if_fail (GSPELL_IS_REGION (region), NULL);
1345
1346 priv = _gspell_region_get_instance_private (region);
1347
1348 if (priv->buffer == NULL)
1349 {
1350 return NULL;
1351 }
1352
1353 string = g_string_new ("Subregions:");
1354
1355 for (l = priv->subregions; l != NULL; l = l->next)
1356 {
1357 Subregion *sr = l->data;
1358 GtkTextIter start;
1359 GtkTextIter end;
1360
1361 gtk_text_buffer_get_iter_at_mark (priv->buffer, &start, sr->start);
1362 gtk_text_buffer_get_iter_at_mark (priv->buffer, &end, sr->end);
1363
1364 g_string_append_printf (string,
1365 " %d-%d",
1366 gtk_text_iter_get_offset (&start),
1367 gtk_text_iter_get_offset (&end));
1368 }
1369
1370 return g_string_free (string, FALSE);
1371 }
1372