1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * gtktextregion.h - GtkTextMark based region utility functions
4 *
5 * This file is part of the GtkSourceView widget
6 *
7 * Copyright (C) 2002 Gustavo Gir�ldez <gustavo.giraldez@gmx.net>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330,
22 * Boston, MA 02111-1307, USA.
23 */
24
25 #include <glib.h>
26 #include <vdk/gtktextregion.h>
27
28 typedef struct _Subregion {
29 GtkTextMark *start;
30 GtkTextMark *end;
31 } Subregion;
32
33 struct _GtkTextRegion {
34 GtkTextBuffer *buffer;
35 GList *subregions;
36 };
37
38
39 /* ----------------------------------------------------------------------
40 Private interface
41 ---------------------------------------------------------------------- */
42
43 /* Find and return a subregion node which contains the given text
44 iter. If left_side is TRUE, return the subregion which contains
45 the text iter or which is the leftmost; else return the rightmost
46 subregion */
47 static GList *
find_nearest_subregion(GtkTextRegion * region,const GtkTextIter * iter,GList * begin,gboolean leftmost,gboolean include_edges)48 find_nearest_subregion (GtkTextRegion *region,
49 const GtkTextIter *iter,
50 GList *begin,
51 gboolean leftmost,
52 gboolean include_edges)
53 {
54 GList *l, *retval;
55
56 g_return_val_if_fail (region != NULL && iter != NULL, NULL);
57
58 if (!begin)
59 begin = region->subregions;
60
61 if (begin)
62 retval = begin->prev;
63 else
64 retval = NULL;
65
66 for (l = begin; l; l = l->next) {
67 GtkTextIter sr_iter;
68 Subregion *sr = l->data;
69 gint cmp;
70
71 if (!leftmost) {
72 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_iter, sr->end);
73 cmp = gtk_text_iter_compare (iter, &sr_iter);
74 if (cmp < 0 || (cmp == 0 && include_edges)) {
75 retval = l;
76 break;
77 }
78
79 } else {
80 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_iter, sr->start);
81 cmp = gtk_text_iter_compare (iter, &sr_iter);
82 if (cmp > 0 || (cmp == 0 && include_edges))
83 retval = l;
84 else
85 break;
86 }
87 }
88 return retval;
89 }
90
91 /* ----------------------------------------------------------------------
92 Public interface
93 ---------------------------------------------------------------------- */
94
95 GtkTextRegion *
gtk_text_region_new(GtkTextBuffer * buffer)96 gtk_text_region_new (GtkTextBuffer *buffer)
97 {
98 GtkTextRegion *region;
99
100 g_return_val_if_fail (buffer != NULL, NULL);
101
102 region = g_new (GtkTextRegion, 1);
103 region->buffer = buffer;
104 region->subregions = NULL;
105
106 return region;
107 }
108
109 void
gtk_text_region_destroy(GtkTextRegion * region)110 gtk_text_region_destroy (GtkTextRegion *region)
111 {
112 g_return_if_fail (region != NULL);
113
114 while (region->subregions) {
115 Subregion *sr = region->subregions->data;
116 gtk_text_buffer_delete_mark (region->buffer, sr->start);
117 gtk_text_buffer_delete_mark (region->buffer, sr->end);
118 g_free (sr);
119 region->subregions = g_list_delete_link (region->subregions,
120 region->subregions);
121 }
122 region->buffer = NULL;
123 g_free (region);
124 }
125
126 GtkTextBuffer *
gtk_text_region_get_buffer(GtkTextRegion * region)127 gtk_text_region_get_buffer (GtkTextRegion *region)
128 {
129 g_return_val_if_fail (region != NULL, NULL);
130
131 return region->buffer;
132 }
133
134 void
gtk_text_region_clear_zero_length_subregions(GtkTextRegion * region)135 gtk_text_region_clear_zero_length_subregions (GtkTextRegion *region)
136 {
137 GtkTextIter start, end;
138 GList *node;
139
140 g_return_if_fail (region != NULL);
141
142 for (node = region->subregions; node; ) {
143 Subregion *sr = node->data;
144 gtk_text_buffer_get_iter_at_mark (region->buffer, &start, sr->start);
145 gtk_text_buffer_get_iter_at_mark (region->buffer, &end, sr->end);
146 if (gtk_text_iter_equal (&start, &end)) {
147 gtk_text_buffer_delete_mark (region->buffer, sr->start);
148 gtk_text_buffer_delete_mark (region->buffer, sr->end);
149 g_free (sr);
150 if (node == region->subregions)
151 region->subregions = node = g_list_delete_link (node, node);
152 else
153 node = g_list_delete_link (node, node);
154 } else {
155 node = node->next;
156 }
157 }
158 }
159
160 void
gtk_text_region_add(GtkTextRegion * region,GtkTextIter * start,GtkTextIter * end)161 gtk_text_region_add (GtkTextRegion *region,
162 GtkTextIter *start,
163 GtkTextIter *end)
164 {
165 GList *start_node, *end_node;
166
167 g_return_if_fail (region != NULL && start != NULL && end != NULL);
168
169 gtk_text_iter_order (start, end);
170
171 /* don't add zero-length regions */
172 if (gtk_text_iter_equal (start, end))
173 return;
174
175 /* find bounding subregions */
176 start_node = find_nearest_subregion (region, start, NULL, FALSE, TRUE);
177 end_node = find_nearest_subregion (region, end, start_node, TRUE, TRUE);
178
179 if (start_node == NULL || end_node == NULL || end_node == start_node->prev) {
180 /* create the new subregion */
181 Subregion *sr = g_new0 (Subregion, 1);
182 sr->start = gtk_text_buffer_create_mark (region->buffer, NULL, start, TRUE);
183 sr->end = gtk_text_buffer_create_mark (region->buffer, NULL, end, FALSE);
184
185 if (start_node == NULL) {
186 /* append the new region */
187 region->subregions = g_list_append (region->subregions, sr);
188
189 } else if (end_node == NULL) {
190 /* prepend the new region */
191 region->subregions = g_list_prepend (region->subregions, sr);
192
193 } else {
194 /* we are in the middle of two subregions */
195 GList *tmp = g_list_prepend (start_node, sr);
196
197 }
198 }
199 else {
200 GtkTextIter iter;
201 Subregion *sr = start_node->data;
202 if (start_node != end_node) {
203 /* we need to merge some subregions */
204 GList *l = start_node->next;
205 Subregion *q;
206
207 gtk_text_buffer_delete_mark (region->buffer, sr->end);
208 while (l != end_node) {
209 q = l->data;
210 gtk_text_buffer_delete_mark (region->buffer, q->start);
211 gtk_text_buffer_delete_mark (region->buffer, q->end);
212 g_free (q);
213 l = g_list_delete_link (l, l);
214 }
215 q = l->data;
216 gtk_text_buffer_delete_mark (region->buffer, q->start);
217 sr->end = q->end;
218 g_free (q);
219 GList *tmp = g_list_delete_link (l, l);
220 }
221 /* now move marks if that action expands the region */
222 gtk_text_buffer_get_iter_at_mark (region->buffer, &iter, sr->start);
223 if (gtk_text_iter_compare (&iter, start) > 0)
224 gtk_text_buffer_move_mark (region->buffer, sr->start, start);
225 gtk_text_buffer_get_iter_at_mark (region->buffer, &iter, sr->end);
226 if (gtk_text_iter_compare (&iter, end) < 0)
227 gtk_text_buffer_move_mark (region->buffer, sr->end, end);
228 }
229 }
230
231
232 void
gtk_text_region_substract(GtkTextRegion * region,GtkTextIter * start,GtkTextIter * end)233 gtk_text_region_substract (GtkTextRegion *region,
234 GtkTextIter *start,
235 GtkTextIter *end)
236 {
237 GList *start_node, *end_node, *node;
238 GtkTextIter sr_start_iter, sr_end_iter;
239 gboolean done;
240 gboolean start_is_outside, end_is_outside;
241 Subregion *sr;
242
243 g_return_if_fail (region != NULL && start != NULL && end != NULL);
244
245 gtk_text_iter_order (start, end);
246
247 /* find bounding subregions */
248 start_node = find_nearest_subregion (region, start, NULL, FALSE, FALSE);
249 end_node = find_nearest_subregion (region, end, start_node, TRUE, FALSE);
250
251 /* easy case first */
252 if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
253 return;
254
255 /* deal with the start point */
256 start_is_outside = end_is_outside = FALSE;
257
258 sr = start_node->data;
259 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
260 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
261
262 if (gtk_text_iter_in_range (start, &sr_start_iter, &sr_end_iter) &&
263 !gtk_text_iter_equal (start, &sr_start_iter)) {
264 /* the starting point is inside the first subregion */
265 if (gtk_text_iter_in_range (end, &sr_start_iter, &sr_end_iter) &&
266 !gtk_text_iter_equal (end, &sr_end_iter)) {
267 /* the ending point is also inside the first
268 subregion: we need to split */
269 Subregion *new_sr = g_new0 (Subregion, 1);
270 new_sr->end = sr->end;
271 new_sr->start = gtk_text_buffer_create_mark (region->buffer,
272 NULL, end, TRUE);
273 GList *tmp = g_list_append (start_node, new_sr);
274
275 sr->end = gtk_text_buffer_create_mark (region->buffer,
276 NULL, start, FALSE);
277
278 /* no further processing needed */
279 return;
280
281 } else {
282 /* the ending point is outside, so just move
283 the end of the subregion to the starting point */
284 gtk_text_buffer_move_mark (region->buffer, sr->end, start);
285
286 }
287 } else {
288 /* the starting point is outside (and so to the left)
289 of the first subregion */
290 start_is_outside = TRUE;
291
292 }
293
294 /* deal with the end point */
295 if (start_node != end_node) {
296 sr = end_node->data;
297 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
298 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
299 }
300
301 if (gtk_text_iter_in_range (end, &sr_start_iter, &sr_end_iter) &&
302 !gtk_text_iter_equal (end, &sr_end_iter)) {
303 /* ending point is inside, move the start mark */
304 gtk_text_buffer_move_mark (region->buffer, sr->start, end);
305 } else {
306 end_is_outside = TRUE;
307 }
308
309 /* finally remove any intermediate subregions */
310 done = FALSE;
311 node = start_node;
312
313 while (!done) {
314 if (node == end_node)
315 /* we are done, exit in the next iteration */
316 done = TRUE;
317
318 if ((node == start_node && !start_is_outside) ||
319 (node == end_node && !end_is_outside)) {
320 /* skip starting or ending node */
321 node = node->next;
322 } else {
323 GList *l = node->next;
324 sr = node->data;
325 gtk_text_buffer_delete_mark (region->buffer, sr->start);
326 gtk_text_buffer_delete_mark (region->buffer, sr->end);
327 g_free (sr);
328 region->subregions = g_list_delete_link (region->subregions,
329 node);
330 node = l;
331 }
332 }
333 }
334
335 gint
gtk_text_region_subregions(GtkTextRegion * region)336 gtk_text_region_subregions (GtkTextRegion *region)
337 {
338 g_return_val_if_fail (region != NULL, 0);
339
340 return g_list_length (region->subregions);
341 }
342
343 gboolean
gtk_text_region_nth_subregion(GtkTextRegion * region,guint subregion,GtkTextIter * start,GtkTextIter * end)344 gtk_text_region_nth_subregion (GtkTextRegion *region,
345 guint subregion,
346 GtkTextIter *start,
347 GtkTextIter *end)
348 {
349 Subregion *sr;
350
351 g_return_val_if_fail (region != NULL, FALSE);
352
353 sr = g_list_nth_data (region->subregions, subregion);
354 if (sr == NULL)
355 return FALSE;
356
357 if (start)
358 gtk_text_buffer_get_iter_at_mark (region->buffer, start, sr->start);
359 if (end)
360 gtk_text_buffer_get_iter_at_mark (region->buffer, end, sr->end);
361
362 return TRUE;
363 }
364
365 GtkTextRegion *
gtk_text_region_intersect(GtkTextRegion * region,GtkTextIter * start,GtkTextIter * end)366 gtk_text_region_intersect (GtkTextRegion *region,
367 GtkTextIter *start,
368 GtkTextIter *end)
369 {
370 GList *start_node, *end_node, *node;
371 GtkTextIter sr_start_iter, sr_end_iter;
372 Subregion *sr, *new_sr;
373 gboolean done;
374 GtkTextRegion *new_region;
375
376 g_return_val_if_fail (region != NULL && start != NULL && end != NULL, NULL);
377
378 gtk_text_iter_order (start, end);
379
380 /* find bounding subregions */
381 start_node = find_nearest_subregion (region, start, NULL, FALSE, FALSE);
382 end_node = find_nearest_subregion (region, end, start_node, TRUE, FALSE);
383
384 /* easy case first */
385 if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
386 return NULL;
387
388 new_region = gtk_text_region_new (region->buffer);
389 done = FALSE;
390
391 sr = start_node->data;
392 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
393 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
394
395 /* starting node */
396 if (gtk_text_iter_in_range (start, &sr_start_iter, &sr_end_iter)) {
397 new_sr = g_new0 (Subregion, 1);
398 new_region->subregions = g_list_prepend (new_region->subregions, new_sr);
399
400 new_sr->start = gtk_text_buffer_create_mark (new_region->buffer, NULL,
401 start, TRUE);
402 if (start_node == end_node) {
403 /* things will finish shortly */
404 done = TRUE;
405 if (gtk_text_iter_in_range (end, &sr_start_iter, &sr_end_iter))
406 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer,
407 NULL, end, FALSE);
408 else
409 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer,
410 NULL, &sr_end_iter,
411 FALSE);
412 } else {
413 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
414 &sr_end_iter, FALSE);
415 }
416 node = start_node->next;
417 } else {
418 /* start should be the same as the subregion, so copy it in the loop */
419 node = start_node;
420 }
421
422 if (!done) {
423 while (node != end_node) {
424 /* copy intermediate subregions verbatim */
425 sr = node->data;
426 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter,
427 sr->start);
428 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
429
430 new_sr = g_new0 (Subregion, 1);
431 new_region->subregions = g_list_prepend (new_region->subregions, new_sr);
432 new_sr->start = gtk_text_buffer_create_mark (new_region->buffer, NULL,
433 &sr_start_iter, TRUE);
434 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
435 &sr_end_iter, FALSE);
436 /* next node */
437 node = node->next;
438 }
439
440 /* ending node */
441 sr = node->data;
442 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
443 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
444
445 new_sr = g_new0 (Subregion, 1);
446 new_region->subregions = g_list_prepend (new_region->subregions, new_sr);
447
448 new_sr->start = gtk_text_buffer_create_mark (new_region->buffer, NULL,
449 &sr_start_iter, TRUE);
450
451 if (gtk_text_iter_in_range (end, &sr_start_iter, &sr_end_iter))
452 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
453 end, FALSE);
454 else
455 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
456 &sr_end_iter, FALSE);
457 }
458
459 new_region->subregions = g_list_reverse (new_region->subregions);
460 return new_region;
461 }
462
463 void
gtk_text_region_debug_print(GtkTextRegion * region)464 gtk_text_region_debug_print (GtkTextRegion *region)
465 {
466 GList *l;
467
468 g_return_if_fail (region != NULL);
469
470 g_print ("Subregions: ");
471 l = region->subregions;
472 while (l) {
473 Subregion *sr = l->data;
474 GtkTextIter iter1, iter2;
475 gtk_text_buffer_get_iter_at_mark (region->buffer, &iter1, sr->start);
476 gtk_text_buffer_get_iter_at_mark (region->buffer, &iter2, sr->end);
477 g_print ("%d-%d ", gtk_text_iter_get_offset (&iter1),
478 gtk_text_iter_get_offset (&iter2));
479 l = l->next;
480 }
481 g_print ("\n");
482 }
483
484
485