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